77

Apache Kylin在去哪儿数据分析中的实践

 6 years ago
source link: http://mp.weixin.qq.com/s/HuvLr0qxhnXcMPBw-2c4wA
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Apache Kylin在去哪儿数据分析中的实践

Original 马万龙 Qunar技术沙龙 2017-12-21 00:16 Posted on

Image

2017年2月加入去哪儿网,目前任职平台事业部大数据研发中心数据开发工程师,主要负责平台商业结算和数据产品研发。敬畏数据,对数据科学充满热情并持续求知。

1. OLAP 业务背景

1.1 实时计算的 OLAP(多维分析)业务场景

作为公司的平台数据部门,我们承担着来自各个业务线 PB 级别的数据计算任务,同时配合市场部门进行商业大数据分析,在此基础上实现数据落地和最优的商业决策。在我们面临的诸多业务场景中,有相当一部分需要支持实时查询,能够在大数据量情况下进行秒级交互式多维分析,所见即所得;能够基于目前十分完善的 Hadoop 生态系统,实时处理数据,支持水平扩展;并且要支持 BI 工具的集成,适用于现有系统的 SQL 和 JDBC 接口。

对于计算量较小的一般实时业务需求,我们原来也做过预计算的尝试,使用 Hive 将所有的业务指标数据计算出来,导入 MySQL 或者 HBase,但是一旦业务逻辑复杂,数据模型将难以定义抽象,导致可扩展性非常低;如果数据计算量太大,预计算将会消耗过多的集群资源,影响集群中其他任务的正常运行,这些因素都使得传统方案的成本大大增加。

1.2 Apache Kylin 方案

目前在开源社区涌现出各种优秀的 SQL on Hadoop,比如 SparkSQL,Phoenix,Impala 等,但是针对实时计算 OLAP 业务背景,大数据 OLAP 最迫切所要解决的问题就是大量实时运算导致的响应时间迟滞。我们使用了 Apache Kylin 开源的分布式分析引擎,它提供了 Hadoop 之上的 SQL 查询接口及多维分析能力以支持超大规模数据,能够支持 TB 到 PB 级别的数据量,使得查询响应性能大幅提高。

我们在使用 Apache Kylin 进行数据开发的过程中,踩了很多坑,同时积累了一些宝贵的经验,本文将这些经验分享出来,若有不正之处,请谅解和批评指正,不胜感激。

2. 关于 Apache Kylin

Apache Kylin 本质上是一个进行大数据预计算的工具,将海量数据按照预先设定的聚合方式进行预计算,存储到 HBase 中等待查询,通过这种方式实现 “实时”,被称为数据分析界的 “神兽”。Apache Kylin 最初由 eBay Inc 开发并于 2014 年 10 月贡献至开源社区,2014 年 11 月加入 Apache 孵化器项目,于 2015 年 11 月正式成为 Apache 顶级项目。值得一提的是,Kylin 也成为首个完全由中国团队设计开发的 Apache 顶级项目。

在 Apache Kylin 出现以前,实时查询的需求就已经存在,一部分研究机构和公司都在 Hadoop 基础上研究自己的分布式实时查询引擎。Apache Kyiln 同样构建在 Hadoop 等分布式计算平台之上,充分利用了 MapReduce 的并行处理能力和可扩展基础设施,高效地处理超大规模数据,可根据数据的规模实现架构的可伸缩。它提供了丰富的数据源支持,可以从 Hive、Kafka 等获取数据,通过 MapReduce 构建 cube(多维立方体),充分利用 HBase 的列式特性存储计算结果;提供标准 SQL 解析与查询优化,以及 ODBC/JDBC 驱动及 REST API 等多个模块。可插拔的灵活架构,允许支持更多的数据源接入 Kylin,也支持采用其它技术作为存储引擎。

Apache 的基本架构和原理

相比于其他 SQL on Hadoop 引擎,Apache Kylin 的特点是具有强大的预计算(离线计算)能力,同时具有很好的快速查询和高并发能力,这取决于 Kylin 的多维分析将预先定义好的度量进行预计算,将计算好的结果保存在 cube,等待查询直接访问。可见 Kylin 的理论基础是空间换时间,将复杂、耗时的在线计算工作事先完成,以便 Kylin 在实际查询过程中有更少的计算量,提供更高的吞吐量。

3. Apache Kylin 方案实施过程和经验

经过三年多的发展,Apache Kylin 日趋成熟稳定,其团队一直保持着对 Kylin 的维护和改进,Apache Jira 上用户提出的关于 Kylin 的 bug 也逐一被修复解决,使得 Kylin 逐步完善,可靠性大大提高。下面结合我们的业务模型,将重点集中在使用 Kylin 进行多维数据计算过程中遇到的问题及解决方案,以及一些关于 cube 的优化思路。

3.1 业务模型抽象

