【Apache Kylin 】大数据下的OLAP解决方案(作业构建)

kylin如何实现超大数据集的秒级多维分析查询

预计算

对于超大数据集的复杂查询,既然现场计算需要花费较长时间,那么根据空间换时间的原理,我们就可以提前将所有可能的计算结果计算并存储下来,从而实现超大数据集的秒级多维分析查询。

 

Kylin的预计算是如何实现的

将数据源Hive表中的数据按照指定的维度和指标由计算引擎MapReduce离线计算出所有可能的查询结果(即Cube)存储到HBase中。

Kylin的多维计算主要是体现在OLAP Cube的计算。Cube由多个Cuboid组合而成,Cuboid上的数据是原始数据聚合的数据,因此创建Cube可以看作是在原始数据导入时做的一个预计算预处理的过程。Kylin的强大之处在于充分利用了Hadoop的MapReduce并行处理的能力,高效处理导入的数据。

 ApacheKylin创建Cube的过程如下图所示:

                           【Apache Kylin 】大数据下的OLAP解决方案(作业构建)_第1张图片

下面对构建流程详细分析

在kylin的web页面上创建完成一个cube之后可以点击action下拉框执行build或者merge操作,这两个操作都会调用cube的rebuild接口,调用的参数包括:

1、cube名,用于唯一标识一个cube,在当前的kylin版本中cube名是全局唯一的,而不是每一个project下唯一的;

2、本次构建的startTime和endTime,这两个时间区间标识本次构建的segment的数据源只选择这个时间范围内的数据;对于BUILD操作而言,startTime是不需要的,因为它总是会选择最后一个segment的结束时间作为当前segment的起始时间。

3、buildType标识着操作的类型,可以是”BUILD”、”MERGE”和”REFRESH”。

这些操作的统一入口就是JobService.submitJob函数,该函数首先取出该cube所有关联的构建cube的job,并且判断这些job是否有处于READY、RUNNING、ERROR状态,如果处于该状态意味着这个job正在执行或者可以之后被resume执行。

【Apache Kylin 】大数据下的OLAP解决方案(作业构建)_第2张图片

如果希望build或者merge cube,必须将未完成的cube的操作执行discard操作。然后根据操作类型执行具体的操作:

1. 如果是BUILD操作则执行createBatchCubingJob函数。

2. 如果是MERGE操作则执行createBatchMergeJob函数。

3. 如果是REFRESH,则同样执行createBatchCubingJob函数。为这个时间区间的segment重新构建。

这里采用了工厂模式,如下图所示:

【Apache Kylin 】大数据下的OLAP解决方案(作业构建)_第3张图片


BUILD操作

Build操作是构建一个cube指定时间区间的数据,由于kylin基于预计算的方式提供数据查询,构建操作是指将原始数据(存储在Hadoop中,通过Hive获取)转换成目标数据(存储在Hbase中)的过程。主要的步骤可以按照顺序分为四个阶段:

1、根据用户的cube信息计算出多个cuboid文件。

2、根据cuboid文件生成HTable。

3、更新cube信息。

4、回收临时文件。

每一个阶段操作的输入都需要依赖于上一步的输出,所以这些操作全是顺序执行的。

在kylin的CUBE模型中,每一个cube是由多个cuboid组成的,理论上有N个普通维度的cube可以是由2的N次方个cuboid组成的,那么我们可以计算出最底层的cuboid,也就是包含全部维度的cuboid(相当于执行一个group by全部维度列的查询),然后在根据最底层的cuboid一层一层的向上计算,直到计算出最顶层的cuboid(相当于执行了一个不带group by的查询),其实这个阶段kylin的执行原理就是这个样子的,不过它需要将这些抽象成mapreduce模型,提交mapreduce作业执行。

 

1. 生成原始数据(CreateIntermediate Flat Hive Table)

这一步的操作是根据cube的定义生成原始数据,这里会新创建一个hive外部表,然后再根据cube中定义的星状模型,查询出维度(对于DERIVED类型的维度使用的是外键列)和度量的值插入到新创建的表中,这个表是一个外部表,表的数据文件(存储在HDFS)作为下一个子任务的输入,它首先根据维度中的列和度量中作为参数的列得到需要出现在该表中的列,然后执行三步hive操作,这三步hive操作是通过hive-e的方式执行的shell命令。

