Apache Kylin采用“预计算”的模式,用户只需要提前定义好查询维度,Kylin将帮助我们进行计算,并将结果存储到HBase中,为海量数据的查询和分析提供亚秒级返回,是一种典型的“空间换时间”的解决方案。
数据仓库
Data Warehouse,简称DW,中文名数据仓库,是商业智能(BI)中的核心部分。数据仓库中存储的则主要是历史数据,主要是将不同数据源的数据整合到一起,目的是为企业决策提供支持,所以可能存在大量数据冗余,但利于多个维度查询,为决策者提供更多观察视角。
OLAP
OLAP(Online Analytical Process),联机分析处理,以多维度的方式分析数据,一般带有主观的查询需求,多应用在数据仓库,侧重于提供决策支持。与之对应的是OLTP(Online Transaction Process),联机事务处理,侧重于数据库的增删查改等常用业务操作。
OLAP以多维度的方式分析数据,而且能够弹性地提供以下几种操作
维度和度量
事实表和维度表
模型概念
需要被分析的数据必须先保存为Hive表的形式,然后Kylin才能从Hive中导入数据,创建Cube。Cube支持从Hive视图中构建,基于这个特点,可以将原始数据做一定的处理,如增加维度或者做一些预处理,生成相应的视图,基于视图来构建Cube。
维度的基数,维度的基数体现了Cube的复杂程度,维度基数过大,会增加Cube的膨胀程度,使用Count-Distinct来对一个维度的基数做一个统计,可以保证能够设计合理的Cube。
Kylin支持增量Cube构建,通常是按事件属性来增量的从Hive表中抽取数据。因此Hive表最好按时间属性分区,这样可以避免全量数据的扫描,减少读写操作对集群的压力,节省Cube构建的时间。
UHC 代表 Ultra High Cardinality,即超高基数。基数表示维度不同值的数量。通常,维度的基数从数十到数百万。如果超过百万,我们将其称为超高基维度,Kylin 支持超高基维度,但是在 Cube 设计中额外注意超高基维度,它们可能会使 Cube 体积非常大、查询变慢。
Cube 的最大物理维度数量 (不包括衍生维度) 是 63,但是不推荐使用大于 30 个维度的 Cube,会引起维度灾难。
创建或者选择一个已有的Project,将Hive中表的定义导入到Kylin中,Web界面的操作如下,Mode->DataSource->Load Hive Table。
之后Kylin会触发一个MR或者Spark任务,计算此表基于每个列的基数,这里Kylin对基数的计算方法采用的是HyperLogLog近似算法,与精确值有误差,但是作为参考值已经足够了。
数据模型是构建Cube的基础,该数据模型可以描述为一个星型模型或者一个雪花模型,有了模型定义Cube的时候,可以在此模型定义的表和列中进行选择,基于一个模型可以创建多个Cube,减少了用户的重复性工作。
在Web界面,点击New->New Model,开始创建数据模型。
填写好基本信息之后,开始构建模型。
首先选择事实表,然后添加维度表,添加维度表需要选择连接的类型,是Inner还是Left,然后选择连接的主键和外键。
接下来会选择用作维度或者度量的列,这里只是选择一个范围,不代表这些列将来一定会用作Cube的构建,在这里可以把可能会用到的列都添加进来,创建Cube的时候,将只能从这些列中选择。
度量列只能来自事实表,维度列可以来自维度表和事实表。
最后一步是,为模型补充分割时间的列和过滤条件,如果此模型中的事实表的记录是按照时间来增加的,可以指定一个日期或者时间列作为模型的分割时间列,从而可以让Cube按此列做增量构建。
除此之外,可以指定过滤条件。Kylin在向Hive请求数据的时候,会带上此过滤条件。
1)首先选择要使用的数据模型,并为此Cube输入一个唯一的名称,添加一些描述信息。
2)然后选择Cube的维度
Add Dimension逐个添加维度,可以是普通维度,可以是衍生(Derived)维度。
需要为每一个维度起个名字,然后选择表和列,如果是衍生维度,则必须是来自某个维度表,一次可以选择多个列,这些列值都可以从该维度表的主键衍生出来。
3)创建度量
Kylin默认会创建一个Count(1)的度量。可以单击“+Measure”按钮来添加新的度量。Kylin支持的度量有:SUM、MIN、MAX、COUNT、COUNT DISTINCT、TOP_N、RAW等。Kylin可以支持在一个Cube中添加多达上百个的度量。
4)关于Cube数据刷新的设置。在这里可以设置自动合并的阈值、数据保留的最短时间,以及第一个Segment的起点时间(如果Cube有分割时间列的话)
设置Auto Merge Thresholds:合并的阈值可以设置多个层级,当最大阈值不能满足时,尝试下一个稍小的阈值。
设置Volatile Range:如何你不想Kylin自动合并最近某个时间段的Segment,可以设置改属性。
设置Retention Threshold:如果你想只保留最近1年的Segment中的数据,可以设置该值为365。
5)高级设置。在此页面上可以设置聚合组和Rowkey
Kylin默认会把所有维度都放在同一个聚合组中;如果维度数较多(例如>10),那么建议用户根据查询的习惯和模式,单击“New Aggregation Group+”,将维度分为多个聚合组。
聚合组的优化细节还会在本篇文章的后续讲解。
在HBase中Key的存储方式?
Kylin以Key-Value的方式将Cube存储到HBase中。HBase的key,也就是Rowkey,是由各维度的值拼接而成的;为了更高效地存储这些值,Kylin会对它们进行编码和压缩;每个维度均可以选择合适的编码(Encoding)方式,默认采用的是字典(Dictionary)编码技术;除了字典以外,还有整数(Int)和固定长度(Fixed Length)的编码等。
字典编码是将此维度下的所有值构建成一个从string到int的映射表;Kylin会将字典序列化保存,在Cube中存储int值,从而大大减小存储的大小。
字典编码的优势是产生的编码非常紧凑,尤其在维度值的基数较小且长度较大的情况下,特别节约空间。由于产生的字典是在查询时加载入构建引擎和查询引擎的,所以在维度的基数大、长度也大的情况下,容易造成构建引擎或查询引擎的内存溢出。
Kylin支持的编码方式还有以下几种:
各维度在Rowkeys中的顺序如何设置?
各维度在Rowkeys中的顺序,对于查询的性能会产生较明显的影响。通常建议将 mandantory 维度放在开头, 然后是在过滤 ( where 条件)中起到很大作用的维度;如果多个列都会被用于过滤,将高基数的维度(如 user_id)放在低基数的维度(如 age)的前面。这样做的好处是,充分利用过滤条件来缩小在HBase中扫描的范围,从而提高查询的效率。
其余需要主要的设置?
6)高级设置
为Cube配置参数。和其他Hadoop工具一样,Kylin使用了很多配置参数以提高灵活性,用户可以根据具体的环境、场景等配置不同的参数进行调优。
单击“+Property”按钮,然后输入参数名和参数值,如指定kylin.hbase.region.cut=2
,这样此Cube在存储的时候,Kylin将会为每个HTable Region分配2GB来创建一个HTable Region。当Segment中一些Cuboid的大小总和超出一定的阈值时,系统会将这些Cuboid的数据分片到多个分区中以实现Cuboid数据读取的并行化,从而优化Cube的查询速度。
类型的配置还有:kylin.hbase.region.count.min
和kylin.hbase.region.count.max
决定每个Segment最少或最多被划分成多少个分区。
最后再单击“Save”按钮进行保存,一个Cube就设计完成了。
Cube的构建方式通常有两种:全量构建和增量构建;两者的构建步骤是完全一样的,区别只在于构建时读取的数据源是全集还是子集
Cube的构建是如何由任务引擎来调度执行的?
构建N维Cuboid的详细说明
构建N维Cuboid的过程,每一步以前一步的输出作为输入,然后去掉一个维度以聚合得到一个子Cuboid。举个例子,Cuboid ABCD去掉A得到BCD,去掉B得到ACD。
有些Cuboid可以从一个以上的父Cuboid聚合得到,这种情况下,Kylin会选择最小的一个父Cuboid。举例,AB可以从ABC(id:1110)和ABD(id:1101)生成,则ABD会被选中,因为它的比ABC要小。在这基础上,如果D的基数较小,聚合运算的成本就会比较低。所以,当设计rowkey序列的时候,请记得将基数较小的维度放在末尾。这样不仅有利于cube构建,而且有助于cube查询,因为预聚合也遵循相同的规则。
通常来说,从N维到(N/2)维的构建比较慢,因为这是Cuboid数量爆炸性增长的阶段:N维有1个cuboid,(N-1)维有N个cuboid,(N-2)维有N*(N-1)个cuboid,以此类推。经过(N/2)维构建的步骤,整个构建任务会逐渐变快。
一般来说,Cube的膨胀率应该在0%~1000%之间,通常,膨胀率高有以下几个方面的原因:
衍生维度用于在有效维度内将维度表上的非主键维度排除掉,并使用维度表的主键(其实是事实表上相应的外键)来替代它们。Kylin会在底层记录维度表主键与维度表其他维度之间的映射关系,以便在查询时能够动
态地将维度表的主键“翻译”成这些非主键维度,并进行实时聚合。
例如:
原始组合:ABC,AB,AC,BC,A,B,C
当定义B维度从A维度衍生时的组合:AC,A,C
可见从7种组合变成了3种组合。
假设原始的维度表这样定义
A | B | C |
---|---|---|
1 | a | ? |
2 | b | ? |
3 | c | ? |
4 | b | ? |
如果我们想根据B维度来进行查询,如select count(*) from tbl inner join lookup_tbl group by lookup_tbl.B
则Kylin会对该查询进行优化,使其由A维度进行分组。
A | COUNT(*) |
---|---|
1 | 1 |
2 | 1 |
3 | 1 |
4 | 1 |
之后根据B维度从A维度衍生出来的映射关系,将A替换为B,则如下所示
B | COUNT(*) |
---|---|
a | 1 |
b | 1 |
c | 1 |
b | 1 |
最后会对以上结果进行聚合操作,如下所示
B | COUNT(*) |
---|---|
a | 1 |
b | 2 |
c | 1 |
因此,如果从维度表主键到某个维度表维度所需要的聚合工作量非常大,那么定义一个普通的维度可能是一种更好的选择。
聚合组假设一个Cube的所有维度均可以根据业务需求划分成若干组。每个分组的维度集合均是Cube所有维度的一个子集,不同的分组各自拥有一套维度集合,它们可能与其他分组有相同的维度,也可能没有相同的维度。构建引擎会保证每一个Cuboid无论在多少个分组中出现,它都只会被物化一次。
通过使用多个聚合组,可以大大降低Cube中的Cuboid数量。下面来举例说明,如果一个Cube有(M+N)个维度,那么默认它会有2^(m + n) 个 Cuboid;如果把这些维度分为两个不相交的聚合组,那么Cuboid的数量将被减少为2^m + 2^n。
在单个聚合组中,可以对维度设置高级属性,如Mandatory、Hierarchy、Joint等。这几种属性都是为优化Cube的计算而设计的。
Mandatory 必要维度,总是出现的维度。指的是那些总是会出现在Where条件或Group By语句里的维度;通过将某个维度指定为Mandatory,此聚合组产生的所有Cuboid中每一个Cuboid都会包含该维度,Kylin就可以不用预计算那些不包含此维度的Cuboid,从而减少计算量。
Hierarchy 层级维度,例如 “国家” -> “省” -> “市” 是一个层级;不符合此层级关系的 cuboid 可以被跳过计算,例如 [“省”], [“市”]. 定义层级维度时,将父级别维度放在子维度的左边。通过指定Hierarchy,Kylin可以省略不满足此模式的Cuboid。假设一个层级中包含D1,D2…Dn这n个维度,那么在该分组产生的任何Cuboid中,这n个维度只会以(),(D1),(D1,D2)…(D1,D2…Dn)这n+1种形式中的一种出现。
Joint 联合维度,其通常适用于如下两种情形。总是会在一起查询的维度,基数非常接近(有1:1映射关系)。如果某些列形成一个联合,那么在该分组产生的任何Cuboid中,这些联合维度要么一起出现,要么都不出现。
高基数维度使用聚合组控制Cube的膨胀率的思想?
高基数维度的Cuboid在行数和体积上往往非常庞大,这会导致整个Cube的膨胀率变大。如果根据业务需求知道这个高基数的维度只会与若干个维度(而不是所有维度)同时被查询到,那么就可以通过聚合组对这个高基数维度做一定的“隔离”。
把这个高基数的维度放入一个单独的聚合组,再把所有可能会与这个高基数维度一起被查询到的其他维度也放进来。
这样,这个高基数的维度就被“隔离”在一个聚合组中了,所有不会与它一起被查询到的维度都没有和它一起出现在任何一个分组中,因此也就不会有多余的Cuboid产生。这点也大大减少了包含该高基数维度的Cuboid的数量,可以有效地控制Cube的膨胀率。