1.Kylin是什么?

Apache Kylin™是一个开源的、分布式的分析型数据仓库,提供Hadoop/Spark 之上的 SQL 查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由 eBay 开发并贡献至开源社区。它能在亚秒内查询巨大的表。

Apache Kylin™ 令使用者仅需三步,即可实现超大数据集上的亚秒级查询。

定义数据集上的一个星形或雪花形模型
在定义的数据表上构建cube
使用标准 SQL 通过 ODBC、JDBC 或 RESTFUL API 进行查询,仅需亚秒级响应时间即可获得查询结果

优点:
1)标准SQL接口:Kylin是以标准的SQL作为对外服务的接口。
2)支持超大数据集:Kylin对于大数据的支撑能力可能是目前所有技术中最为领先的。早在2015年eBay的生产环境中就能支持百亿记录的秒级查询,之后在移动的应用场景中又有了千亿记录秒级查询的案例。
3)亚秒级响应:Kylin拥有优异的查询相应速度,这点得益于预计算,很多复杂的计算,比如连接、聚合,在离线的预计算过程中就已经完成,这大大降低了查询时刻所需的计算量,提高了响应速度。
4)可伸缩性和高吞吐率:单节点Kylin可实现每秒70个查询,还可以搭建Kylin的集群。
5)BI工具集成
Kylin可以与现有的BI工具集成,具体包括如下内容。
ODBC:与Tableau、Excel、PowerBI等工具集成
JDBC:与Saiku、BIRT等Java工具集成
RestAPI:与JavaScript、Web网页集成
Kylin开发团队还贡献了Zepplin的插件,也可以使用Zepplin来访问Kylin服务。

2.Kylin的架构

Kylin 提供与多种数据可视化工具的整合能力,如 Tableau,PowerBI 等,令用户可以使用 BI 工具对 Hadoop 数据进行分析

kylin 部署apache hadoop apache kylin优缺点_kylin


1)REST Server

REST Server是一套面向应用程序开发的入口点,旨在实现针对Kylin平台的应用开发工作。 此类应用程序可以提供查询、获取结果、触发cube构建任务、获取元数据以及获取用户权限等等。另外可以通过Restful接口实现SQL查询。

2)查询引擎(Query Engine)

当cube准备就绪后,查询引擎就能够获取并解析用户查询。它随后会与系统中的其它组件进行交互,从而向用户返回对应的结果。

3)Routing

负责将解析的SQL生成的执行计划转换成cube缓存的查询,cube是通过预计算缓存在hbase中,这部分查询可以在秒级设置毫秒级完成,而且还有一些操作使用过的查询原始数据(存储在Hadoop的hdfs中通过hive查询)。这部分查询延迟较高。

4)元数据管理工具(Metadata)

Kylin是一款元数据驱动型应用程序。元数据管理工具是一大关键性组件,用于对保存在Kylin当中的所有元数据进行管理,其中包括最为重要的cube元数据。其它全部组件的正常运作都需以元数据管理工具为基础。 Kylin的元数据存储在hbase中。

5)任务引擎(Cube Build Engine)

这套引擎的设计目的在于处理所有离线任务,其中包括shell脚本、Java API以及Map Reduce任务等等。任务引擎对Kylin当中的全部任务加以管理与协调,从而确保每一项任务都能得到切实执行并解决其间出现的故障。

3.Kylin工作原理

3.1维度和度量

维度:即观察数据的角度。比如员工数据,可以从性别角度来分析,也可以更加细化,从入职时间或者地区的维度来观察。维度是一组离散的值,比如说性别中的男和女,或者时间维度上的每一个独立的日期。因此在统计时可以将维度值相同的记录聚合在一起,然后应用聚合函数做累加、平均、最大和最小值等聚合计算。
度量:即被聚合(观察)的统计值,也就是聚合运算的结果。比如说员工数据中不同性别员工的人数,又或者说在同一年入职的员工有多少。

3.2Cube和Cuboid

