文章目录
1. Kylin设计核心思想
业务的发展伴随着事实表的数据体量的增大,查询时间也越来越长,而另一方面,结果集的数据量却没有显著增长,指标的变化也并不频繁。我们希望所有的查询都能在秒级返回查询结果,于是,我们有了一个思路:为什么不能把所有的结果先算好,查询时直接从结果集中取出来呢?这也就是 kylin 设计的初衷。
kylin的核心思想是预计算。
好,现在这个组件已经有了一个准确的定位,那么接下来要解决的问题就是如何实现预计算,这套架子里究竟需要什么组件呢?
2. Kylin基本架构
以下即为 kylin 的插件式架构。
上图中,蓝色的框框是 kylin 系统的边界,里面是 kylin 的核心功能,蓝色框框外面的部分是 Kylin 的外围插件和适配器。
从模块上来看,大致分为以下几部分:
- Data Source Abstraction:数据源抽象层,目前离线的主流实现是 hive,实时的主流实现是 kafka。
- Cube Builder:也就是构建引擎,目前支持 MapReduce 和 Spark。
- Storage Abstraction:结果集存储抽象层,目前主流实现是 hbase,也可以以数据流的方式输出到其他大数据生态工具。
- Metadata:元数据管理,包括 model,cube,job 等元数据的信息,由于 Kylin 的结果表名和 rowkey 都是经过编码的,必须要通过元数据来映射,所以元数据非常关键,若元数据损坏,相对应的结果集也会不可用,表现为表损坏或查询时报错。
- Rest Server:通过 Restful API,JDBC/ODBC 的方式向外界提供接口。
- Query Engine:查询引擎,内置 Apache 开源 SQL 解析工具 Calcite,生成执行计划并执行物理查询计划。
- Routing:路由转换,实时查询情景下,路由指向数据存储层 Hbase,准实时查询情景下,路由指向数据源层 Hive,准实时情景一般是在结果集的数据满足不了查询需求时,Kylin会转而向数据源做查询,也就是所谓的“查询下压”。
从数据流上来看,大致分为以下几部分:
- 红线:离线任务数据流,从数据源 Hive 读取数据,经计算引擎 MapReduce 或 Spark 计算,生成结果集并存储到 Hbase,离线构建任务可以定时,构建任务可通过 Rest API 定时调度。
- 绿色实线:实时查询数据流,从客户端通过 Rest API 或 JDBC/ODBC,经 Rest Server 和 Query Engine 处理,由路由转发至 Hbase,秒级返回查询结果。
- 绿色虚线:准实时查询数据流,查询下压时,通过数据源直接汇总出查询结果,性能视计算复杂程度而定。
接下来,我们就要涉及到 Cube 构建的一些概念啦~~
3. 基本概念
- Cube / Segment / Cuboid :一个查询结果集为一个 Cube; 对于分区表来说,一个日期区间为一个 Segment,一个 Cube 可分为多个 Segment ;每个 Segment 中,有不同维度组合的小结果集,每个小结果集称为一个 Cuboid ,如A维度,B维度,A+B维度,就是3个不同的 Cuboid,同一个Cube 中,不同 Segment 中的 Cuboid 组合和个数都相同。
- 字典:可以把字典理解为维度值向整型数值的映射表,若一个维度值用了字典编码,Hbase 的 rowkey 中就会存储映射后的整数值以提高检索速度,字典内部数据是有序的,方便查找和筛选。字典分为两种:Global Dictionary 和 Segment Dictionary,区别是作用域不同,分别为整个 Cube 或一个 Segment。字典在运算时会加载到内存中,所以,对于一些基数比较高的维度,要慎用字典,防止查询时内存溢出。
那么,万事俱备,我们开始构建 Cube 吧~
4. Cube构建及优化
4.1 Cube构建及优化总览
构建Cube的过程设计可以说是 kylin 系统设计的精华,这7个步骤封装了 kylin 绝大多数的实现细节,呈现出来的是简洁精炼的构建步骤。
上图中,有7个步骤,可以分为三个阶段:前四步为构建阶段,5、6步为优化阶段,最后一步为总览。下面我们一个一个详细说一下。
4.2 Cube构建分步骤说明
4.2.1 Cube Info
在这一步中,我们需要设置的是 Cube 的基本信息,其中包括:
- Model Name:model 名。
- Cube Name: Cube 名,全局唯一,否则会创建失败。
- Notification Email List:发生了 Notification Events 时要通知的邮件列表,逗号分隔。
- Notification Events:发生什么事件时要邮件通知列表中的用户,分为 Success(构建成功)、Error(构建失败)、Discard(构建中止)。
- Description:描述。
4.2.2 Dimensions
设置 Cube 所需的维度,查找表中的维度可以设置为 normal 或 derived,设置为 derived 的维度不会在 rowkey 中出现,而是用其他 normal 维度通过一层映射得到。
4.2.3 Measures
这一步骤主要选择 Cube 中要构建的指标。
所有 Cube 都会计算一个默认的指标 COUNT,即数行数,除此之外,我们还可以定义其他指标:
- SUM:求和。
- MIN:最小值。
- MAX:最大值。
- COUNT:计数。
- COUNT_DISTINCT:去重计数,有精确计算和模糊计算两种。一般来说,计算越精确,构建时资源消耗越多,查询时响应速度越慢;计算越模糊,构建时资源消耗越少,查询时响应速度越快。精确计算的 COUNT_DISTINCT 会存储为 bitmap,模糊计算的 COUNT_DISTINCT 会存储为其他的数据结构(如HyperLogLog),无论是哪种数据结构,都会占用一定的存储空间,若数据量大,占用的空间可能会在M级别,所以,若有多个去重指标,最好把它们存储在不同的列簇中,防止 Hbase 检索速度过慢。
- TOP_N:前 N 位。适用于以下场景:
select dimA, measure
from table_name
where dimB = 'b' and dimC = 'c'
order by measure
limit N;
在不用 TOP_N 的情况下,查询引擎会从 Hbase 中查出所有符合条件的行,并依据其中的某个指标对所有列做排序。例如下面的场景,经过 TOP_N 优化后,Hbase 中数据存储的形式会从这样:
Base Cuboid 的 RowKey | SUM(PRICE) |
---|---|
20120218_00_seller0000001 | 291.58 |
20120218_00_seller0000002 | 365.18 |
20120218_00_seller0000003 | 135.29 |
… | … |
20120218_00_seller1000000 | 272.31 |
20120218_01_seller0000001 | 172.52 |
变成这样:
base cuboid 的 RowKey | Top_N Measure |
---|---|
20120218_00 | sellerOO 10091:1092.21, seller0005002:1090.35,…sellerOOO 1789:891.37 |
20120218_01 | seller0003012:xx.xx |
20120218 02 | seller0004001:xx.xx |
… | … |
20120218 50 | seIler000699: xx.xx |
经 TOP_N 优化后存储结构节省了检索的计算成本,免去了排序的计算成本,可提高查询性能,但带来的问题是列簇的空间占用增加,Cube 的体积会变大。且子 Cuboid 的 TOP_N 指标是从父 TOP_N 的指标聚合出来的,会有一定程度的误差。
7) EXTENDED_COLUMN:引申列。适用于筛选时指定某些 id,但显示时却要显示成 name 时的场景,即把一个维度作为另一个维度的引申列。(后续补充引申列的查询方法和存储结构)
8) PERCENTILE:分位数。查询时该函数的语法与 hive 类似,例如 percentile(measure_a, 0.5),即计算 measure_a 的二分位数,即中位数。
4.2.4 Refresh Setting
这一步骤主要是表的刷新机制和生命周期设置,分为以下几项:
- Auto Merge Thresholds:自动合并 Segments,可以设多级。若不合并,查询引擎需要在对各 Segments 查询后,对结果做一个合并。所以合并 Segment 在一定程度上可以提高查询效率。但是 Segment 是 Cube 构建和刷新的最小单位,合并后就无法对原有的某一个 Segment 单独做刷新操作。
- Volatile Range:最近 N 天的 Segments 不作合并,适用于因数据延迟导致最近 N 天数据会有波动,需要刷新 Segments 的情况。
- Retention Threshold:生命周期,自动删除 N 天以前的 Segment。
- Partition Start Date:起始日期,默认 1970-01-01。
4.3 Cube 构建前优化策略
前四步走完,相当于 Cube 的构建步骤基本完成了,以下的步骤,更确切地说是对 Cube 的优化,以提升构建速度或查询性能。
4.3.1 Advanced Settings
4.3.1.1 Aggregation Groups
Aggregation Groups 直译过来是聚合组。就是指定 Cube 中哪些维度会同时出现,若 Cube 中有两个维度没有同时出现在任何一个聚合组中,Cube 构建时就不会生成同时包含这两个维度的 Cuboid,聚合组是一个强大的剪枝工具。主要包含以下几项:
- Max Dimension Combination:查询时会涉及到的最大维度个数。设置后,多于该维度个数的 Cuboid 将不会被构建。
- Includes:聚合组里包含哪些维度。
- Mandatory Dimensions:在该聚合组中,有哪些维度是查询时必选的。
- Hierarchy Dimensions:哪些维度之间有层级关系,如 国家 -> 省 -> 市。
- Joint Dimensions:哪些维度之间几乎是一对一的。
注意:若某维度被设置为 Mandatory Dimensions,该维度将不允许出现在 Hierarchy Dimensions 或 Joint Dimensions 中。
4.3.1.2 Rowkeys
rowkey 的优化分为以下几个方面:
- Hbase 中 rowkey 的维度顺序。《Apache Kylin 权威指南》中给出了一个维度评分的公式,维度评分越高,越应该放在前排:
维度打分 = 维度出现在过滤条件中的概率 * 用该维度过滤时可以过滤掉的记录数
-
维度编码。维度编码有以下几种:
a) boolean:布尔型。
b) dict:字典型,一般把字符串映射为整型,rowkey 中存储映射后的整型数值,字典在运算时会加载到内存中,若维度基数过高,可能会导致内存溢出。此时应考虑换成其他数据编码。
c) fixed_length:定长,若维度值长度不等,编码时会对其进行截断或补位,适用于电话号码等定长维度。
d) fixed_length_hex:16进制定长编码,用途尚不明确。
e) integer:整型编码。 -
是否按某维度分片。一般来说,数据分片策略可近似地认为是随机的,若设置时按某维度进行分片,可能会提高查询效率,但如果这个分片的维度基数过高,查询时可能会造成 Hbase 过大的压力,甚至会影响到 Kylin 的可用性。此优化项需谨慎使用。
- 官方推荐单台 Hbase 机器的 region 数量在 100 个左右,若超过太多,可能会影响查询性能。
4.3.1.3 Mandatory Cuboids
必须出现的 Cuboid,支持文件导入。
还有一种方式可以指定要构建的 Cuboid,就是把这个 Cuboid 中出现的所有维度放到一个聚合组里,再把这些维度全部设置为 Mandatory Dimensions。
4.3.1.4 Cubing Engine
目前有两种 Cubing Engine 可供选择:MapReduce 和 Spark,前者速度慢,但稳定,后者速度快,但有内存占用过高,导致系统不稳定的风险。
一般来说,若有 COUNT_DISTINCT 指标,我们会优先考虑稳定性,选择 MapReduce,否则我们优先考虑速度,选择 Spark。
4.3.1.5 Column Family
一般来说,COUNT_DISTINCT 指标会单独放到一个列簇里面,其他指标会放到另一个列簇里面。若同时有多个 COUNT_DISTINCT 指标,需要根据每个指标列占用空间的大小,评估是否要把不同的 COUNT_DISTINCT 指标放到不同的列簇当中。
4.3.2 Overview Settings
其他自定义配置项,此处省略。
4.4 Cube 构建后优化策略
4.4.1 Cube 优化判断指标
一个足够优化的 Cube,需要包含常查询的 Cuboid,删除不常查询的 Cuboid,即在满足查询性能的前提下,占用的空间足够小,只物化高频查询的 Cuboid。基于这一点,我们有一个指标,称为膨胀率。
膨胀率 (Expansion Rate) = Cube数据大小 / 源数据大小
膨胀率一般在0 ~ 1000%,若超过这个阈值,这个 Cube 很可能需要优化。例如下面这个 Cube:
膨胀率已超过1000%,所以我们要对其进行优化。
4.4.2 Cube 构建后优化方法
在 Cube 已经构建好以后,又如何进行优化呢?
这时我们就要用到 Cube Planner 这个工具啦,Cube Planner 是内嵌在 Kylin 中的一个剪枝工具,可以统计出每个 Cuboid 的命中次数,进而把各个 Cuboid 做冷热分级,Cube Planner 的统计结果会可视化成下面这张旭日图:
上图中,左边是优化前的 Cube,我们会发现,这个 Cube 之所以膨胀率会超过1000%,是因为它物化了很多从来没被使用过的 Cuboid,右边是推荐的 Cuboid 组合,我们发现 Cube Planner 在优化后会把没被命中过的 Cuboid 删除,优化 Cube 在空间上的占用,还会新增一些 Cuboid,在时间上优化 Cube 的查询性能。
鼠标悬浮在每个 Cuboid 上时,Kylin 还会显示出该 Cuboid 的相关信息:
- Name:各维度是否出现,按 rowkey 排序。
- ID:cuboid ID。
- Query Count:命中数,也包括子 Cuboid 没被物化时,由此 Cuboid 计算得出的查询命中数。
- Exactly Match Count:精确命中数。
- Row Count:行数。
- Rollup Rate:子cuboid行数 / 父cuboid行数。