Kylin对大数据量的多维分析

一、Kylin简介
Apache Kylin(http://kylin.apache.org/cn/)是一个开源的分布式分析引擎,提供Hadoop之上的SQL查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由eBay 开发并贡献至开源社区。它能在亚秒内查询巨大的Hive表。

1、kylin的总体架构
Kylin 作为一个Olap引擎完成了从数据源抓取数据,ETL到自己的存储引擎,提供REST服务等一系列工作,其架构如图所示:

Kylin 的生态圈包括:
Kylin Core: Kylin 引擎的框架,查询、任务、以及存储引擎都集中于此,除此之外还包括一个REST 服务器来响应各种客户端请求。
扩展插件: 各种提供额外特性的插件,如安全认证、SSO等
完整性组件: Job管理器,ETL、监控以及报警
交互界面: 基于Kylin Core之上的用户交互界面
驱动: 提供了JDBC以及ODBC的连接方式

2、Kylin典型的应用场景
场景如下:
数据存于HDFS,利用Hive将HDFS数据以关系数据方式存取,数据量巨大,在500G以上
每天有数G甚至数十G的数据增量导入
有10个左右的分析维度

Kylin的核心思想是利用空间换时间,由于查询方面制定了多种灵活的策略,进一步提高空间的利用率,使得这样的平衡策略在应用中是值得采用的。

二、Cube说明
Cube是一种典型的多维数据分析技术,一个Cube可以有多个事实表,多个维表构成。

1、维度组合Cuboid
Cube是所有的维度组合,任一维度的组合称为cuboid。
理论上来说,一个N维的Cube,便有2的N次方种维度组合(Cuboid),一个Cube包含time,item, location, supplier四个维度,那么组合便有16种:

Kylin中Cube的Build过程,其实是将所有的维度组合事先计算,存储于HBase中,以空间换时间,HTable对应的RowKey,就是各种维度组合,指标存在Column中。
这样,将不同维度组合的查询SQL,转换成基于RowKey的范围扫描,然后对指标进行汇总计算。

2、Kylin构建Cube的过程
如下图所示:

STEP1. 根据Cube定义的事实表和维度,在Hive中生成一张中间表;
STEP2. 使用MapReduce,从事实表中抽取维度的Distinct值,并以字典树的方式压缩编码,同时也对所有维度表进行压缩编码,生成维度字典;
STEP3. 计算和统计所有的维度组合,并保存,其中,每一种维度组合,称为一个Cuboid;
STEP4. 创建HBase Table;
STEP5. 利用step1中间表的数据,使用MapReduce,生成每一种维度组合的数据;
STEP6. 将Cuboid数据转换成HFile,并导入到HBase Table中:
STEP7. 更新Cube信息,清理中间表:
整个Build过程结束。

3、增量Cubing
Kylin与传统的OLAP一样,无法应对数据Update的情况(更新数据会导致Cube的失效,需要重建整个Cube)。面对每天甚至每两个小时这样固定周期的增量数据,Kylin使用了一种增量Cubing技术来进行快速响应。Kylin的Cube可以根据时间段划分成多个Segment。在Cube第一次Build完成之后会有一个Segment,在每次增量Build后会产生一个新的Segment。增量Cubing依赖已有的Cube Segments和增量的原始数据。增量Cubing的步骤和新建 Cube的步骤类似,Segment之间以时间段进行区分。

增量Cubing所需要面对的原始数据量更小,因此增量Cubing的速度是非常快的。然而随着Cube Segments的数目增加,一定程度上会影响到查询的进行,所以在Segments数目到一定数量后可能需要进行Cube Segments的合并操作,实际上merge cube是合成了一个新的大的Cube Segment来替代,Merge操作是一个异步的在线操作,不会对前端的查询业务产生影响。

合并操作步骤如下:
遍历指定的Cube Segment
合并维度字典目录和维度表快照
利用MapReduce合并他们的 N-Dimension cuboid
将cuboid转换成HFile,生成新的HTable,替代原有的多个HTable

三、Kylin使用步骤

1、主要步骤
Kylin中多维分析主要包括以下步骤:
Hive中分析好事实表
Kylin中建立项目(project)
Kylin中建立数据源,从hive同步表
Kylin中建立数据模型model
Kylin中基于model建立Cube
Build Cube
查询Cube

按照上面的过程,最终将Hive中的事实表按照相应的结构,压缩并存储在HBase中。
官网提供了中文文档,说明了如何在Kylin中建立Cube,非常详细:
http://kylin.apache.org/cn/docs15/tutorial/create_cube.html

2、实例-搜索多维统计

搜索业务有以下汇总维度:
时间段、app、操作系统、用户身份、搜索位置、关键词、结果类型、结果类型细分类型
、结果位置,怀孕身份下还需要区分孕周。有10个维度。

任意维度组合下要查询一些指标:
关键词曝光量、曝光人数,搜索量、搜索人数,结果点击量、结果点击人数等等……

操作步骤如下:
准备好搜索行为埋点流水表,从hive同步到kylin。

建立model,基于model创建cube。

设置上述维度

设置上述指标

设置保留数据阀值

高级设置:设置cube组合优化,rowkey顺序

Cube创建完,build。

build结束后cube会处于ready状态。

此时即可查询

任意时间段任意纬度组合查询

四、Count Distinct计算
从1.5.3开始,Apache Kylin提供了两种Count Distinct计算方式,一种是近似的,一种是精确的,精确的Count Distinct指标在Build时候会消耗更多的资源(内存和存储),Build的过程也比较慢。

1、近似Count Distinct
Apache Kylin使用HyperLogLog算法实现了近似Count Distinct,提供了错误率从9.75%到1.22%几种精度供选择。算法计算后的Count Distinct指标,理论上,结果最大只有64KB,最低的错误率是1.22%,这种实现方式用在需要快速计算、节省存储空间,并且能接受错误率的Count Distinct指标计算。
具体的算法可见Paper: http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf

2、精准Count Distinct
从1.5.3版本开始,Kylin中实现了基于bitmap的精确Count Distinct计算方式。当数据类型为tiny int(byte)、small int(short)以及int,会直接将数据值映射到bitmap中;当数据类型为long,string或者其他,则需要将数据值以字符串形式编码成dict(字典),再将字典ID映射到bitmap。
指标计算后的结果,并不是计数后的值,而是包含了序列化值的bitmap,这样才能确保在任意维度上的Count Distinct结果是正确的。这种实现方式提供了精确的无错误的Count Distinct结果,但是需要更多的存储资源,如果数据中的不重复值超过百万,结果所占的存储应该会达到几百MB。

3、全局字典(Global Dictionary)
默认情况下,Kylin在每个Segment中,将数据值编码到一个字典中,同一个数据值,在不同Segment中编码后的ID值也是不同的,因此在这跨两个Segment中进行Count Distinct计算时候,结果是不正确的。

在1.5.3版本中,Kylin引进了”Global Dictionary”,用来确保同一个数据值编码后的ID值始终是相同的,与此同时,字典的容量也进行了扩充,一个字典的最大容量达到了20亿,之前默认的字典最大容量为500万。全局字典可以代替之前的默认字典。
当前版本1.5.3的UI中,没有提供定义全局字典的地方,需要手动修改Cube的json:
1. "dictionaries": [
2. {
3. "column": " USER_ID ",
4. "builder": "org.apache.kylin.dict.GlobalDictionaryBuilder"
5. }
6. ]

详见文档 http://kylin.apache.org/blog/2016/08/01/count-distinct-in-kylin/

Example:
1. | DT | USER_ID | FLAG1 | FLAG2 | USER_ID_FLAG1 | USER_ID_FLAG2 |
2. | :———-: | :——: | :—: | :—: | :————-: | :————-: |
3. | 2016-06-08 | AAA | 1 | 1 | AAA | AAA |
4. | 2016-06-08 | BBB | 1 | 1 | BBB | BBB |
5. | 2016-06-08 | CCC | 0 | 1 | NULL | CCC |
6. | 2016-06-09 | AAA | 0 | 1 | NULL | AAA |
7. | 2016-06-09 | CCC | 1 | 0 | CCC | NULL |
8. | 2016-06-10 | BBB | 0 | 1 | NULL | BBB |

表中有几个基础列:DT、USER_ID、FLAG1、FLAG2,假设Cube按天Build,有3个Segments。如果不使用全局字典,在一个Segment(一天数据)中计算Count Distinct是准确的,但是如果在两个Segment中计算Count Distinct,结果是错误的
1. select count(distinct user_id_flag1) from test where dt in (‘2016-06-08’, ‘2016-06-09’)
这个查询结果是将会是2,而不是3, 因为在Segment 2016-06-08的字典中,AAA的编码ID为1,BBB的编码ID为2,而在Segment 2016-06-09中,CCC的编码ID也为1,因此,编码ID去重后,结果是2.
如果使用下面的方式配置了全局字典,那么这三个值的编码ID为: AAA=>1, BBB=>2, CCC=>3,去重后的结果是正确的3。
事实上,USER_ID_FLAG1和USER_ID_FLAG2都是USER_ID的子集,这时候,只需要使用USER_ID来创建全局字典,那么另外两个,其实可以复用的。

1. "dictionaries": [
2. { "column": "USER_ID", "builder": "org.apache.kylin.dict.GlobalDictionaryBuilder" },
3. { "column": "USER_ID_FLAG1", "reuse": "USER_ID", "builder": "org.apache.kylin.dict.GlobalDictionaryBuilder" },
4. { "column": "USER_ID_FLAG2", "reuse": "USER_ID", "builder": "org.apache.kylin.dict.GlobalDictionaryBuilder" }
5. ]

“reuse”是用来优化字典的,当多个字段的值是同一个数据集的时候,指定复用同一个字典即可,不需要再建立字典。全局字典不能用在维度的编码中,如果一个字段即是维度,又是Count Distinct指标,那么就需要为维度指定其他的编码方式。

全局字典是比较大的,在Build时候,”Build Base Cuboid Data”这一步会消耗较长时间。
如果字典大小超过Mapper的内存大小时候,字典需要消耗大量时间在缓存加载和回收上,解决该问题的办法是修改Cube的参数,适当增大Mapper使用的内存:
kylin.job.mr.config.override.mapred.map.child.java.opts=-Xmx8g
kylin.job.mr.config.override.mapreduce.map.memory.mb=8500

4、总结
选择哪种Count Distinct计算方式呢?
1. 如果能接受1.22%以内的误差,近似计算肯定是最好的方式;
2. 如果业务需要精确去重计数,那么肯定得选择精确Count Distinct;
3. 如果不需要跨Segment(天)的去重,或者字段值是tinyint/smallint/int, 或者字段去重后的值小于500万,那么就是用默认字典;否则,就需要配置全局字典,同时,如果可以,则是用“reuse”来进行优化。

五、注意事项

1、事实表的准备
Kylin只支持简单的聚合函数,sum、count等、尽量在hive做好预处理。
对于维表比较大的情况,或者查询Select部分存在复杂的逻辑判断,存在Apache Kylin不支持的函数或语句时,可以将事实表和维表的关联处理创建为Hive视图,之后根据视图创建Cube模型。

2、Cube高级设置
Cube的计算量主要跟维度个数、维度基数有关。尽量减少不必要的维度组合计算。

(1)Mandatory Dimension
每次查询必然带有的条件建议在字典设置步骤将其设置为Mandatory。这样会最终 Build出来Cube的大小会减少一半。

(2)Hierachy Dimension
一系列具有层次关系的Dimension组成一个Hierachy, 比如年、月、日组成了一个Hierachy, 在Cube中,如果不设置Hierarchy, 会有 年、月、日、年月、年日、月日 6个cuboid, 但是设置了Hierarchy之后Cuboid增加了一个约束,希望低Level的Dimension一定要伴随高Level的Dimension 一起出现。

(3)Joint Dimensions
这是一个将维度进行分组,以求达到降低维度组合数目的手段。不同分组的维度之间不会进行组合计算。将经常出现在同一SQL中的不同维度放置在一个维度组中,将从不出现在一个SQL查询中的不同维度设置在不同的维度组中。
Group的优化措施与查询SQL紧密依赖,可以说是为了查询的定制优化。 维度组合从2的(k+m+n)次幂降低到 2的k次幂加2的m次幂加2的n次幂。如果查询的维度是跨Group的,那么Kylin需要以较大的代价从N-Cuboid中聚合得到所需要的查询结果

(4)Cube定义中RowKey顺序:按查询频率从高到低,从前往后排。Mandatory维度,Where过滤条件中出现频率较多的维度,高基数维度,低基数维度。

3、Segment的合并和清理
大的事实表采用天分区增量构建,为了不影响查询性能,可以定期做合并(Merge),周期可以根据实际情况确定。

这里需要注意的是:kylin可以设置数据自动清理策略,例如只保留30天的数据。但是实际上它不是按天算的,是按Segments个数。如果是按天构建,不合并数据的情况下每天都会有一个Segments,这时设置保留30天确实是保留30天;设置了合并数据的情况下,假设每周合并一次,那每周都会有7个Segments(天)合并成一个大的Segments(周),那保留30个Segments数据就远不止一个月的。要根据合并策略设置合适的清理策略。

而且在kylin里面清理的数据只是去除了关联,数据实际上还在hbase上,需要手动清理
Steps:
1. Check which resources can be cleanup, this will not remove anything:
export KYLIN_HOME=/path/to/kylin_home
${KYLIN_HOME}/bin/kylin.sh org.apache.kylin.storage.hbase.util.StorageCleanupJob --delete false

Here please replace (version) with the specific Kylin jar version in your installation;
2. You can pickup 1 or 2 resources to check whether they’re no longer be referred; Then add the “–delete true” option to start the cleanup:
${KYLIN_HOME}/bin/kylin.sh org.apache.kylin.storage.hbase.util.StorageCleanupJob --delete true
On finish, the intermediate HDFS location and HTables should be dropped;
详查http://kylin.apache.org/docs15/howto/howto_cleanup_storage.html

4、部署层面
可以通过Nginx在前端做负载均衡,后端启动多个Query Server接收查询请求处理。

5、新功能展望

近实时的流计算:
Apache Kylin v1.6.0带来了更可靠更易于管理的从Apache Kafka流中直接构建Cube的能力,使得用户可以在更多场景中更自然地进行数据分析,使得数据从产生到被检索到的延迟,从以前的一天或数小时,降低到数分钟。
http://kylin.apache.org/cn/blog/2016/12/04/release-v1.6.0/

Spark 构建引擎:
Apache Kylin v2.0.0 引入了一个全新的基于 Apache Spark 的构建引擎。它可用于替换原有的 MapReduce 构建引擎。初步测试显示 Cube 的构建时间一般能缩短到原先的 50% 左右。
http://kylin.apache.org/cn/blog/2017/02/25/v2.0.0-beta-ready/