Kylin作为一个OLAP引擎,需要Cube模型支撑,在我们的工作过程中,在和用户以及相关的开发人员、测试、产品等介绍Kylin的过程中,他们总是会对Cube的模型有一些疑惑,作为经常接触这个概念的我来说这是再明了不过的了,而他们还是会在我讲解多次之后表示还在云里雾里,所以就希望通过一篇关于Cube和Kylin创建Cube的过程来聊一下Cube是什么,以及Kylin的一些高级设置。
Cube一个Cube(多维立方体)其实就是一个多维数组,例如定义一个三维数组int array[M][N][K],每一个维度分别有M、N和K个成员,同样对于一个Cube而言,也可以有三个维度,假设分别为time、location和product,每一个维度的distinct值(称为维度的cardinality)分别是M、N和K个,而数组中的每一个值则是每一个维度取一个值对应的聚合结果,例如array[0][1][2],它就相当于time取第一个值(假设为2016-01-01),location取第二个值(假设为HangZhou)、product取第三个值(假设为Food)对应的一个聚合结果,假设这里的聚合函数为COUNT(1),那么求得的值相当于执行了SELECT COUNT(1) from table where time = ‘2016-01-01’ and location = ‘HangZhou’ and product = ‘Food’,这样当我们有多个聚合函数呢,那么就相当于数组中的每一个元素是一个包含多个值的结构体:
struct{
int count; //保存COUNT(1)的值
double sales; //保存SUM(sales)的值
double cost; //保存SUM(cost)的值
}
如上图,每一个data cell就保存了一个类似的结构体,因此只要能够计算和保存这样的一个多维数组,我们所有的查询就都可以直接定位到多维数组中的一个或者一批值,然后进行过滤得到结果。
但是一个多维数组和Cube模型还是有一些区别,例如在多维数组中我们必须在每一维指定一个值(下标)才能对应得到一个确定的值,但是在Cube中,虽然定义了三个维度,但是我可以只指定两个维度,甚至一个维度都不指定而进行查询,例如执行SELECT COUNT(1) from table where time = ‘2016-01-01’ and location = ‘HangZhou’,这就相当于求数组中array[0][1]的值,但这个返回的是一个数组,而SQL返回的是一个值,因此需要将这个数组中的每一个data cell取出来再进行聚合运算(例如计数、相加等)得到的值才是真正的结果。
好了,到这里可以看出有两种方案计算上面的SQL:1、取出array[0][1]这个数组再进行聚合,2、直接计算出来这个二维数组,那么在这个二维数组上指定time和location就能够对应一个data cell值了。由于可以通过方案1进行再聚合计算,所以理论上如果保存了所有维度的组合(假设N个维度,那么就是一个N为的数组),那么所有的N-1、N-2、…、0维的任何值都是可以通过这个N维数组进行再聚合计算出来的,但是这势必就影响查询的性能。所以这其实就是时间和空间的一个博弈,那么在实践中到底应该如何进行权衡呢,答案是:看需求!也就是建立哪些数组(N个维度,第K层有C(N,K)个K维数组)就要看你真正要执行的查询有哪些了,对经常在一块进行组合查询的维度简历一个数组是再适合不过的了,例如time和location这两个维度老是在一个SQL中出现,那么建立一个这样的二维数组是需要的,而product和location这两个不会在一个SQL中出现,那么这个二维数组就不需要预计算了。那么在Kylin里面是如何决定预计算哪些数组呢?
这里的一切就要从Kylin创建Cube开始说起,在Kylin中创建一个Cube需要以下几步:
1、设置Cube名、描述信息等
2、设置Cube依赖的表模型(星状模型,一个事实表和可选的多个维度表)
3、设置维度(维度有几种类型这里不再讨论,创建完之后就可以暂时性的忽略这几种不同的类型,都把它当做普通的维度就可以了)
4、设置度量(每一个度量包括列和聚合函数,列只能是事实表上的列)
5、设置filter条件(用于对表中的数据进行过滤)
6、设置增量更新的信息(设置增量列和起始时间,该列必须是时间格式列)
7、高级设置(设置维度组、RowKey等)
前面6步的设置比较浅显易懂,那么对于Cube的优化主要通过“高级设置”这一步实现的,这里设置的主要有以下几种:
1、设置Rowkey
2、设置维度组
3、设置Cube Size
在进入到设置RowKey的时候会看到每一个维度的设置(Derived维度看到的是外键列而不是Derived的列),每一个维度可以设置ID(通过拖拽可以改变每一个维度的ID)、Mandatory、Dictionary和Length。
首先看一下Mandatory维度,需要设置为Mandatory的维度是哪些在大多数SQL中都会出现的维度,例如time这个维度,如果每次查询都需要带上它进行过滤或者group by,那么就可以把它设置为mandatory。
维度顺序其次,ID决定了这个维度在数组中执行查找时该维度对应的第一个维度,例如在上例中time的ID就是1,location对应的ID就是2,product对应的ID为3,这个顺序是非常重要的,一般情况我们会将mandatory维度放置在rowkey的最前面,而其它的维度需要将经常出现在过滤条件中的维度放置在靠前的位置,假设在上例的三维数组中,我们经常使用time进行过滤,但是我把time的ID设置为3(location的ID=1,product的ID=2),这时候如果从数组中查找time大于’2016-01-01’并且小于’2016-01-31’,这样的查询就需要从这样的最小的key=<min(location)、min(product)、‘2016-01-01’>扫描到最大的key=<max(location)、max(product)、‘2016-01-31’>,但是如果把time的ID设置为1,扫描的区间就会变成key=<‘2016-01-01’、min(location)、min(product)>到key=<‘2016-01-31’、max(location)、max(product)>,Kylin在实现时需要将Cube的数组存储在Hbase中,然后按照hbase中的rowkey进行扫描,假设min(location)=’BeiJing’、max(location)=’ZhengZhou’, min(product)=’aaaa’,max(product)=’zzzz’,这样在第一种情况下hbase中需要扫描的rowkey范围是[BeiJing-aaaa-2016-01-01, ZhengZhou-zzzz-2016-01-31],而第二种情况需要扫描的rowkey范围是[2016-01-01-BeiJing-aaaa, 2016-01-31-ZhengZhou-zzzz].可以看出第二种情况可以减少扫面的rowkey,查询的性能也就更好了。但是在kylin中并不会存储原始的成员值(例如HangZhou、2016-01-01这样的值),而是需要对它们进行编码,是否需要编码则有后面两个设置项决定。
维度字典Dictionary可以设置为true和false,设置为true表示需要为这个维度建立字典树,如果设置为false则表示不需要设置,而需要设置Length,而Length则意味着在实际存储到hbase的rowkey时使用该维度的前Length个字符作为它的值(剪切每一个成员值只保留前Length个字符),一般情况下是不建议设置Length的,而是设置Dcitionary为true,只有当cardinality比较大时并且只需要取前N个字节就可以表示这个维度时才建议设置Length=N,因为每一个维度的dictionary都会保存在内存中,如果字典树占用很大的内存会影响kylin的使用甚至导致OOM,对于dictionary的编码使用的是字典树,它的原理实际上是为每一个维度成员赋予一个整数的id,实际存储的时候存储的是这个id的二进制值(使用int最多占用4个字节),并且保证每一个id的顺序和维度成员的顺序相同的,例如aaa的id=1,aab的id=2,aac的id=3,这样在查询的时候就可以直接根据column>aaa转换成id>1,方便hbase coprocessor的处理。
维度组设置完了RowKey接下来要设置维度组,维度组的设置主要是为了让不出现在一个查询中的两个维度不计算cuboid(通过划分到两个不同的维度组中),这其实相当于把一个cube的树结构划分成多个不同的树,可以在不降低查询性能的情况下减少cuboid的计算量,目前在Kylin-1.x版本中cuboid的算法有一点的问题,可以参考我对这个算法的改进那篇博文。
Cube Size最后设置CubeSize,该项的设置会对cuboid转换成hfile这一步的计算产生影响,并且影响hbase中表的分区大小,可选值为SMALL、MEDIUM和LARGE,在kylin-1.1版本之后可以在配置文件可以设置这三个配置的分区大小,默认情况下SMALL=10GB,MEDIUM=20GB,LARGE=100GB,在计算完全部的cuboid之后会统计所有cuboid文件中key和value的大小,然后根据这个大小和用户的CubeSize配置决定划分多少region,然后执行一个MR任务计算每一个region的hfile,由于kylin在创建hfile的时候都是通过预分区的方式(通过计算出每一个分区临界值的key),然后批量load到htable的,所以不会导致region的分裂和合并,所以我们还是建议将CubeSize设置为SMALL,并且配置中将small的配置设置为5GB,这样可以提高生成hfile这一步的速度(每一个region负责一个region,减小分区的大小会增加reducer的个数)。
总结好了,本文主要介绍了kylin中创建cube的过程,其中还主要介绍了cube模型的概念,最后详细介绍了kylin在创建cube中的高级设置的优化方案,如果有什么错误的地方,还希望多多指正。