Kylin#Apache Kylin Cube增量构建(四)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kylin#Apache Kylin Cube增量构建(四)相关的知识,希望对你有一定的参考价值。

参考技术A

全量构建可以看作增量构建的一种特例:在全量构建中,Cube中只存在唯一的一个Segment,该Segment没有分割时间的概念,因此也就没有起始时间和结束时间。全量构建和增量构建各有适用的场景,用户可以根据自己的业务场景灵活切换

(注意更新俩字的含义,kylin是不保存历史状态的,如果是增量更新就是更新指定的时间区间)
整体来说,增量构建的Cube上的查询会比全量构建Cube上的查询做更多的运行时聚合,而这些运行时聚合都发生在单点的查询引擎上,因此,通常来说,增量构建的Cube上的查询会比全量构建的Cube上的查询慢一些。

对于小数据量的Cube或经常需要全表更新的Cube,使用全量构建可以以少量的重复计算降低生产环境中的维护复杂度,减少运维精力。而对于大数据量的Cube,如一个包含两年历史数据的Cube,如果使用全量构建每天更新数据,那么每天为了新数据而重复计算过去两年的数据会严重增加查询成本,在这种情况下需要考虑使用增量构建。
(这里对每日新增的描述,是每日新增,但是对于拉链表来说也是每日新增,那么应该如何选择??)

并非所有的Cube都适合进行增量构建,Cube的定义必须包含一个时间维度,用来分割不同的Segment,我们将这样的维度称为 分割时间列(Partition Date Column) 。尽管由于历史原因该命名中存在“date”字样,但是分割时间列可以是Hive中的Date类型、Timestamp类型还可以是String类型。无论是哪种类型,Kylin都要求用户显式地指定分割时间列的数据格式。

满足了设计增量Cube的条件之后,在进行增量构建时将增量部分的起始时间和结束时间作为增量构建请求的一部分提交给Kylin的任务引擎,任务引擎会根据起始时间和结束时间从Hive中抽取相应时间的数据,并对这部分数据做预计算处理,然后将预计算的结果封装成新的Segment,保存相应的信息到元数据和存储引擎中。一般来说,增量部分的起始时间等于Cube中最后一个Segment的结束时间。
(对于动态分区表来说,我们并不遵守这规范)

在Web GUI上触发Cube的增量构建与触发全量构建的方式基本相同。在Web GUI的Model页面中,选中想要增量构建的Cube,点击“Action”→“Build”命令

http://kylin.apache.org/docs/howto/howto_build_cube_with_restapi.html
http://kylin.apache.org/docs/howto/howto_use_restapi.html

增量构建的Cube每天都可能还有新的增量,这样的Cube日积月累最终可能包含上百个Segment,导致运行时的查询引擎需要聚合多个Segment的结果才能返回正确的查询结果,最终会使查询性能受到严重的影响。从存储引擎的角度来说,大量的Segment会带来大量的文件,这些文件会充斥系统为Kylin所提供的命名空间,给存储空间的多个模块带来巨大的压力,如Zookeeper、HDFS Namenode等。在这种情况下,我们有必要采取措施控制Cube中Segment的数量,如可以自动/手动合并Segment、清理老旧无用的Segment。

Kylin提供了一种简单的机制来控制Cube中Segment的数量——合并Segment。在Web GUI中选择需要进行Segment合并的Cube,点击“Action→Merge”命令,然后在对话框中选择需要合并的Segment,可以同时合并多个Segment,但是这些Segment必须是连续的。

在Cube Designer的“Refresh Settings”的页面中有“Auto Merge Thresholds”“Volatile Range”和“RetentionThreshold”三个设置项可以用来帮助管理Segment碎片。虽然这三项设置还不能完美地解决所有业务场景的需要,但是灵活地搭配使用这三项设置可以大大减少对Segment进行管理的工作量

“Auto Merge Thresholds”允许用户设置几个层级的时间阈值,层级越靠后,时间阈值越大。举例来说,用户可以为一个Cube指定层级(7天、28天),每当Cube中有新的Segment状态变为“READY”的时候,会触发一次系统自动合并的尝试。系统首先会尝试最大一级的时间阈值,结合上面例子中设置的层级(7天、28天),系统会查看是否能把连续的若干个Segment合并成一个超过28天的较大Segment,在挑选连续Segment的过程中,如果有的Segment本身的时间长度已经超过28天,那么系统会跳过该Segment,从它之后的Segment中挑选连续的累计超过28天的Segment。当没有满足条件的连续Segment能够累计超过28天时,系统会使用下一个层级的时间阈值重复此寻找的过程。每当有满足条件的连续Segment被找到,系统就会触发一次自动合并Segment的构建任务,在构建任务完成后,新的Segment被设置为“READY”状态,自动合并的整个尝试过程则需要重新执行。

从碎片管理的角度来说,自动合并是将多个Segment合并为一个Segment,以达到清理碎片的目的。保留Segment从另一个角度帮助实现了碎片管理,那就是及时清理不再使用的Segment。在许多业务场景中只会对过去一段时间内的数据进行查询,如对于某个只显示过去1年数据的报表,支持它的Cube,事实上,只需要保留过去一年的Segment。由于数据往往在Hive中已经备份,因此无须再在Kylin中备份超过一年的历史数据。

在这种情况下,我们可以将“Retention Threshold”设置为“365”。每当有新的Segment状态变为“READY”的时候,系统会检查每一个Segment:如果它的结束时间距离最晚的一个Segment的结束时间已经大于“Retention Threshold”,那么这个Segment将被视为无须保留,且系统会自动从Cube中删除这个Segment。

如果启用了“Auto Merge Thresholds”,使用“RetentionThreshold”的时候需要注意,不能将“Auto Merge Thresholds”的最大层级设置得太高。假设我们将“Auto Merge Thresholds”的最大一级设置为“1000”天,而“Retention Threshold”为“365”天,那么受自动合并的影响,新加入的Segment会不断地被自动合并到一个越来越大的Segment之中,更糟糕的是,这会不断地更新这个大Segment的结束时间,最终导致这个大Segment永远不会被释放。因此, 推荐自动合并的最大一级的时间不要超过1年
(这个自动合并是怎么做到的???)

在实际应用场景中,我们常常遇到这样的问题:由于ETL过程的延迟,业务需要每天刷新过去N天的Cube数据。举例来说,客户有一个报表每天都需要更新,但是每天源数据的更新不仅包含当天的新数据,还包括了过去7天数据的补充。一种比较简单的方法是,每天在Cube中增量构建一个长度为一天的Segment,这样过去7天的数据会以7个Segment的形式存在于Cube之中。Cube的管理员除了每天创建一个新的Segment代表当天的新数据(BUILD操作)以外,还需要对代表过去7天的7个Segment进行刷新(REFRESH操作,Web GUI上的操作及REST API参数与BUILD类似,这里不再详细展开)。这样的方法固然有一定的作用,但是每天为每个Cube触发的构建数量太多,容易造成Kylin的任务队列堆积大量未能完成的任务

上述方案的另一个弊端是每天一个Segment会使Cube中迅速累积大量的Segment,需要Cube管理员手动地对超过7天的Segment进行合并,期间还必须避免将7天的Segment一起合并了。举例来说,假设现在有100个Segment,每个Segment代表过去的一天的数据,Segment按照起始时间排序。在合并时,我们只能挑选前面93个Segment进行合并,如果不小心把第94个Segment也一起合并了,当我们试图刷新过去7天(94-100)的Segment时,会发现为了刷新第94天的数据,不得不将1~93的数据一并重新进行计算,因为此时第94天的数据已经和1~93这93天的数据糅合在一个Segment之中了,这是一种极大的资源浪费。更糟糕的是,即使使用之前介绍的自动合并的功能,类似的问题也仍然存在,但在Kylin v2.3.0及之后的版本中,增加了一种机制能够阻止自动合并试图合并近期N天的Segment,那就是“Volatile Range”。

“Volatile Range”的设置非常简单,在Cube Designer的“Refresh Setting”中,在“Volatile Range”后面的文本框中输入最近的不想要合并的Segment的天数。举个例子,如果设置“VolatileRange”为“2”,“Auto Merge Thresholds”为“7”,现有01-01到01-08的数据即8个Segment,自动合并不会被开启,直到01-09的数据进来,才会触发自动合并,将01-01到01-07的数据合并为一个Segment。
(不是很明白,假设我有一个cube我每天都更新最近60天的数据,那么我应该如何设置合并)

Apache Kylin介绍

Kylin是ebay开发的一套OLAP系统,主要是对hive中的数据进行预计算,利用hadoop的mapreduce框架实现。通过设计维度、度量,我们可以构建星型模型或雪花模型,生成数据多维立方体Cube,基于Cube可以做钻取、切片、旋转等多维分析操作。

Apache Kylin核心思想

简单来说,Kylin的核心思想是预计算,用空间换时间,即对多维分析可能用到的度量进行预计算,将计算好的结果保存成Cube,供查询时直接访问。把高复杂度的聚合运算、多表连接等操作转换成对预计算结果的查询,这决定了Kylin能够拥有很好的快速查询和高并发能力。

Apache Kylin架构

kylin由以下几部分组成:

· REST Server:提供一些restful接口,例如创建cube、构建cube、刷新cube、合并cube等cube的操作,project、table、cube等元数据管理、用户访问权限、系统配置动态修改等。除此之外还可以通过该接口实现SQL的查询,这些接口一方面可以通过第三方程序的调用,另一方也被kylin的web界面使用。

· jdbc/odbc接口:kylin提供了jdbc的驱动,驱动的classname为
org.apache.kylin.jdbc.Driver,使用的url的前缀jdbc:kylin:,使用jdbc接口的查询走的流程和使用RESTFul接口查询走的内部流程是相同的。这类接口也使得kylin很好的兼容tebleau甚至mondrian。

· Query引擎:kylin使用一个开源的Calcite框架实现SQL的解析,相当于SQL引擎层。

· Routing:该模块负责将解析SQL生成的执行计划转换成cube缓存的查询,cube是通过预计算缓存在hbase中,这部分查询是可以再秒级甚至毫秒级完成,而还有一些操作使用过查询原始数据(存储在hadoop上通过hive上查询),这部分查询的延迟比较高。

· Metadata:kylin中有大量的元数据信息,包括cube的定义,星状模型的定义、job的信息、job的输出信息、维度的directory信息等等,元数据和cube都存储在hbase中,存储的格式是json字符串,除此之外,还可以选择将元数据存储在本地文件系统。

· Cube构建引擎:这个模块是所有模块的基础,它负责预计算创建cube,创建的过程是通过hive读取原始数据然后通过一些mapreduce计算生成Htable然后load到hbase中。

Apache Kylin关键流程

在kylin中,最关键的两个流程是cube的预计算过程和SQL查询转换成cube的过程,cube的构造可以分成cube的构建和cube的合并,首先需要创建一个cube的定义,包括设置cube名、cube的星状模型结构,dimension信息、measure信息、设置where条件、根据hive中事实表定义的partition设置增量cube,设置rowkey等信息,这些设置在mondrian中也是可以看到的,一个cube包含一些dimension和measure,where条件决定了源数据的大小,在mondrian中可以通过view实现。另外,kylin还提供了增量计算的功能,虽然达不到实时计算的需求,但是基本上可以满足数据分析的需求。

Apache Kylin cube模型

首先我们会设置cube名和notification列表,前者需要保证是全局唯一的,后者是一些Email用于通知cube的一些事件的发生。接着我们需要定义一个星状模型,和一般的数据仓库模型一样,需要指定一个事实表和任意多个维度表,如果存在维度表还需要指定事实表和维度表的关联关系,也就是join方式。接下来是定义dimension,在定义dimension的时候可以选择dimension的类型,分为Normal、Hierachy以及Derived,这个后面再进行介绍,dimension的定义决定着cube的大小,也需要用户对原始的表非常了解。

接下来是定义measure,kylin会为每一个cube创建一个聚合函数为count(1)的度量,它不需要关联任何列,用户自定义的度量可以选择SUM、COUNT、DISTINCT COUNT、MIN、MAX,而每一个度量定义时还可以选择这些聚合函数的参数,可以选择常量或者事实表的某一列,一般情况下我们当然选择某一列。

定义完measure之后需要设置where条件,这一步是对原始数据进行过滤,例如我们设定销售额小于XXX的地区不在于本次分析范围之内,那么就可以在where条件里设定location in xxx(子查询),那么生成的cube会过滤掉这些location,这一步其实相当于对无效数据的清洗,但是在kylin中这个是会固化的,不容易改变,例如我今天希望将销售额小于XX的地区清洗掉,明天可能有想将年消费小于xxx的用户去除,这就需要每次都创建一个相同的cube,区别仅仅在于where条件,他们之间会有很多的重复缓存数据,也会导致存储空间的浪费,但这也是MOLAP系统不可避免的,因此当过滤条件变化比较多的时候,更好的方案则是创建一个完整的cube(不设置任何where条件),使用子查询的方式过滤掉不希望要的一些维度成员。

接下来的一步是设置增量cube信息,首先需要选择事实表中的某一个时间类型的分区列(貌似只能是按照天进行分区),然后再指定本次构建的cube的时间范围(起始时间点和结束时间点),这一步的结果会作为原始数据查询的where条件,保证本次构建的cube只包含这个闭区间时间内的数据,如果事实表没有时间类型的分区别或者没有选择任何分区则表示数据不会动态更新,也就不可以增量的创建cube了。

最后一步设置rowkey,这一步的建议是看看就可以了,不要进行修改,除非对kylin内部实现有比较深的理解才能知道怎么去修改。当然这里有一个可以修改的是mandatory dimension,如果一个维度需要在每次查询的时候都出现,那么可以设置这个dimension为mandatory,可以省去很多存储空间,另外还可以对所有维度进行划分group,不会组合查询的dimension可以划分在不同的group中,这样也会降低存储空间。

Dimension介绍

在一个多维数据集合中,维度的个数决定着维度之间可能的组合数,而每一个维度中成员集合的大小决定着每一个可能的组合的个数,例如有三个普通的维度A、B、C,他们的不同成员数分别为10/100/1000,那么一个维度的组合有2的3次方个,分别是空、A、B、C、AB、BC、AC、ABC,每一个成员我们称为cuboid(维度的组合),而这些集合的成员组合个数分别为1、10、100、1000、10*100、100*1000、10*1000和10*100*1000。我们称每一个dimension中不同成员个数为cardinatily,我们要尽量避免存储cardinatily比较高的维度的组合,在上面的例子中我们可以不缓存BC和C这两个cuboid,可以通过计算的方式通过ABC中成员的值计算出BC或者C中某个成员组合的值,这相当于是时间和空间的一个权衡吧。

在kylin中存在的四种维度是为了减少cuboid的个数,而不是每一个维度是否缓存的,当前kylin是对所有的cuboid中的所有组合都进行计算和存储的,对于普通的dimension,从上面的例子中可以看出N个维度的cuboid个数为2的N次方,而kylin中设置了一些维度可以减少cuboid个数,当然,这需要使用者对自己需要的维度十分了解,知道自己可能根据什么进行group by。

好了,我们先来看一下kylin中的三种特殊的dimension以及它们的作用

1、Mandatory维度

这种维度意味着每次查询的group by中都会携带的,将某一个dimension设置为mandatory可以将cuboid的个数减少一半,如下图:

这是因为我们确定每一次group by都会携带A,那么就可以省去所有不包含A这个维度的cuboid了。

2、hierarchy维度

这种维度是最常见的,尤其是在mondrian中,我们对于多维数据的操作经常会有上卷下钻之类的操作,这也就需要要求维度之间有层级关系,例如国家、省、城市,年、季度、月等。有层级关系的维度也可以大大减少cuboid的个数。如下图:

这里仅仅局限于A/B/C是一个层级,例如A是年份,B是季度、C是月份,那么查询的时候可能的组合只有年、xx年的季度、xx年xx季度的xx月,这就意味着我们不能再单独的对季度和月份进行聚合了,例如我们查询的时候不能使用group by month,而必须使用group by year,quart,month。如果需要单独的对month进行聚合,那么还需要再使用month列定义一个单独的普通维度。

3、derived维度

这类维度的意思是可推导的维度,需要该维度对应的一个或者多个列可以和维度表的主键是一对一的,这种维度可以大大减少cuboid个数,如下图:

例如timeid是时间这个维度表的主键,也就是事实表的外检,时间只精确到天,那么year、month、day三列可以唯一对应着一个time_id,而time_id是事实表的外键,那么我们可以指定year、month、day为一个derived维度,实际存储的时候可以只根据timeid的取值决定维度的组合,但这就要求我们在查询的时候使用的group by必须指定derived维度集合中的所有列。

最后,简单介绍一下如何计算cuboid个数的,假设我们存在两个普通维度brand、product,存在一个hierarchy,包含四个维度分别为year、quart、month和day,一个derived维度,指定location信息,包含country、province和city列,这相当于一共9个维度,但是根据上面的分析我们并不需要512分cuboid。