生产场景中 Kylin 是以 Hive 作为 MetaData 的,我们的业务也依赖于 Hive 中的基础数据。通过对原始数据的清洗和转换,按照预先定义好的数据模型载入数据,再根据具体的业务分析需要,进行关联抽取,形成事实表(fact table)。事实表的抽象要结合业务范围,同时也要考虑大数据统计的原则,大数据查询要的一般是统计结果,是多条记录经过聚合计算后的统计值,我们没有必要将每条记录的原始值存入到预计算结果中(除非查询需要)但是聚合是按照维度进行的,因此聚合之前的事实表要容纳所有需要统计的维度来构建 cube,事实表的每条记录也要求包含准确、独立的数据记录。

事实表的抽象直接影响模型(Model)和 Cube 的设计,也影响维度表(Dimension Table)的抽取,同时我们也要考虑事实表维护的成本和复杂度,以便在增量构建 cube 时减低资源消耗,提高 cube 构架的速度。下面是一个从具体业务场景演化来的业务模型,出于业务保密性考虑我们对其中的关键指标做了模糊处理,但仍然能够体现我们在进行业务模型设计时的思路:

在上图展示的业务场景中,围绕用户产生的数据 A 及相应的设备,对应有若干统计指标,为方便后续建模,我们将查询需要的维度集合起来形成一张宽表。Kylin 将根据 MeteData 的定义,将数据以关系表的形式输入,且必须符合星型模型(Star Schema),下方构建引擎从数据源抽取数据构建 cube。对于所有非标准星型的数据模型,都可以通过预处理转化拉平,做成一张宽表来解决。我们只要能够根据业务逻辑把这表关联起来,生成一张宽表 face_A,基于宽表和业务需要设计不同的 cube 做数据聚合。宽表不只能解决数据模型的问题,还能解决维度变化、或者超高基数的维度等问题。

3.2 Cube 降维策略

Cube 的维度数量将直接影响其构建难度和存储空间的压力,根据多维立方体模型,在未采取任何优化措施的情况下直接构建一个多维度的 Cube,所有的维度都将参与组合,对于 N 个维度来说,组合的所有可能性共有 2^N 种,每一种维度的组合都会对度量做聚合运算,最终把运算结果保存为一个物化视图,即 Cuboid,这样将产生数量巨大的 Cuboid。

一个四维 Cube 的例子

假设某个 Cube 有 10 个维度,这 10 个维度全部参与构建且没有任何优化措施,那么 Cube 就会存在 2^10 =1024 个 Cuboid;而如果有 20 个维度,那么 Cube 中总共会存在 2^20 =1048576 个 Cuboid,这个数量级的 Cuboid 将严重影响 Cube 的构建速度,占用大量存储空间,这样的 Cube 是非常劣质的 Cube。如果设计者没有丰富的优化经验或者 Cube 本身没有足够的优化空间,对 Kylin 来说恐怕将是一个灾难,Cuboid 的数量将是一个指数级别的增长,Cube“带病”构建后膨胀率会非常大,导致可用性降低。下图所示的是一个劣质 Cube 的示例,这个 Cube 包含 29 个维度,所有维度都参与构建,且没有采取有效的优化措施,导致膨胀率达到 17W+,极大地影响了 Kylin 的性能,浪费了过多的存储空间。

一个劣质 Cube 示例

Cube 降维优化的最直接方法就是剪枝,将多余的维度从 Cube 中删去,从而减少额外的空间占用。做剪枝优化的原则是,选择性地减去多余的维度,以减少生成不必要的 Cuboid。可以被剪枝的维度,一类是不会参与查询的维度,这一类维度只存在事实表中,但不参与具体的 Cube 构建;另一类是会带来不必要 Cuboid 的维度,有的 Cuboid 因为查询样式的原因永远不会被查询到,因此显得多余;有的 Cuboid 的能力和其他 Cuboid 接近,因此显得多余。但是 Cube 管理员无法提前甄别每一个 Cuboid 是否多余,因此 Kylin 提供了一系列简单的工具来帮助他们完成 Cube 的剪枝优化。