第一步. drop TABLE IFEXISTS xxx.

第二步. CREATE EXTERNALTABLE IF NOT EXISTS xxx() ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘\177’STORED AS SEQUENCEFILE LOCATION xxxx,

其中表名是根据当前的cube名和segment的uuid生成的,location是当前job的临时文件,只有当insert插入数据的时候才会创建,注意这里每一行的分隔符指定的是’\177’。

第三步. 插入数据,在执行之前需要首先设置一些配置项,这些配置项通过hive的SET命令设置,是根据这个cube的job的配置文件(一般是在kylin的conf目录下,kylin_job_conf.xml)设置的,最后执行的是INSERTOVERWRITE TABLE xxx SELECT xxxx语句,SELECT子句中选出cube星状模型中事实表与维度表按照设置的方式join之后的出现在维度或者度量参数中的列(特殊处理derived列),然后再加上用户设置的where条件和partition的时间条件(根据输入build的参数).这一步执行完成之后location指定的目录下就有了原始数据的文件,为接下来的任务提供了输入。

 

2.重新分配中间表(Redistributeintermediate table)

在前面步骤,hive会在HDFS文件夹中生成数据文件,一些文件非常大,一些有些小,甚至是空的。文件分布不平衡会导致随后的MR作业不平衡:一些mappers作业很快执行完毕,但其它的则非常缓慢。为了平衡作业,kylin增加这一步“重新分配”数据,例如:


Redistribute table, cmd:

【Apache Kylin 】大数据下的OLAP解决方案(作业构建)_第4张图片

首先,kylin获取到这中间表的行数,然后根据行数的数量,它会重新分配文件需要的数据量。默认情况下,kylin分配每100万行一个文件。

然后,kylin执行hivesql命令” INSERT OVERWIRTE TABLE ….DISTRIBUTE BY”通过指定的reducers来重新分布rows。


3.创建事实表distinct column文件(Extract Fact Table Distinct Columns)

在这一步是根据上一步生成的hive表计算出还表中的每一个出现在事实表中的度量的distinct值,并写入到文件中,它是启动一个MR任务完成的,MR任务的输入是HCatInputFormat,它关联的表就是上一步创建的临时表,这个MR任务的map阶段首先在setup函数中得到所有度量中出现在事实表的度量在临时表的index,根据每一个index得到该列在临时表中在每一行的值value,然后将作为mapper的输出,该任务还启动了一个combiner,它所做的只是对同一个key的值进行去重(同一个mapper的结果),reducer所做的事情也是进行去重(所有mapper的结果),然后将每一个index对应的值一行行的写入到以列名命名的文件中。如果某一个维度列的distinct值比较大,那么可能导致MR任务执行过程中的OOM。


4. 创建维度词典(Build DimensionDictionary)

这一步是根据上一步生成的distinctcolumn文件和维度表计算出所有维度的词典信息,词典是为了节约存储而设计的,用于将一个成员值编码成一个整数类型并且可以通过整数值获取到原始成员值,每一个cuboid的成员是一个key-value形式存储在HBase中,key是维度成员的组合,但是一般情况下维度是一些字符串之类的值(例如商品名),所以可以通过将每一个维度值转换成唯一整数而减少内存占用,在从HBase查找出对应的key之后再根据词典获取真正的成员值。

这一步是在kylin进程内的一个线程中执行的,它会创建所有维度的dictionary,如果是事实表上的维度则可以从上一步生成的文件中读取该列的distinct成员值(FileTable),否则则需要从原始的hive表中读取每一列的信息(HiveTable),根据不同的源(文件或者hive表)获取所有的列去重之后的成员列表,然后根据这个列表生成dictionary,kylin中针对不同类型的列使用不同的实现方式,对于time之类的(date、time、dtaetime和timestamp)使用DateStrDictionary,这里目前还存在着一定的问题,因为这种编码方式会首先将时间转换成‘yyyy-MM-dd’的格式,会导致timestamp之类的精确时间失去天以后的精度。针对数值型的使用NumberDictionary,其余的都使用一般的TrieDictionary(字典树)。这些dictionary会作为cube的元数据存储的kylin元数据库里面,执行query的时候进行转换。

之后还需要计算维度表的snapshotTable,每一个snapshot是和一个hive维度表对应的,生成的过程是:首先从原始的hive维度表中顺序得读取每一行每一列的值,然后使用TrieDictionary方式对这些所有的值进行编码,这样每一行每一列的之都能够得到一个编码之后的id(相同的值id也相同),然后再次读取原始表中每一行的值,将每一列的值使用编码之后的id进行替换,得到了一个只有id的新表,这样同时保存这个新表和dictionary对象(id和值得映射关系)就能够保存整个维度表了,同样,kylin也会将这个数据存储元数据库中。

针对这一步需要注意的问题:首先,这一步的两个步骤都是在kylin进程的一个线程中执行的,第一步会加载某一个维度的所有distinct成员到内存,如果某一个维度的cardinality比较大 ,可能会导致内存出现OOM,然后在创建snapshotTable的时候会限制原始表的大小不能超过配置的一个上限值,如果超过则会执行失败。但是应该强调的是这里加载全部的原始维度表更可能出现OOM。对于UHC列,请使用其他编码方法,如“fixed_length”、“整数”等等。

 

5. 保存Cuboid的统计信息(Save Cuboid Statistics)

如下图所示,SaveStatisticsStep这一步作业源码分析可知,这里决定了cube的构建算法。

【Apache Kylin 】大数据下的OLAP解决方案(作业构建)_第5张图片


6.创建HTable(Create HTable)

创建一个HTable的时候还需要考虑一下几个事情:

1、列的设置。

2、每一个列簇的压缩方式。

3、部署coprocessor。

4、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)

 

7. 计算生成BaseCuboid文件(Build Base Cuboid Data)

何谓Base cuboid呢?假设一个cube包含了四个维度:A、B、C、D,那么这四个维度成员间的所有可能的组合就是base cuboid,这就类似在查询的时候指定了select count(1)from xxx group by A,B,C,D;这个查询结果的个数就是base cuboid集合的成员数。这一步也是通过一个MR任务完成的,输入是临时表的路径和分隔符,map对于每一行首先进行split,然后获取每一个维度列的值组合作为rowKey,但是rowKey并不是简单的这些维度成员的内容组合,而是首先将这些内容从dictionary中查找出对应的id,然后组合这些id得到rowKey,这样可以大大缩短HBase的存储空间,提升查找性能。然后在查找该行中的度量列,根据cube定义中度量的函数返回对该列计算之后的值。这个MR任务还会执行combiner过程,执行逻辑和reducer相同,在reducer中的key是一个rowKey,value是相同的rowKey的measure组合的数组,reducer回分解出每一个measure的值,然后再根据定义该度量使用的聚合函数计算得到这个rowKey的结果,其实这已经类似于HBase存储的格式了。

 

8. 计算第N层cuboid文件(Build N-Dimension Cuboid Data)

这一个流程是由多个步骤的,它是根据维度组合的cuboid的总数决定的,上一层cuboid执行MR任务的输入是下一层cuboid计算的输出,由于最底层的cuboid(base)已经计算完成,所以这几步不需要依赖于任何的hive信息,它的reducer和basecuboid的reducer过程基本一样的(相同rowkey的measure执行聚合运算),mapper的过程只需要根据这一行输入的key(例如A、B、C、D中某四个成员的组合)获取可能的下一层的的组合(例如只有A、B、C和B、C、D),那么只需要将这些可能的组合提取出来作为新的key,value不变进行输出就可以了。

举个例子,假设一共四个维度A、B、C、D,他们的成员分别是(A1、A2、A3),(B1、B2)、(C1)、(D1),有一个measure(对于这列V,计算sum(V)),这里忽略dictionary编码。原始表如下:

A               B       C       D       V

A1        B1     C1     D1     2

A1        B2     C1     D1     3

A2        B1     C1     D1     5

A3        B1     C1     D1     6

A3        B2     C1     D1     8

那么base cuboid最终的输出如下

, 2)

, 3)

, 5)

, 6)

, 8)

那么它作为下面一个cuboid的输入,对于第一行输入(, 2),mapper执行完成之后会输出

, 2)

, 2)

, 2)

, 2)

这四项,同样对于其他的内一行也会输出四行,最终他们经过reducer的聚合运算,得到如下的结果:

