目录
2.1.4 Cube,Cuboid,Cube Segment
1.Apache Kylin概述
Apache Kylin是Hadoop大数据平台上的一个开源OLAP引擎。它采用多维立方体预计算技术,可以将大数据的SQL查询速度提升到亚秒级别。相对于之前的分钟乃至小时级别的查询速度,亚秒级别速度是百倍到千倍的提升,该引擎为超大规模数据集上的交互式大数据分析打开了大门。Apache Kylin也是中国人主导的、唯一的Apache顶级开源项目,在开源社区有世界级的影响力。
1.1 apache kylin的工作原理
1.1.1 维度和度量简介
在说明MOLAP Cube之前需要先介绍一下维度(Dimension)和度量(Measure)这两个概念。简单来讲,维度就是观察数据的角度。比如电商的销售数据,可以从时间的维度来观察(如图1-2的左侧所示),也可以进一步细化,从时间和地区的维度来观察(如图1-2的右侧所示)。维度一般是一组离散的值,比如时间维度上的每一个独立的日期,或者商品维度上的每一件独立的商品。因此统计时可以把维度值相同的记录聚合在一起,然后应用聚合函
数做累加、平均、去重复计数等聚合计算。
度量就是被聚合的统计值,也是聚合运算的结果,它一般是连续的值,如图1-2中的销售额,抑或是销售商品的总件数。通过比较和测算度量,分析师可以对数据进行评估,比如今年的销售额相比去年有多大的增长,增长的速度是否达到预期,不同商品类别的增长比例是否合理等。
1.1.2 Cube和Cuboid
有了维度和度量,一个数据表或数据模型上的所有字段就可以分类了,它们要么是维度,要么是度量(可以被聚合)。于是就有了根据维度和度量做预计算的Cube理论。给定一个数据模型,我们可以对其上的所有维度进行组合。对于N个维度来说,组合的所有可能性共有2的N次方种。对于每一种维度的组合,将度量做聚合运算,然后将运算的结果保存为一个物化视图,称为Cuboid。所有维度组合的Cuboid作为一个整体,被称为Cube。所以简单来说,一个Cube就是许多按维度聚合的物化视图的集合。
1.1.3 工作原理
Apache Kylin的工作原理就是对数据模型做Cube预计算,并利用计算的结果加速查询,具体工作过程如下。
- 指定数据模型,定义维度和度量。
- 预计算Cube,计算所有Cuboid并保存为物化视图。
- 执行查询时,读取Cuboid,运算,产生查询结果。
由于Kylin的查询过程不会扫描原始记录,而是通过预计算预先完成表的关联、聚合等复杂运算,并利用预计算的结果来执行查询,因此相比非预计算的查询技术,其速度一般要快一到两个数量级,并且这点在超大的数据集上优势更明显。当数据集达到千亿乃至万亿级别时,Kylin的速度甚至可以超越其他非预计算技术1000倍以上。
1.2 apache kylin的技术架构
Apache Kylin系统可以分为在线查询和离线构建两部分,技术架构如图所示,在线查询的模块主要处于上半区,而离线构建则处于下半区。
1.3 apache kylin的主要特点
Apache Kylin的主要特点包括支持SQL接口、支持超大数据集、秒级响应、可伸缩性、高吞吐率、BI工具集成等。
2 快速入门
2.1 核心概念
在开始使用Kylin之前,我们有必要先了解一下Kylin里的各种概念和术语
2.1.1 数据仓库,OLAP与BI
数据仓库(Data Warehouse)是一种信息系统的资料储存理论,此理论强调的是利用某些特殊的资料储存方式,让所包含的资料特别有利于分析和处理,从而产生有价值的资讯,并可依此做出决策。利用数据仓库的方式存放的资料,具有一旦存入,便不会随时间发生变动的特性,此外,存入的资料必定包含时间属性,通常一个数据仓库中会含有大量的历史性资料,并且它可利用特定的分析方式,从其中发掘出特定的资讯。
OLAP(Online Analytical Process),联机分析处理,以多维度的方式分析数据,而且能够弹性地提供上卷(Roll-up)、下钻(Drill-down)和透视分析(Pivot)等操作,它是呈现集成性决策信息的方法,多用于决策支持系统、商务智能或数据仓库。其主要的功能在于方便大规模数据分析及统计计算,可对决策提供参考和支持。与之相区别的是联机交易处理(OLTP),联机交易处理,更侧重于基本的、日常的事务处理,包括数据的增删改查。
·OLAP需要以大量历史数据为基础,再配合上时间点的差异,对多维度及汇整型的信息进行复杂的分析。
·OLAP需要用户有主观的信息需求定义,因此系统效率较佳。
OLAP的概念,在实际应用中存在广义和狭义两种不同的理解方式。广义上的理解与字面上的意思相同,泛指一切不会对数据进行更新的分析处理。但更多的情况下OLAP被理解为其狭义上的含义,即与多维分析相关,基于立方体(Cube)计算而进行的分析。
BI(Business Intelligence),即商务智能,指用现代数据仓库技术、在线分析技术、数据挖掘和数据展现技术进行数据分析以实现商业价值。
2.1.2 维度和度量
维度和度量是数据分析中的两个基本概念。
维度是指审视数据的角度,它通常是数据记录的一个属性,例如时间、地点等。度量是基于数据所计算出来的考量值;它通常是一个数值,如总销售额、不同的用户数等。分析人员往往要结合若干个维度来审查度量值,以便在其中找到变化规律。在一个SQL查询中,Group By的属性通常就是维度,而所计算的值则是度量。
2.1.3 事实表和维度表
事实表(Fact Table)是指存储有事实记录的表,如系统日志、销售记录等;事实表的记录在不断地动态增长,所以它的体积通常远大于其他表。
维度表(Dimension Table)或维表,有时也称查找表(Lookup Table),是与事实表相对应的一种表;它保存了维度的属性值,可以跟事实表做关联;相当于将事实表上经常重复出现的属性抽取、规范出来用一张表进行管理。常见的维度表有:日期表(存储与日期对应的周、月、季度等的属性)、地点表(包含国家、省/州、城市等属性)等。使用维度表有诸多好处,具体如下:
缩小了事实表的大小。
便于维度的管理和维护,增加、删除和修改维度的属性,不必对事
实表的大量记录进行改动。
维度表可以为多个事实表重用,以减少重复工作。
2.1.4 Cube,Cuboid,Cube Segment
Cube(或Data Cube),即数据立方体,是一种常用于数据分析与索引的技术;它可以对原始数据建立多维度索引。通过Cube对数据进行分析,可以大大加快数据的查询效率。
Cuboid在Kylin中特指在某一种维度组合下所计算的数据。
Cube Segment是指针对源数据中的某一个片段,计算出来的Cube数据。通常数据仓库中的数据数量会随着时间的增长而增长,而Cube Segment也是按时间顺序来构建的。
2.2 在hive中准备数据
2.2.1 星形模型
数据挖掘有几种常见的多维数据模型,如星形模型(Star Schema)、雪花模型(Snowf?lake Schema)、事实星座模型(Fact Constellation)等。星形模型中有一张事实表,以及零个或多个维度表;事实表与维度表通过主键外键相关联,维度表之间没有关联,就像很多星星围绕在一个恒星周围,故取名为星形模型。如果将星形模型中某些维度的表再做规范,抽取成更细的维度表,然后让维度表之间也进行关联,那么这种模型称为雪花模型。星座模型是更复杂的模型,其中包含了多个事实表,而维度表是公用的,可以共享。不过,Kylin只支持星形模型的数据集,这是基于以下考虑。·星形模型是最简单,也是最常用的模型。·由于星形模型只有一张大表,因此它相比于其他模型更适合于大数据处理。·其他模型可以通过一定的转换,变为星形模型。
2.2.2 维度表的设计
除了数据模型以外,Kylin还对维度表有一定的要求,具体要求如下。
- 要具有数据一致性,主键值必须是唯一的;Kylin会进行检查,如果有两行的主键值相同则会报错。
- 维度表越小越好,因为Kylin会将维度表加载到内存中供查询;过大的表不适合作为维度表,默认的阈值是300MB。
- 改变频率低,Kylin会在每次构建中试图重用维度表的快照,如果维度表经常改变的话,重用就会失效,这就会导致要经常对维度表创建快照。
- 维度表最好不要是Hive视图(View),虽然在Kylin1.5.3中加入了对维度表是视图这种情况的支持,但每次都需要将视图进行物化,从而导致额外的时间开销。
2.2.3 维度的基数
维度的基数(Cardinality)指的是该维度在数据集中出现的不同值的个数;例如“国家”是一个维度,如果有200个不同的值,那么此维度的基数就是200。通常一个维度的基数会从几十到几万个不等,个别维度如“用户ID”的基数会超过百万甚至千万。基数超过一百万的维度通常被称为超高基数维度(Ultra High Cardinality,UHC),需要引起设计者的注意。
Cube中所有维度的基数都可以体现出Cube的复杂度,如果一个Cube中有好几个超高基数维度,那么这个Cube膨胀的概率就会很高。在创建Cube前需要对所有维度的基数做一个了解,这样就可以帮助设计合理的Cube。计算基数有多种途径,最简单的方法就是让Hive执行一个count distinct的SQL查询;
2.3 设计Cube
2.3.1 导入Hive表定义
登录Kylin的Web界面,创建新的或选择一个已有的项目之后,需要做的就是将Hive表的定义导入到Kylin中。单击Web界面的Model→Data source下的“Load Hive Table”图标,然后输入表的名称(可以一次导入多张表,以逗号分隔表名),单击按钮“Sync”,Kylin就会使用Hive的API从Hive中获取表的属性信息。
同时,Kylin会在后台触发一个MapReduce任务,计算此表每个列的基数。通常稍过几分钟之后再刷新页面,就会看到显示出来的基数信息,
需要注意的是这里Kylin对基数的计算方法采用的是HyperLogLog的近似算法,与精确值略有误差,但作为参考值已经足够了。
2.3.2 创建数据模型
有了表信息之后,就可以开始创建数据模型(Data Model)了。数据模型是Cube的基础,它主要用于描述一个星形模型。有了数据模型以后,定义Cube的时候就可以直接从此模型定义的表和列中进行选择了,省去重复指定连接(join)条件的步骤。基于一个数据模型还可以创建多个Cube,以方便减少用户的重复性工作。在Kylin界面的“Models”页面中,单击“New”→“New Model”,开始创建数据模型。给模型输入名称之后,选择一个事实表(必需的),然后添加维度表(可选)
填写模型名称和说明:
选择数据来源(表名):
选择列名:尽量多选
如果不是分区表,以下不用填写,下一步保存即可
到此为止,模型建完:
2.4 构建Cube
新创建的Cube只有定义,而没有计算的数据,它的状态是“DISABLED”,是不会被查询引擎挑中的。要想让Cube有数据,还需要对它进行构建。Cube的构建方式通常有两种:全量构建和增量构建;两者的构建步骤是完全一样的,区别只在于构建时读取的数据源是全集还是子集。
Cube的构建包含如下步骤,由任务引擎来调度执行。
- 创建临时的Hive平表(从Hive读取数据)。
- 计算各维度的不同值,并收集各Cuboid的统计数据。
- 创建并保存字典。
- 保存Cuboid统计信息。
- 创建HTable。
- 计算Cube(一轮或若干轮MapReduce)。
- 将Cube的计算结果转成HFile。
- 加载HFile到HBase。
- 更新Cube元数据。
- 垃圾回收。
2.4.1 全量构建和增量构建
1 全量构建
对数据模型中没有指定分割时间列信息的Cube,Kylin会采用全量构建,即每次从Hive中读取全部的数据来开始构建。通常它适用于以下两种情形。
- ·事实表的数据不是按时间增长的。
- ·事实表的数据比较小或更新频率很低,全量构建不会造成太大的开销。
2.增量构建
增量构建的时候,Kylin每次都会从Hive中读取一个时间范围内的数据,然后进行计算,并以一个Segment的形式进行保存。下次再构建的时候,会自动以上次结束的时间为起点时间,再选择新的终止时间进行构建。经过多次构建,Cube中将会有多个Segment依次按时间顺序进行排列,如Seg-1,Seg-2,…,Seg-N。查询的时候,Kylin会查询一个或多个Segment然后再做聚合计算,以便返回正确的结果给请求者。使用增量构建的好处是,每次只需要对新增数据进行计算,从而避免了对历史数据进行重复计算。对于数据量很大的Cube,使用增量构建是非常有必要的。
下面简单举例全量构建:
选择模型以及填写Cube名称,其中Notification Email List指的是接收邮件的邮箱,Notification Events 指的是需要发送邮件的级别,默认成功,失败以及丢弃三种
选择维度,尽量选择以后会用到的,这是优化的一个点
选择度量:
没有什么特殊需要的话,之后的一直点下一步就好了:
查看创建好的Cube,发现处于DISABLED禁用状态,需要我们build创建一下
查看正在计算的Cube
查看运行日志:
3.Cube优化
Apache Kylin的核心思想是根据用户的数据模型和查询样式对数据进行预计算,并在查询时直接利用预计算结果返回查询结果。相比普通的大规模并行处理的解决方案,Kylin具有响应时间快、查询时资源需求小、吞吐量大等优点。用户的数据模型包括维度、度量、分割时间列等基本信息,也包括用户通过Cube优化工具赋予的额外的模型信息。例如,层级(Hierarchy)是一种用来描述若干个维度之间存在层级关系的优化工具,提供层级信息有助于帮助预计算跳过多余的预计算,从而减少预计算的工作量,并且最终减少存储引擎所需要存储的Cube数据大小。数据模型是数据固有的属性,除此之外,查询的样式如果相对固定,也可以用来帮助Cube的优化。例如,如果我们知道客户端的查询总是会带有某个维度上的过滤(Filter)条件,或者总是会按照这个维度进行聚合(GroupBy),那么所有的不带这个维度的场景下的预计算都可以被跳过,因为即使为这些场景进行了预计算,这些预计算结果也从来不会被用到。总的来说,在构建Cube之前,Cube的优化手段提供了更多与数据模型或查询样式相关的信息,用于指导构建出体积更小、查询速度更快的Cube。可以看到Cube的优化目的始终有两个:空间优化和查询时间优化。
3.1 Cuboid剪枝优化
3.1.1 维度的诅咒
在没有采取任何优化措施的情况下,Kylin会对每一种维度的组合进行预计算,每种维度的组合的预计算结果
被称为Cuboid。假设有4个维度,结合简单的数学知识,我们可能最终会有2的4次方 =16个Cuboid需要计算。
但在现实情况中,用户的维度数量一般远远大于4个。假设用户有10个维度,那么没有经过任何优化的Cube就会存在210 =1024个Cuboid;而如果用户有20个维度,那么Cube中总共会存在220 =1048576个Cuboid。虽然每个Cuboid的大小存在很大的差异,但是单单想到Cuboid的数量就足以让人想象到这样的Cube对构建引擎、存储引擎来说压力有多么巨大。因此,在构建维度数量较多的Cube时,尤其要注意Cube的剪枝优化。
3.1.2 检查Cuboid数量
Apache Kylin提供了一个简单的工具,供用户检查Cube中哪些Cuboid最终被预计算了,我们称其为被物化(Materialized)的Cuboid。同时,这种方法还能给出每个Cuboid所占空间的估计值。由于该工具需要在对数据进行一定阶段的处理之后才能估算Cuboid的大小,因此一般来说只能在Cube构建完毕之后再使用该工具。
bin/kylin.sh org.apache.kylin.engine.mr.common.cubeStatsReader CUBE_NAME
CUBE_NAME 想要查看的cube的名字
3.1.3 检查Cube大小
还有一种更为简单的方法可以帮助我们判断Cube是否已经足够优化。在Web GUI的Model页面选择一个READY状态的Cube,当我们把光标移到该Cube的Cube Size列时,Web GUI会提示Cube的源数据大小,以及当前Cube的大小除以源数据大小的比例,称为膨胀率(Expansion Rate)
一般来说,Cube的膨胀率应该在0%~1000%之间,如果一个Cube的膨胀率超过1000%,那么Cube管理员应当开始挖掘其中的原因。通常,膨胀率高有以下几个方面的原因。
- Cube中的维度数量较多,且没有进行很好的Cuboid剪枝优化,导致Cuboid数量极多。
- Cube中存在较高基数的维度,导致包含这类维度的每一个Cuboid占用的空间都很大,这些Cuboid累积造成整体Cube体积变大。
- 存在比较占用空间的度量,例如Count Distinct,因此需要在Cuboid的每一行中都为其保存一个较大的寄存器,最坏的情况将会导致Cuboid中每一行都有数十KB,从而造成整个Cube的体积变大。
3.2 并发粒度优化
当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最少或最多被划分成多少个分区。
3.3 Rowkeys优化
3.3.1 编码
编码(Encoding)代表了该维度的值应使用何种方式进行编码,合适的编码能够减少维度对空间的占用,例如,我们可以把所有的日期都用三个字节进行编码,相比于字符串存储,或者是使用长整数形式存储的方法,我们的编码方式能够大大减少每行Cube数据的体积。而Cube中可能存在数以亿计的行数,使用编码节约的空间累加起来将是一个非常巨大的数字。
目前Kylin支持的编码方式有以下几种:
- Date编码:将日期类型的数据使用三个字节进行编码,其支持从0000-01-01到9999-01-01中的每一个日期。
- Time编码:仅支持表示从1970-01-0100:00:00到2038-01-1903:14:07的时间,且Time-stamp类型的维度经过编码和反编码之后,会失去毫秒信息,所以说Time编码仅仅支持到秒。但是Time编码的优势是每个维度仅仅使用4个字节,这相比普通的长整数编码节约了一半。如果能够接受秒级的时间精度,请选择Time编码来代表时间的维度。
- Integer编码:Integer编码需要提供一个额外的参数“Length”来代表需要多少个字节。Length的长度为1~8。如果用来编码int32类型的整数,可以将Length设为4;如果用来编码int64类型的整数,可以将Length设为8。在更多情况下,如果知道一个整数类型维度的可能值都很小,那么就能使用Length为2甚至是1的int编码来存储,这将能够有效避免存储空间的浪费。
- Dict编码:对于使用该种编码的维度,每个Segment在构建的时候都会为这个维度所有可能的值创建一个字典,然后使用字典中每个值的编号来编码。Dict的优势是产生的编码非常紧凑,尤其在维度值的基数较小且长度较大的情况下,特别节约空间。由于产生的字典是在查询时加载入构建引擎和查询引擎的,所以在维度的基数大、长度也大的情况下,容易造成构建引擎或查询引擎的内存溢出。
- Fixed_length编码:编码需要提供一个额外的参数“Length”来代表需要多少个字节。该编码可以看作Dict编码的一种补充。对于基数大、长度也大的维度来说,使用Dict可能不能正常工作,于是可以采用一段固定长度的字节来存储代表维度值的字节数组,该数组为字符串形式的维度值的UTF-8字节。如果维度值的长度大于预设的Length,那么超出的部分将会被截断。
在未来,Kylin还有可能为特定场景、特定类型的维度量身定制特别的编码方式,例如在很多行业,身份证号码可能就是一个重要的维度,但是身份证号码由于其具有特殊性而不能使用整数类型的编码(身份证最后一位可能是X),其高基数的特点也决定了不能使用dict编码,在目前的版本中只能使用f?ixed_length编码,但是显然f?ixed_length不能充分利用身份证号码中大部分字节是数字的特性来进行深度编码,因此存在一定程度的浪费。
3.3.2 按维度分片
系统可能会对Cuboid的数据进行分片处理。但是默认情况下Cuboid的分片策略是随机的,也就是说,我们无法控制Cuboid的哪些行会被分到同一个分片中。这种默认的方法固然能够提高读取的并发程度,但是它仍然有优化的空间。按维度分片(Shard by Dimension)提供了一种更加高效的分片策略,那就是按照某个特定维度进行分片。简单地说,如果Cuboid中某两个行的Shard by Dimension的值相同,那么无论这个Cuboid最终会被划分成多少个分片,这两行数据必然会被分配到同
一个分片中。
这种分片策略对查询有着极大的好处。我们知道,Cuboid的每个分片都会被分配到存储引擎的不同物理机器上Kylin在读取Cuboid数据的时候会向存储引擎的若干机器发送所读取的RPC请求。在RPC请求接收端,存储引擎会读取本机的分片据,并在进行一定的预处理后再发送RPC回应(如图6-11所示)。以HBase存储引擎为例,不同的Region代表不同的Cuboid分片,在读取Cuboid数据的时候,HBase会为每个Region开启一个Coprocessor实例来处理查询引擎的请求。查询引擎将查询条件和分组条
件作为请求参数的一部分发送到Coprocessor中,Coprocessor就能够在返回结果之前先对当前分片的数据做一定的预聚合(这里的预聚合不是Cube构建的预聚合,而是针对特定查询深度的预聚合)。
如果按照维度划分分片,假设按照一个基数比较高的维度seller_id进行分片,那么在这种情况下,每个分片将会承担一部分seller_id,且各个分片不会有相同的seller_id。所有按照seller_id分组(Group by seller_id)的查询都会变得更加高效,因为每个分区预聚合的结果都会更加专注于某一些seller_id之上,使得分片返回的结果数量大大减少,查询引擎端也无需对各个分片的结果做分片间的聚合。按维度分片也能让过滤条件的执行更加高效,因为是按维度分片,所以每个分片的数据都会更加“整洁”,更方便查找和索引。
3.3.3 调整RowKeys的顺序
在Cube Designer→Advanced Setting→Rowkeys部分,我们可以上下拖动每一个维度来调节维度在Rowkeys中的顺序。这种顺序对于查询非常重要,因为在目前的实现中,Kylin会把所有的维度按照顺序黏合成一个完整的Rowkeys,并且按照这个Rowkeys升序排列Cuboid中所有的行
不难发现,如果在一个比较靠后的维度上有过滤条件,那么这个过滤条件的执行就会非常复杂。以目前的HBase存储引擎为例,Rowkeys对应HBase中的Rowkeys,是一段字节数组。目前没有创建单独的每个维度上的倒排索引,因此对于在比较靠后的维度上的过滤条件,只能依靠HBase的FuzzyKeyFilter来执行。尽管HBase做了大量相应的优化,但是因为是在对靠后的字节运用FuzzyKeyFilter,因此一旦前面维度的基数很大,那么FuzzyKeyFilter的寻找代价就会很高,执行效率就会变差。所以,在调整Rowkeys的顺序时需要遵守以下几个原则:
- 在查询中被用作过滤条件的维度有可能放在其他维度的前面。
- 将经常出现在查询中的维度放在不经常出现的维度的前面。
- 对于基数较高的维度,如果查询会有这个维度上的过滤条件,那么将它往前调整;如果没有,则向后调整。
3.4 其他优化
3.4.1 降低度量精度
有一些度量具有多种可选精度,但是精度越高的度量往往越会存在一定的代价,它意味着更大的占用空间和运行时开销。以近似值的Count Distinct度量为例,Kylin提供了多种可选精度,现挑选其中的几种进行对比,见表:
可以看到,精度最大的类型比精度最小的类型多出64倍的空间占用,而即使精度最小的Count Distinct度量也已经非常占用空间了。因此,当业务可以接受较低一些的精度时,用户应当考虑到Cube空间方面的影响,尽量选择小精度的度量。
3.4.2 及时清理无用的segment
随着增量构建出来的Segment的慢慢累积,Cube的查询性能将会变差,因为每次跨Segment的查询都需要从存储引擎中读取每一个Segment的数据,并且在查询引擎中对不同Segment的数据做进一步的聚合,这对于查询引擎和存储引擎来说都是巨大的压力。从这个角度来说,及时地使用第3章介绍的Segment碎片清理方法,有助于提高Cube的使用效率。