有了维度跟度量,一个数据表或者数据模型上的所有字段就可以分类了,它们要么是维度,要么是度量(可以被聚合)。于是就有了根据维度和度量做预计算的Cube理论。

Cuboid:对于每一种维度的组合,将度量值做聚合计算,然后将结果保存为一个物化视图

Cube:所有维度组合的Cuboid作为一个整体

kylin 部署apache hadoop apache kylin优缺点_数据_02


一维度(1D)的组合有:[time]、[item]、[location]和[supplier]4种;

二维度(2D)的组合有:[time, item]、[time, location]、[time, supplier]、[item, location]、[item, supplier]、[location, supplier]3种;

三维度(3D)的组合也有4种;

最后还有零维度(0D)和四维度(4D)各有一种,总共16种。

注意:每一种维度组合就是一个Cuboid,16个Cuboid整体就是一个Cube。

3.3核心算法

Kylin的工作原理就是对数据模型做Cube预计算,并利用计算的结果加速查询:

1)指定数据模型,定义维度和度量;

2)预计算Cube,计算所有Cuboid并保存为物化视图;

3)执行查询,读取Cuboid,运行,产生查询结果。

预计算过程是Kylin从Hive中读取原始数据,按照我们选定的维度进行计算,并将结果集保存到Hbase中,默认的计算引擎为MapReduce,可以选择Spark或者Flink作为计算引擎。

kylin 部署apache hadoop apache kylin优缺点_大数据_03


一次build的结果,我们称为一个Segment。构建过程中会涉及多个Cuboid的创建,具体创建过程由kylin.cube.algorithm参数决定,参数值可选 auto,layer 和 inmem, 默认值为 auto,即 Kylin 会通过采集数据动态地选择一个算法 (layer or inmem),如果用户很了解 Kylin 和自身的数据、集群,可以直接设置喜欢的算法。

kylin 部署apache hadoop apache kylin优缺点_kylin_04


简单介绍一下layer 和inmem 算法

逐层构建算法(layer)

kylin 部署apache hadoop apache kylin优缺点_大数据_05


在逐层算法中,按维度数逐层减少来计算,每个层级的计算(除了第一层,它是从原始数据聚合而来),是基于它上一层级的结果来计算的。比如,[Group by A, B]的结果,可以基于[Group by A, B, C]的结果,通过去掉C后聚合得来的;这样可以减少重复计算;当 0维度Cuboid计算出来的时候,整个Cube的计算也就完成了。

每一轮的计算都是一个MapReduce任务,且串行执行;一个N维的Cube,至少需要N次MapReduce Job。

优点:此算法对集群要求低,运行稳定、代码清晰简单,易于维护缺点:对HDFS的读写操作较多、由于Mapper不做预聚合,此算法会对Hadoop MapReduce输出较多数据、当Cube有比较多维度的时候,所需要的MapReduce任务也相应增加;

快速构建算法(inmem)

从1.5.x开始引入该算法,利用Mapper端计算先完成大部分聚合,再将聚合后的结果交给Reducer,从而降低对网络瓶颈的压力。该算法的主要思想是,对Mapper所分配的数据块,将它计算成一个完整的小Cube 段(包含所有Cuboid);每个Mapper将计算完的Cube段输出给Reducer做合并,生成大Cube,也就是最终结果;

kylin 部署apache hadoop apache kylin优缺点_大数据_06


优点:Mapper会利用内存做预聚合,算出所有组合;一轮MapReduce便会完成所有层次的计算,减少Hadoop任务的调配

4.Cube构建优化

假设用户有10 个维度,那么没有经过任何优化的Cube就会存在210 =1024个Cuboid;在构建维度数量较多的Cube时,尤其要注意Cube的剪枝优化(即减少Cuboid的生成)。

4.1.找到问题Cube(了解,没有实际用途)

4.1.1检查Cuboid数量

FirstCube cube