当我们在做降维优化的时候,有可能会陷入一个误区:试图通过组合维度实现维度下降,比如有 3 个维度 A,B 和 C,这三个维度间不存在层级关系,因此不能使用 Hierarchy Dimensions 优化,一个看似可行的方法是将 3 个维度拼接起来,组合成为 1 个维度,通过 substring 或者 like 函数实时查询。从 Cube 降维优化的角度看,维度确实得到下降,Cuboid 的数量也会减少 2^2 个,但是这将给查询带来很大的麻烦。substring 或者 like 查询是一种不精确匹配,Kylin 可能会一路追溯到 Base Cuboid 来回答查询请求,逐个数据扫描以获取目标记录,当数据量超过 HBase 扫描阈值,查询引擎报 org.apache.kylin.gridtable.GTScanSelfTerminatedException 异常而终止查询。理论上所有能通过 Cuboid 处理的查询请求都可以使用 Base Cuboid 来处理,这就像能通过 Base Cuboid 处理的查询同样能够原始 Hive 数据直接得到结果一样,但是多位立方体物化如此多的 Cuboid 的原因在于,构建大量的 Cuboid 使其有各自擅长的查询场景,每个精确匹配的 Cuboid 对所有度量做了最大努力的预先聚合计算,用空间换时间,这正是 Apache Kylin 预计算的精髓所在。当然,如果 cube 优化得当,通过这种方法降维从而牺牲一部分物化 Cuboid 带来的精确匹配所带来的聚合计算开销也没有想象中的那么恐怖,这是一种时间与空间上的平衡。

还有一种有效的降维方法是在满足业务查询需求的基础上,将 Cube 拆分成多个小的 Cube,子 Cube 的维度是原始 Cube 维度的子集,从而实现维度下降。这种方法的前提是维度可分离,拆分得到的多个 Cube 仍能够满足查询需要。

多维 Cube 拆分

3.3 Cube 优化方法

除了 4.2 展示的降维优化策略,我们再介绍 3 种有效的 Cube 优化方法。总的来说,在 Cube 构建之前,一切优化手段都致力于提供更多与数据模型或查询样式相关的信息,用于指导构建出体积更小、查询速度更快的 Cube,具体采用哪种优化方法,也要根据具体业务场景选择。

3.3.1 Hierachy Dimension(层级维度)

层级维度优化适用场景有限但是功能强大。对于一系列具有层次关系的维度组成一个层级维度层次, 比如年、季度、月、日组成了一个层次。 在 Cube 中,如果不设置 Hierarchy Dimension, 经过多维立方体计算处理,会有年、季度、月、日、年 + 季度、年 + 月、年 + 日……等 2^4 个 Cuboid, 但是设置了 Hierarchy Dimension 之后 Cuboid 增加了一个约束,希望层次级别较低的维度一定要伴随层次级别较高的维度一起出现,从而限制了 Cube 的维度组合数,设置了 Hierachy Dimension 能使得需要计算的维度组合减少一半。

层级维度优化示例

3.3.2 Mandatory Dimensions(强制维度)

在我们的查询场景中会有一些维度总是在条件或聚合时出现,通过将这一类维度指定为强制维度,可以减少构建不包含此类维度的 Cuboid,从而减少构建开销和查询计算量。虽然这一类维度很少出现,但是为我们提供了一个有价值的 Cube 优化方向,每使用一个强制维度都会使 Cube 的 Cuboid 数量减少一半。

不生成不包含强制维度 A 的 Cuboid

3.3.3 Joint Dimensions(联合维度)

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

3.4 精确查询的需求

Count Distinct 度量是多维数据分析中做常用的计算方式之一,但是它的计算成本往往是很高的,这其中不仅包含计数的实时聚合计算代价,还包括不同的精度要求对空间占用大小的影响。类似 Count Distinct 这一类度量具有多种可选精度,但是精度越高的度量往往越会存在一定的代价,它意味着更大的占用空间和运行时开销。如果我们的业务不接受模糊精度,而是需要精确统计,那么就要牺牲一些存储空间来构建高精度的 Cube;另一方面,如果我们的业务对精度要求不高,也可以通过降低度量精度的方法对 cube 进行优化。

Apache Kylin 使用 HyperLogLog 算法实现了近似 Count Distinct,提供了错误率从 9.75% 到 1.22% 几种精度供选择;算法计算后的 Count Distinct 指标,理论上,结果最大只有 64KB,最低的错误率是 1.22%;这种实现方式用在需要快速计算、节省存储空间,并且能接受错误率的 Count Distinct 指标计算。

Count Distinct 精度和占用空间列表

需要注意的是,从 1.5.3 版本开始,Kylin 中实现了基于 bitmap 的精确 Count Distinct 计算方式。当数据类型为 tiny int(byte)、small int(short)以及 int,会直接将数据值映射到 bitmap 中;当数据类型为 long,string 或者其他,则需要将数据值以字符串形式编码成 dict(字典),再将字典 ID 映射到 bitmap,因此需要在 advanced setting 中添加全局字典(Global Dictionary),否则如果基数过大,在构建过程中会报 Too high cardinality is not suitable for dictionary 错误。

指标计算后的结果,并不是计数后的值,而是包含了序列化值的 bitmap。这样,才能确保在任意维度上的 Count Distinct 结果是正确的。这种实现方式提供了精确的无错误的 Count Distinct 结果,但是需要更多的存储资源,如果数据中的不重复值超过百万,结果所占的存储应该会达到几百 MB。

Image
Image

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK