Kylin构建Cube过程详解
1 前言
在使用kylin的时候,最重要的一步就是创建cube的模型定义,即指定度量和维度以及一些附加信息,然后对cube进行build,当然我们也可以根据原始表中的某一个string字段(这个字段的格式必须是日期格式,表示日期的含义)设定分区字段,这样一个cube就可以进行多次build,每一次的build会生成一个segment,每一个segment对应着一个时间区间的cube,这些segment的时间区间是连续并且不重合的,对于拥有多个segment的cube可以执行merge,相当于将一个时间区间内部的segment合并成一个。下面开始分析cube的build过程。
2 cube示例
以手机销售为例,表sale记录各手机品牌在各个国家,每年的销售情况。表phone是手机品牌,表country是国家列表,两表通过外键与sale表相关联。这三张表就构成星型模型,其中sale是事实表,phone、country是维度表。
现在需要知道各品牌手机于2010-2012年,在中国的总销量,那么查询sql为:
select b.`name`, c.`name`, sum(a.count) from sale as a left join phone as b on a.`pid`=b.`id` left join country as c on a.`cid`=c.`id` where a.`time` >= 2010 and a.`time` <= 2012 and c.`name` = "中国" group by b.`name`
其中时间(time), 手机品牌(b.name,后文用phone代替),国家(c.name,后文用country代替)是维度,而销售数量(a.count)是度量。手机品牌的个数可用于表示手机品牌列的基度。各手机品牌在各年各个国家的销量可作为一个cuboid,所有的cuboid组成一个cube,如下图所示:
上图展示了有3个维度的cube,每个小立方体代表一个cuboid,其中存储的是度量列聚合后的结果,比如苹果在中国2010年的销量就是一个cuboid。
3 入口介绍
在kylin的web页面上创建完成一个cube之后可以点击action下拉框执行build或者merge操作,这两个操作都会调用cube的rebuild接口,调用的参数包括:
- cube名,用于唯一标识一个cube,在当前的kylin版本中cube名是全局唯一的,而不是每一个project下唯一的;
- 本次构建的starttime和endtime,这两个时间区间标识本次构建的segment的数据源只选择这个时间范围内的数据;对于build操作而言,starttime是不需要的,因为它总是会选择最后一个segment的结束时间作为当前segment的起始时间。
- buildtype标识着操作的类型,可以是”build”、”merge”和”refresh”。
4 构建cube过程
kylin中cube的build过程,是将所有的维度组合事先计算,存储于hbase中,以空间换时间,htable对应的rowkey,就是各种维度组合,指标存在column中,这样,将不同维度组合查询sql,转换成基于rowkey的范围扫描,然后对指标进行汇总计算,以实现快速分析查询。整个过程如下图所示:
主要的步骤可以按照顺序分为几个阶段:
- 根据用户的cube信息计算出多个cuboid文件;
- 根据cuboid文件生成htable;
- 更新cube信息;
- 回收临时文件。
每一个阶段操作的输入都需要依赖于上一步的输出,所以这些操作全是顺序执行的。下面对这几个阶段的内容细分为11步具体讲解一下:
4.1 创建hive事实表中间表(create intermediate flat hive table)
这一步的操作会新创建一个hive外部表,然后再根据cube中定义的星状模型,查询出维度和度量的值插入到新创建的表中,这个表是一个外部表,表的数据文件(存储在hdfs)作为下一个子任务的输入。
4.2 重新分配中间表(redistribute flat hive table)
在前面步骤,hive会在hdfs文件夹中生成数据文件,一些文件非常大,一些有些小,甚至是空的。文件分布不平衡会导致随后的mr作业不平衡:一些mappers作业很快执行完毕,但其它的则非常缓慢。为了平衡作业,kylin增加这一步“重新分配”数据。首先,kylin获取到这中间表的行数,然后根据行数的数量,它会重新分配文件需要的数据量。默认情况下,kylin分配每100万行一个文件。
4.3 提取事实表不同列值 (extract fact table distinct columns)
在这一步是根据上一步生成的hive中间表计算出每一个出现在事实表中的维度列的distinct值,并写入到文件中,它是启动一个mr任务完成的,它关联的表就是上一步创建的临时表,如果某一个维度列的distinct值比较大,那么可能导致mr任务执行过程中的oom。
4.4 创建维度字典(build dimension dictionary)
这一步是根据上一步生成的distinct column文件和维度表计算出所有维度的子典信息,并以字典树的方式压缩编码,生成维度字典,子典是为了节约存储而设计的。
每一个cuboid的成员是一个key-value形式存储在hbase中,key是维度成员的组合,但是一般情况下维度是一些字符串之类的值(例如商品名),所以可以通过将每一个维度值转换成唯一整数而减少内存占用,在从hbase查找出对应的key之后再根据子典获取真正的成员值。
4.5 保存cuboid的统计信息(save cuboid statistics)
计算和统计所有的维度组合,并保存,其中,每一种维度组合,称为一个cuboid。理论上来说,一个n维的cube,便有2的n次方种维度组合,参考网上的一个例子,一个cube包含time, item, location, supplier四个维度,那么组合(cuboid)便有16种:
4.6 创建htable
创建一个htable的时候还需要考虑一下几个事情:
- 列簇的设置。
- 每一个列簇的压缩方式。
- 部署coprocessor。
- htable中每一个region的大小。
在这一步中,列簇的设置是根据用户创建cube时候设置的,在hbase中存储的数据key是维度成员的组合,value是对应聚合函数的结果,列簇针对的是value的,一般情况下在创建cube的时候只会设置一个列簇,该列包含所有的聚合函数的结果;
在创建htable时默认使用lzo压缩,如果不支持lzo则不进行压缩,在后面kylin的版本中支持更多的压缩方式;
kylin强依赖于hbase的coprocessor,所以需要在创建htable为该表部署coprocessor,这个文件会首先上传到hbase所在的hdfs上,然后在表的元信息中关联,这一步很容易出现错误,例如coprocessor找不到了就会导致整个regionserver无法启动,所以需要特别小心;region的划分已经在上一步确定了,所以这里不存在动态扩展的情况,所以kylin创建htable使用的接口如下:
public void createtable(final htabledescriptor desc , byte [][] splitkeys)
4.7 用spark引擎构建cube(build cube with spark)
在kylin的cube模型中,每一个cube是由多个cuboid组成的,理论上有n个普通维度的cube可以是由2的n次方个cuboid组成的,那么我们可以计算出最底层的cuboid,也就是包含全部维度的cuboid(相当于执行一个group by全部维度列的查询),然后在根据最底层的cuboid一层一层的向上计算,直到计算出最顶层的cuboid(相当于执行了一个不带group by的查询),其实这个阶段kylin的执行原理就是这个样子的,不过它需要将这些抽象成mapreduce模型,提交spark作业执行。
使用spark,生成每一种维度组合(cuboid)的数据。
build base cuboid data;
build n-dimension cuboid data : 7-dimension;
build n-dimension cuboid data : 6-dimension;
……
build n-dimension cuboid data : 2-dimension;
build cube。
4.8 将cuboid数据转换成hfile(convert cuboid data to hfile)
创建完了htable之后一般会通过插入接口将数据插入到表中,但是由于cuboid中的数据量巨大,频繁的插入会对hbase的性能有非常大的影响,所以kylin采取了首先将cuboid文件转换成htable格式的hfile文件,然后在通过bulkload的方式将文件和htable进行关联,这样可以大大降低hbase的负载,这个过程通过一个mr任务完成。
4.9 导hfile入hbase表(load hfile to hbase table)
将hfile文件load到htable中,这一步完全依赖于hbase的工具。这一步完成之后,数据已经存储到hbase中了,key的格式由cuboid编号+每一个成员在字典树的id组成,value可能保存在多个列组里,包含在原始数据中按照这几个成员进行group by计算出的度量的值。
4.10 更新cube信息(update cube info)
更新cube的状态,其中需要更新的包括cube是否可用、以及本次构建的数据统计,包括构建完成的时间,输入的record数目,输入数据的大小,保存到hbase中数据的大小等,并将这些信息持久到元数据库中。
4.11 清理hive中间表(hive cleanup)
这一步是否成功对正确性不会有任何影响,因为经过上一步之后这个segment就可以在这个cube中被查找到了,但是在整个执行过程中产生了很多的垃圾文件,其中包括:
- 临时的hive表;
- 因为hive表是一个外部表,存储该表的文件也需要额外删除;
- fact distinct这一步将数据写入到hdfs上为建立子典做准备,这时候也可以删除了;
- rowkey统计的时候会生成一个文件,此时可以删除;
- 生成hfile时文件存储的路径和hbase真正存储的路径不同,虽然load是一个remove操作,但是上层的目录还是存在的,也需要删除。
至此整个build过程结束。
上一篇: Flink Time深度解析
下一篇: 基于python的爬虫流程图(精简版)