bin/kylin.sh org.apache.kylin.engine.mr.common.CubeStatsReader FirstCube
|---- Cuboid 111, est row: 10, est MB: 0
    |---- Cuboid 011, est row: 9, est MB: 0, shrink: 90%
        |---- Cuboid 001, est row: 3, est MB: 0, shrink: 33.33%
        |---- Cuboid 010, est row: 7, est MB: 0, shrink: 77.78%
    |---- Cuboid 101, est row: 9, est MB: 0, shrink: 90%
        |---- Cuboid 100, est row: 5, est MB: 0, shrink: 55.56%
|---- Cuboid 110, est row: 8, est MB: 0, shrink: 80%

在这棵树中,每个节点代表一个Cuboid,每个Cuboid都由一连串1或0的数字组成,如果数字为0,则代表这个Cuboid中不存在相应的维度;如果数字为1,则代表这个Cuboid中存在相应的维度。除了最顶端的Cuboid之外,每个Cuboid都有一个父亲Cuboid,且都比父亲Cuboid少了一个“1”。其意义是这个Cuboid就是由它的父亲节点减少一个维度聚合而来的(上卷)。最顶端的Cuboid称为Base Cuboid,它直接由源数据计算而来。
每行Cuboid的输出中除了0和1的数字串以外,后面还有每个Cuboid 的的行数与父亲节点的对比(Shrink值)。所有Cuboid行数的估计值之和应该等于Segment的行数估计值,每个Cuboid都是在它的父亲节点的基础上进一步聚合而成的,因此从理论上说每个Cuboid无论是行数还是大小都应该小于它的父亲。在这棵树中,我们可以观察每个节点的Shrink值,如果该值接近100%,则说明这个Cuboid虽然比它的父亲Cuboid少了一个维度,但是并没有比它的父亲Cuboid少很多行数据。换而言之,即使没有这个Cuboid, 我们在查询时使用它的父亲Cuboid,也不会有太大的代价。那么我们就可以对这个Cuboid进行剪枝操作。

4.1.2检查Cube大小

还有一种更为简单的方法可以帮助我们判断Cube是否已经足够优化。在Web GUI的Model页面选择一个READY状态的Cube,当我们把光标移到该Cube的Cube Size列时,Web GUI会提示Cube的源数据大小,以及当前Cube的大小除以源数据大小的比例,称为膨胀率(Expansion Rate),如图所示。

一般来说,Cube的膨胀率应该在0%~1000%之间,如果一个Cube的膨胀率超过1000%,那么Cube管理员应当开始挖掘其中的原因。通常,膨胀率高有以下几个方面的原因。

1)Cube中的维度数量较多,且没有进行很好的Cuboid剪枝优化,导致Cuboid数量极多;

2)Cube中存在较高基数的维度,导致包含这类维度的每一个Cuboid占用的空间都很大,这些Cuboid累积造成整体Cube体积变大;

3)存在比较占用空间的度量,例如Count Distinct,因此需要在Cuboid的每一行中都为其保存一个较大的寄存器,最坏的情况将会导致Cuboid中每一行都有数十KB,从而造成整个Cube的体积变大;

kylin 部署apache hadoop apache kylin优缺点_数据_07

4.2 优化构建(重点、重点)

4.2.1 使用聚合组

定义:每个分组各自独立地根据自身的规则贡献出一批需要被物化的Cuboid,所有分组贡献的Cuboid的并集就成为了当前Cube中所有需要物化的Cuboid的集合。不同的分组有可能会贡献出相同的Cuboid,构建引擎会察觉到这点,并且保证每一个Cuboid无论在多少个分组中出现,它都只会被物化一次。

对于每个分组内部的维度,用户可以使用如下三种可选的方式定义,它们之间的关系,具体如下。

1)强制维度(Mandatory),如果一个维度被定义为强制维度,那么这个分组产生的所有Cuboid中每一个Cuboid都会包含该维度。每个分组中都可以有0个、1个或多个强制维度。如果根据这个分组的业务逻辑,则相关的查询一定会在过滤条件或分组条件中,因此可以在该分组中把该维度设置为强制维度。

kylin 部署apache hadoop apache kylin优缺点_大数据_08


2)层级维度(Hierarchy),每个层级包含两个或更多个维度。假设一个层级中包含D1,D2…Dn这n个维度,那么在该分组产生的任何Cuboid中, 这n个维度只会以(),(D1),(D1,D2)…(D1,D2…Dn)这n+1种形式中的一种出现。每个分组中可以有0个、1个或多个层级,不同的层级之间不应当有共享的维度。如果根据这个分组的业务逻辑,则多个维度直接存在层级关系,因此可以在该分组中把这些维度设置为层级维度。

kylin 部署apache hadoop apache kylin优缺点_kylin_09


3)联合维度(Joint),每个联合中包含两个或更多个维度,如果某些列形成一个联合,那么在该分组产生的任何Cuboid中,这些联合维度要么一起出现,要么都不出现。每个分组中可以有0个或多个联合,但是不同的联合之间不应当有共享的维度(否则它们可以合并成一个联合)。如果根据这个分组的业务逻辑,多个维度在查询中总是同时出现,则可以在该分组中把这些维度设置为联合维度。

kylin 部署apache hadoop apache kylin优缺点_数据_10


总结:聚合组的设计非常灵活,甚至可以用来描述一些极端的设计。假设我们的业务需求非常单一,只需要某些特定的Cuboid,那么可以创建多个聚合组,每个聚合组代表一个Cuboid。具体的方法是在聚合组中先包含某个Cuboid所需的所有维度,然后把这些维度都设置为强制维度。这样当前的聚合组就只能产生我们想要的那一个Cuboid了。

再比如,有的时候我们的Cube中有一些基数非常大的维度,如果不做特殊处理,它就会和其他的维度进行各种组合,从而产生一大堆包含它的Cuboid。包含高基数维度的Cuboid在行数和体积上往往非常庞大,这会导致整个Cube的膨胀率变大。如果根据业务需求知道这个高基数的维度只会与若干个维度(而不是所有维度)同时被查询到,那么就可以通过聚合组对这个高基数维度做一定的“隔离”。我们把这个高基数的维度放入一个单独的聚合组,再把所有可能会与这个高基数维度一起被查询到的其他维度也放进来。这样,这个高基数的维度就被“隔离”在一个聚合组中了,所有不会与它一起被查询到的维度都没有和它一起出现在任何一个分组中,因此也就不会有多余的Cuboid产生。这点也大大减少了包含该高基数维度的Cuboid的数量,可以有效地控制Cube的膨胀率。这些操作可以在Cube Designer的Advanced Setting中的Aggregation Groups区域完成,如下图所示。

kylin 部署apache hadoop apache kylin优缺点_spark_11

4.2.2 并发粒度优化

主要是三个参数

kylin.hbase.region.cut 
kylin.hbase.region.count.min
kylin.hbase.region.count.max

当Segment中某一个Cuboid的大小超出一定的阈值时,系统会将该Cuboid的数据分片到多个分区中,以实现Cuboid数据读取的并行化,从而优化Cube的查询速度。具体的实现方式如下:构建引擎根据Segment估计的大小,以及参数“kylin.hbase.region.cut”的设置决定Segment在存储引擎中总共需要几个分区来存储,如果存储引擎是HBase,那么分区的数量就对应于HBase中的Region数量。kylin.hbase.region.cut的默认值是5.0,单位是GB,也就是说对于一个大小估计是50GB的Segment,构建引擎会给它分配10个分区。用户还可以通过设置kylin.hbase.region.count.min(默认为1)和kylin.hbase.region.count.max(默认为500)两个配置来决定每个Segment最少或最多被划分成多少个分区。

5.Rowkeys

Kylin中的编码方式包括Date编码、Time编码、Integer编码、Boolean编码、Dict编码和Fixed Length编码,用户可以根据需求选择合适的编码方式。

