去年12月挖的坑,今天找时间填上。update:20190119
一、kylin架构
核心是预计算,在此架构上做了一些优化。计算结果存储在Hbase,对Hive等查询转换为Hbase的Scan,提高速度。
缺点亦明显,先天没有AD-HOC能力
二、kylin高可用部署
Kylin的每个结点都可以部署为Job节点(build cube用)兼查询节点。并且每个节点之间对等。因此只要前面加个Nginx做请求转发即可。
Kylin支持通过增加节点水平扩容。
三、kylin on hbase vs kylin on druid
目前的 Kylin 数据存储使用 HBase,存储 Cube 时将维度值和度量值转换成 HBase 的 KeyValue。因为 HBase 不支持二级索引,只有一个行键 (RowKey) 索引,Kylin 的维度值会按照固定的顺序拼接作为 RowKey 存储,那么排在 RowKey 前面的维度,就会获得比后面的维度更好的过滤性能。
也就是说排在rowkey后面的维度查询,效率极低,因为需要scan Hbase的行数非常大,Druid可以解决这个问题。
其特点:
1)数据实时流入,毫秒级延迟即可查询。与ES有些像?
2)支持倒排索引,具有良好的过滤性能。(Hbase只支持一级索引,导致rowkey后面的维度过滤性能差)
这是其较于 Kylin On Hbase最重要的优势。
四、rowkey设计优化
直接贴结论:
1)查询频率高的维度在前
2)基数大的维度在前
两者冲突时,暂时没想明白。
五、维度优化
为什么需要维度优化
因为如果不进行任何维度优化,直接将所有的维度放在一个聚集组里,Kylin就会计算所有的维度组合(cuboid)。
比如,有12个维度,Kylin就会计算2的12次方即4096个cuboid,实际上查询可能用到的cuboid不到1000个,甚至更少。 如果对维度不进行优化,会造成集群计算和存储资源的浪费,也会影响cube的build时间和查询性能,所以我们需要进行cube的维度优化。
聚集组
聚集组:用来控制哪些cuboid需要计算。
适用场景:不是只需要计算base cuboid的情况下,都需要聚集组。
注意事项:一个维度可以出现在多个聚集组中,但是build期间只会计算一次。
如果不设置聚集组,默认情况下只会计算 base cuboid。
聚集组不宜太多。
衍生维度
衍生维度:维表中可以由主键推导出值的列可以作为衍⽣维度。
使用场景:以星型模型接入时。例如用户维表可以从userid推导出用户的姓名,年龄,性别。
优化效果:维度表的N个维度组合成的cuboid个数会从2的N次方降为2。
强制维度
强制维度:所有cuboid必须包含的维度,不会计算不包含强制维度的cuboid。
适用场景:可以将确定在查询时一定会使用的维度设为强制维度。例如,时间维度。
优化效果:将一个维度设为强制维度,则cuboid个数直接减半。
层次维度
层次维度:具有一定层次关系的维度。
使用场景:像年,月,日;国家,省份,城市这类具有层次关系的维度。
优化效果:将N个维度设置为层次维度,则这N个维度组合成的cuboid个数会从2的N次方减少到N+1。
联合维度
联合维度:将几个维度视为一个维度。
适用场景: 1 可以将确定在查询时一定会同时使用的几个维度设为一个联合维度。
2 可以将基数很小的几个维度设为一个联合维度。
3 可以将查询时很少使用的几个维度设为一个联合维度。
优化效果:将N个维度设置为联合维度,则这N个维度组合成的cuboid个数会从2的N次方减少到1。
Extended Column
在OLAP分析场景中,经常存在对某个id进行过滤,但查询结果要展示为name的情况,比如user_id和user_name。这类问题通常有三种解决方式:
a. 将ID和Name都设置为维度,查询语句类似select name, count(*) from table where id = 1 group by id,name。这种方式的问题是会导致维度增多,导致预计算结果膨胀;
b. 将id和name都设置为维度,并且将两者设置为联合。这种方式的好处是保持维度组合数不会增加,但限制了维度的其它优化,比如ID不能再被设置为强制维度或者层次维度;
c. 将ID设置为维度,Name设置为特殊的Measure,类型为Extended Column。这种方式既能保证过滤id且查询name的需求,同时也不影响id维度的进一步优化。
所以此类需求我们推荐使用 Extended Column。
另外需要提一个分片维度,集群目前用错了:
分片维度
Cube支持在构建阶段进行分片以提高效率,可以并只可以将一个维度设为分片(shard by)维度(注意分片维度和cube的partition维度不同,前者作用于构建阶段,后者是用于分割时间段以支持增量构建)。Cube build第二步需要将中间表的数据重新分布到HDFS各个节点上,默认的partition方式是随机,如果指定了分片维度,则改为使用分片维度进行partition。重新分布数据的意义在于防止中间表文件大小相差太大造成数据倾斜,因此分片维度应该是高基数列以保证分片的粒度足够小,可以较为均匀地分布到各个节点上,这能大大加速之后的MapReduce任务。
六、优化实践
优化
1,shop_id维度,由dict编码改为integer编码。is_pre_order修改为boolean编码。
2,前面理解错了partition的含义,它与Hive里面的Partition不同。用于build cube时中间表的HDFS分布,因此粒度超小越好,数据倾斜的可能越小,因此由dt->shop_id
3,Hbase存了太多的表?会不会因为metadata多影响效率?是否可以优化?Merge后自动删除之?什么情况不适应merge?待讨论。
4,设置shop_id, dt为必选维度,减小cube大小以及build时间。
5,hbase表,hive中间表,job中间数据使用snappy压缩,cpu换内存IO ——已经做了
6,Kylin On Druid?目前看还不需要,目前的表及维度查询实际并没达到触发Hbase的瓶颈问题。
七、Hybird模式
修改维度后,查询向前兼容,适用不能重新build旧的数据的情况(可能因为数据太多,build时间太久不可操作,可能由于旧原始数据已经遗失)
例:
原先维度shop_id、dt, 现新增一个维度is_pre_order。
在20190119这天做的cube维度修改。
则以后查询:
1)sql含有is_pre_order且日期>20190119,则自动路由到含is_pre_order dm的新cube。
2)sql含有is_pre_order但日期<= 20190119,则返回空数据。
3)若sql不含is_pre_order,可查所有日期,<=20190119路由到旧的cube,>20190119路由到新的cube。
也就是Hybird使新增dim不需要重新更换cube,dim的修改对用户透明,用户查询代码不需要任何改动。