第0层的cuboid(不包含任何维度,不包含group by),cuboid的个数为1,这个cuboid的成员个数也为1;

第1层的cuboid包含一个维度,一共有4种组合(分别为brand、product、year、location,因为quart是hierarchy的第二个层级,不能单独group by,而location的三列可以视为一个整体),成员个数则有每一个维度的cardinality;

第2层的cuboid有7种,分别为brand、product、brand、year、brand、location、product、year、product、location、year、location和year、quart;

第3层的cuboid有8种,分别为brand、product、year、brand、product、location、product、year、location、brand、year、location、brand、year、quart、product、year、quart、location、year、quart、year、quart、month;

第4层的cuboid有8种,分别为brand、product、year、location、brand、product、year、quart、brand、location、year、quart、product、location、year、quart、brand、year、quart、month、product、year、quart、month、location、year、quart、month、year、quart、month、day

第5层的cuboid有7种,分别为brand、product、year、quart、location、brand、product、year、quart、momth、brand、location、year、quart、month、product、location、year、quart、month、brand、year、quart、month、day、product、year、quart、month、day、location、year、quart、month、day

第6层的cuboid有5种,分别为brand、product、year、quart、month、location、brand、product、year、quart、momth、day、brand、location、year、quart、month、day、product、location、year、quart、month、day

第7层的cuboid有1中,为brand、product、year、quart、month、day、location
所以一共40个cuboid(kylin计算的是39个,应该没有把第0层的计算在内)。

增量cube

由于kylin的核心在于预计算缓存数据,那么对于实时的数据查询的支持就不如mondrian好了,但是一般情况下我们数据分析并没有完全实时的要求,数据延迟几个小时甚至一天是可以接受的,kylin提供了增量cube的接口,kylin的实现是一个cube(这里是指逻辑上的cube)中可以包含多个segment,每一个segment对应着一个物理cube,在实际存储上对应着一个hbase的一个表,用户定义根据某一个字段进行增量(目前仅支持时间,并且这个字段必须是hive的一个分区字段),在使用的时候首先需要定义好cube的定义,可以指定一个时间的partition字段作为增量cube的依赖字段,其实这个选择是作为原始数据选择的条件,例如选择起始时间A到B的数据那么创建的cube则会只包含这个时间段的数据聚合值,创建完一个cube之后可以再次基于以前的cube进行build,每次build会生成一个新的segment,只不过原始数据不一样了(根据每次build指定的时间区间),每次查询的时候会查询所有的segment聚合之后的值进行返回,有点类似于tablet的存储方式,但是当segment存在过多的时候查询效率就会下降,因此需要在存在多个segment的时候将它们进行合并,合并的时候其实是指定了一个时间区间,内部会选择这个时间区间内的所有segment进行合并,合并完成之后使用新的segment替换被合并的多个segment,合并的执行时非常迅速的,数据不需要再从HDFS中获取,直接将两个hbase表中相同key的数据进行聚合就可以了。但是有一点需要注意的是当合并完成之后,被合并的几个segment所对应的hbase表并没有被删除。实际的使用过程中对于增量的cube可以写个定时任务每天凌晨进行build,当达到一个数目之后进行merge(其实每次build完成之后都进行merge也应该是可以的)。

cube的词典树

kylin的cube数据是作为key-value结构存储在hbase中的,key是每一个维度成员的组合值,不同的cuboid下面的key的结构是不一样的,例如cuboid=brand,product,year下面的一个key可能是brand=’Nike’,product=’shoe’,year=2015,那么这个key就可以写成Nike:shoe:2015,但是如果使用这种方式的话会出现很多重复,所以一般情况下我们会把一个维度下的所有成员取出来,然后保存在一个数组里面,使用数组的下标组合成为一个key,这样可以大大节省key的存储空间,kylin也使用了相同的方法,只不过使用了字典树(Trie树),每一个维度的字典树作为cube的元数据以二进制的方式存储在hbase中,内存中也会一直保持一份。

以上是关于Kylin#Apache Kylin Cube增量构建(四)的主要内容,如果未能解决你的问题,请参考以下文章

kylin通过API增量build

Apache Kylin 深入Cube和查询优化

Apache Kylin 深入Cube和查询优化

大数据分析平台Apache Kylin的部署(Cube构建使用)

Apache Kylin的Cube是如何构建的

kylin cube 构建过程