Date编码

将日期类型的数据使用三个字节进行编码,支持的格式包括yyyyMMdd、yyyy-MM-dd、yyyy-MM-dd HH:mm:ss、yyyy-MM-dd HH:mm:ss.SSS,其中如果包含时间戳部分会被截断。

3个字节(23位), 支持0000-01-01到9999-01-01

Time编码

对时间戳字段进行编码,4个字节,支持范围为[ 1970-01-01 00:00:00, 2038/01/19 03:14:07],毫秒部分会被忽略。time编码适用于time, datetime, timestamp等类型。

Integer编码

将数值类型字段直接用数字表示,不做编码转换。Integer编码需要提供一个额外的参数“Length”来代表需要多少个字节。Length的长度为1到8,支持的整数区间为[ -2^(8N-1), 2^(8N-1)]。

Dict编码

使用字典将长的值映射成短的ID,适合中低基数的维度,默认推荐编码。但由于字典要被加载到Kylin内存中,在超高基情况下,可能引起内存不足的问题。字典只能处理中低基数(少于一千万)的维度;
字典编码为一颗Trie树,也叫字典树,是一种哈希树的变种,优点是利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

它有三个基本特性:

根节点不包含字符,除根节点外每一个节点都只包含一个字符; 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 每个节点的所有子节点包含的字符都不相同。

Fixed_length编码

适用于超高基数场景,将选取字段的前N个字节作为编码值,当N小于字段长度,会造成字段截断,当N较大时,造成RowKey过长,查询性能下降。只适用于varchar或nvarchar类型。
如果维度基数很高(如大于1千万), 选择 “false” 然后为维度输入合适的长度,通常是那列的最大长度值; 如果超过最大值,会被截断。请注意,如果没有字典编码,cube 的大小可能会非常大。

Fixed_Length_Hex编码

适用于字段值为十六进制字符,比如1A2BFF或者FF00FF,每两个字符需要一个字节。只适用于varchar或nvarchar类型。

你可以拖拽维度列去调整其在 rowkey 中位置; 位于rowkey前面的列,将可以用来大幅缩小查询的范围。通常建议将 mandantory 维度放在开头, 然后是在过滤 ( where 条件)中起到很大作用的维度;如果多个列都会被用于过滤,将高基数的维度(如 user_id)放在低基数的维度(如 age)的前面。

6.下压查询到hive

kylin.query.pushdown.runner-class-name=org.apache.kylin.query.adhoc.PushDownRunnerJdbcImpl
#
##kylin.query.pushdown.update-enabled=false
kylin.query.pushdown.jdbc.url=jdbc:hive2://node01:10000/default
kylin.query.pushdown.jdbc.driver=org.apache.hive.jdbc.HiveDriver
kylin.query.pushdown.jdbc.username=hive
kylin.query.pushdown.jdbc.password=
#
kylin.query.pushdown.jdbc.pool-max-total=8
kylin.query.pushdown.jdbc.pool-max-idle=8
kylin.query.pushdown.jdbc.pool-min-idle=0

7.集成spark

准备 “kylin.env.hadoop-conf-dir”
为使 Spark 运行在 Yarn 上,需指定 HADOOP_CONF_DIR 环境变量,其是一个包含 Hadoop(客户端) 配置文件的目录,通常是 /etc/hadoop/conf。

通常 Kylin 会在启动时从 Java classpath 上检测 Hadoop 配置目录,并使用它来启动 Spark。 如果您的环境中未能正确发现此目录,那么可以显式地指定此目录:在 kylin.properties 中设置属性 “kylin.env.hadoop-conf-dir” 好让 Kylin 知道这个目录:

kylin.env.hadoop-conf-dir=/etc/hadoop/conf