, 2)

, 2)

, 2 + 3)

,2 + 5 +6)

...

这样一次将下一层的结果作为输入计算上一层的cuboid成员,直到最顶层的cuboid,这一个层cuboid只包含一个成员,不按照任何维度进行group by。

上面的这些步骤用于生成cuboid,假设有N个维度(对于特殊类型的),那么就需要有N +1层cuboid,每一层cuboid可能是由多个维度的组合,但是它包含的维度个数相同。

 

9. 基于内存构建Cube

这个步骤使用一种新的算法来构建多维数据集:“by-split Cubing(也称为“in-mem Cubing)”。

【Apache Kylin 】大数据下的OLAP解决方案(作业构建)_第6张图片

这里的配置文件是/soft/kylin/kylin_job_conf_inmem.xml,使用的是Fast Cubing通过在map端做聚合来减轻网络压力。

 

10. 将Cuboid数据转换成HFile(Convert Cuboid Data to HFile)

kylin采取了首先将cuboid文件转换成HTable格式的HFile文件,这个过程通过一个MR任务完成。

Kylin通过Cube统计信息来计算HBase 的region数量,默认情况是一个region 为5GB。Region数量越多,更多的reducers就会被使用。如果观察到reducers数量少且性能差,可以在通过调整conf/kylin.properties中的参数来减小每个region的大小。



11. 将HFile导入到HBase表中

这一步将HFile文件load到HTable中,因为load操作会将原始的文件删除(相当于remove),在操作之前首先将所有列组的HFile的权限都设置为777,然后再启动LoadIncrementalHFiles任务执行load操作,它的输入为文件的路径和HTable名,这一步完全依赖于HBase的工具。这一步完成之后,数据已经存储到HBase中了,key的格式由cuboid编号+每一个成员在字典树的id组成,value可能保存在多个列组里,包含在原始数据中按照这几个成员进行GROUP BY计算出的度量的值。

 

收尾工作

执行完上一步就已经完成了从输入到输出的计算过程,接下来要做的就是一些kylin内部的工作,分别是更新元数据,更新cube状态,垃圾数据回收。

 

1. 更新状态

这一步主要是更新cube的状态,其中需要更新的包括cube是否可用、以及本次构建的数据统计,包括构建完成的时间,输入的record数目,输入数据的大小,保存到HBase中数据的大小等,并将这些信息持久到元数据库中。

2. 垃圾文件回收

这一步是否成功对正确性不会有任何影响,因为经过上一步之后这个segment就可以在这个cube中被查找到了,但是在整个执行过程中产生了很多的垃圾文件,其中包括:

1、临时的hive表,

2、因为hive表是一个外部表,存储该表的文件也需要额外删除,

3、fact distinct 这一步将数据写入到HDFS上为建立词典做准备,这时候也可以删除了,

4、rowKey统计的时候会生成一个文件,此时可以删除。

5、生成HFile时文件存储的路径和hbase真正存储的路径不同,虽然load是一个remove操作,但是上层的目录还是存在的,也需要删除。

这一步kylin做的比较简单,并没有完全删除所有的临时文件,其实在整个计算过程中,真正还需要保留的数据只有多个cuboid文件(需要增量build的cube),这个因为在不同segment进行merge的时候是基于cuboid文件的,而不是根据HTable的。

在Kylin-1.x版本中,整个cube的一个build的过程大概就是这样,这样的一个build只不过是生成一个segment,而当一个cube中存在多个segment时可能需要将它们进行merge,merge的过程和build的流程大致是相同的,不过它不需要从头开始,只需要对字典进行merge,然后在对cuboid文件进行merge,最后生成一个新的HTable。

但是在Kylin-2.x版本中,整个架构发生了很大的变化,build的引擎也分成了多套,分别是原始的MR引擎,基于Fast Cubing的MR引擎和Spark引擎,这使得build进行的更迅速,大大降低等待时间,后面会持续的再对新的引擎进行分析。



【Apache Kylin 】大数据下的OLAP解决方案(作业构建)_第7张图片



【Apache Kylin 】大数据下的OLAP解决方案(作业构建)_第8张图片

微信扫一扫
关注该公众号

你可能感兴趣的:(大数据技术,hadoop,数据,apache,kylin,hive,大数据)