Kylin 在 $KYLIN_HOME/spark 中嵌入一个 Spark binary (v2.1.2),所有使用 “kylin.engine.spark-conf.” 作为前缀的 Spark 配置属性都能在 $KYLIN_HOME/conf/kylin.properties 中进行管理。这些属性当运行提交 Spark job 时会被提取并应用;例如,如果您配置 “kylin.engine.spark-conf.spark.executor.memory=4G”,Kylin 将会在执行 “spark-submit” 操作时使用 “–conf spark.executor.memory=4G” 作为参数。

运行 Spark cubing 前,建议查看一下这些配置并根据您集群的情况进行自定义。下面是建议配置,开启了 Spark 动态资源分配:

kylin.engine.spark-conf.spark.master=yarn
kylin.engine.spark-conf.spark.submit.deployMode=cluster
kylin.engine.spark-conf.spark.dynamicAllocation.enabled=true
kylin.engine.spark-conf.spark.dynamicAllocation.minExecutors=1
kylin.engine.spark-conf.spark.dynamicAllocation.maxExecutors=1000
kylin.engine.spark-conf.spark.dynamicAllocation.executorIdleTimeout=300
kylin.engine.spark-conf.spark.yarn.queue=default
kylin.engine.spark-conf.spark.driver.memory=2G
kylin.engine.spark-conf.spark.executor.memory=4G
kylin.engine.spark-conf.spark.yarn.executor.memoryOverhead=1024
kylin.engine.spark-conf.spark.executor.cores=1
kylin.engine.spark-conf.spark.network.timeout=600
kylin.engine.spark-conf.spark.shuffle.service.enabled=true
#kylin.engine.spark-conf.spark.executor.instances=1
kylin.engine.spark-conf.spark.eventLog.enabled=true
kylin.engine.spark-conf.spark.hadoop.dfs.replication=2
kylin.engine.spark-conf.spark.hadoop.mapreduce.output.fileoutputformat.compress=true
kylin.engine.spark-conf.spark.hadoop.mapreduce.output.fileoutputformat.compress.codec=org.apache.hadoop.io.compress.DefaultCodec
kylin.engine.spark-conf.spark.io.compression.codec=org.apache.spark.io.SnappyCompressionCodec
kylin.engine.spark-conf.spark.eventLog.dir=hdfs\:///kylin/spark-history
kylin.engine.spark-conf.spark.history.fs.logDirectory=hdfs\:///kylin/spark-history


## uncomment for HDP
#kylin.engine.spark-conf.spark.driver.extraJavaOptions=-Dhdp.version=current
#kylin.engine.spark-conf.spark.yarn.am.extraJavaOptions=-Dhdp.version=current
#kylin.engine.spark-conf.spark.executor.extraJavaOptions=-Dhdp.version=current

除此之外,为了避免重复上传 Spark jar 包到 Yarn,您可以手动上传一次,然后配置 jar 包的 HDFS 路径;请注意,HDFS 路径必须是全路径名。

jar cv0f spark-libs.jar -C /opt/cloudera/parcels/CDH/lib/spark/jars .
hadoop fs -mkdir -p /kylin/spark/
hadoop fs -put spark-libs.jar /kylin/spark/

然后,要在 kylin.properties 中进行如下配置:

kylin.engine.spark-conf.spark.yarn.archive=hdfs://nameservice1:8020/kylin/spark/spark-libs.jar

8.cube增量构建

#!/bin/bash


#增量构建cube的名字
cubeName=comment_cube2

#startTime
yesterday=`date -d "-1 day" +%F`
#endTime
today=`date +%Y-%m-%d`

#转成秒为单位
ytdTs=`date -d "$yesterday 08:00:00" +%s`
tdTs=`date -d "$today 08:00:00" +%s`

#转成毫秒为单位
startTime=$(($ytdTs*1000))
endTime=$(($tdTs*1000))


#填写对应的kylin地址和账号密码
curl --user ADMIN:KYLIN -X PUT -H 'Content-Type: application/json' -d '{"startTime":'$startTime',"endTime":'$endTime', "buildType":"BUILD"}' http://node02:7070/kylin/api/cubes/$cubeName/rebuild