大数据即席查询-Kylin
一、Kylin 定义
Apache Kylin 是一个开源的分布式分析引擎,提供 Hadoop/Spark 之上的 SQL 查询接口 及多维分析(OLAP)能力以支持超大规模数据,最初由 eBay Inc 开发并贡献至开源社区。 它能在亚秒内查询巨大的 Hive 表
二、Kylin架构图
2.1 REST Server
REST Server 是一套面向应用程序开发的入口点,旨在实现针对 Kylin 平台的应用开发 工作。 此类应用程序可以提供查询、获取结果、触发 cube 构建任务、获取元数据以及获取 用户权限等等。另外可以通过 Restful 接口实现 SQL 查询。
2.2 查询引擎(Query Engine)
当 cube 准备就绪后,查询引擎就能够获取并解析用户查询。它随后会与系统中的其它 组件进行交互,从而向用户返回对应的结果。
2.3 路由器(Routing)
在最初设计时曾考虑过将 Kylin 不能执行的查询引导去 Hive 中继续执行,但在实践后 发现 Hive 与 Kylin 的速度差异过大,导致用户无法对查询的速度有一致的期望,很可能大 多数查询几秒内就返回结果了,而有些查询则要等几分钟到几十分钟,因此体验非常糟糕。 最后这个路由功能在发行版中默认关闭。
2.4 元数据管理工具(Metadata)
Kylin 是一款元数据驱动型应用程序。元数据管理工具是一大关键性组件,用于对保存 在 Kylin 当中的所有元数据进行管理,其中包括最为重要的 cube 元数据。其它全部组件的 正常运作都需以元数据管理工具为基础。 Kylin 的元数据存储在 hbase 中。
2.5 任务引擎(Cube Build Engine)
这套引擎的设计目的在于处理所有离线任务,其中包括 shell 脚本、Java API 以及 Map Reduce 任务等等。任务引擎对 Kylin 当中的全部任务加以管理与协调,从而确保每一项任务 都能得到切实执行并解决其间出现的故障。
三、Kylin特点
Kylin 的主要特点包括支持 SQL 接口、支持超大规模数据集、亚秒级响应、可伸缩性、 高吞吐率、BI 工具集成等。
3.1 标准 SQL 接口
Kylin 是以标准的 SQL 作为对外服务的接口。
3.2 支持超大数据集
Kylin 对于大数据的支撑能力可能是目前所有技术中最为领先的。 早在 2015 年 eBay 的生产环境中就能支持百亿记录的秒级查询,之后在移动的应用场景中又 有了千亿记录秒级查询的案例。
3.3 亚秒级响应
Kylin 拥有优异的查询相应速度,这点得益于预计算,很多复杂的计算, 比如连接、聚合,在离线的预计算过程中就已经完成,这大大降低了查询时刻所需的计算量, 提高了响应速度。
3.4 可伸缩性和高吞吐率
单节点 Kylin 可实现每秒 70 个查询,还可以搭建 Kylin 的集 群。
3.5 BI 工具集成
Kylin 可以与现有的 BI 工具集成,具体包括如下内容
ODBC:与 Tableau、Excel、PowerBI 等工具集成
JDBC:与 Saiku、BIRT 等 Java 工具集成
RestAPI:与 JavaScript、Web 网页集成
Kylin 开发团队还贡献了 Zepplin 的插件,也可以使用 Zepplin 来访问 Kylin 服务
四、Kylin Cube 构建优化
4.1 使用衍生维度(derived dimension)
衍生维度用于在有效维度内将维度表上的非主键维度排除掉,并使用维度表的主键(其 实是事实表上相应的外键)来替代它们。Kylin 会在底层记录维度表主键与维度表其他维度 之间的映射关系,以便在查询时能够动态地将维度表的主键“翻译”成这些非主键维度,并 进行实时聚合
虽然衍生维度具有非常大的吸引力,但这也并不是说所有维度表上的维度都得变成衍生 维度,如果从维度表主键到某个维度表维度所需要的聚合工作量非常大,则不建议使用衍生 维度。
4.2 使用聚合组(Aggregation group)
聚合组(Aggregation Group)是一种强大的剪枝工具。聚合组假设一个 Cube 的所有维 度均可以根据业务需求划分成若干组(当然也可以是一个组),由于同一个组内的维度更可 能同时被同一个查询用到,因此会表现出更加紧密的内在关联。每个分组的维度集合均是 Cube 所有维度的一个子集,不同的分组各自拥有一套维度集合,它们可能与其他分组有相 同的维度,也可能没有相同的维度。每个分组各自独立地根据自身的规则贡献出一批需要被 物化的 Cuboid,所有分组贡献的 Cuboid 的并集就成为了当前 Cube 中所有需要物化的 Cuboid 的集合。不同的分组有可能会贡献出相同的 Cuboid,构建引擎会察觉到这点,并且保证每 一个 Cuboid 无论在多少个分组中出现,它都只会被物化一次。 对于每个分组内部的维度,用户可以使用如下三种可选的方式定义,它们之间的关系, 具体如下
(1)强制维度(Mandatory),如果一个维度被定义为强制维度,那么这个分组产生的所 有 Cuboid 中每一个 Cuboid 都会包含该维度。每个分组中都可以有 0 个、1 个或多个强制维 度。如果根据这个分组的业务逻辑,则相关的查询一定会在过滤条件或分组条件中,因此可 以在该分组中把该维度设置为强制维度。
(2)层级维度(Hierarchy),每个层级包含两个或更多个维度。假设一个层级中包含 D1, D2…Dn 这 n 个维度,那么在该分组产生的任何 Cuboid 中, 这 n 个维度只会以(),(D1), (D1,D2)…(D1,D2…Dn)这 n+1 种形式中的一种出现。每个分组中可以有 0 个、1 个 或多个层级,不同的层级之间不应当有共享的维度。如果根据这个分组的业务逻辑,则多个 维度直接存在层级关系,因此可以在该分组中把这些维度设置为层级维度。
(3)联合维度(Joint),每个联合中包含两个或更多个维度,如果某些列形成一个联合, 那么在该分组产生的任何 Cuboid 中,这些联合维度要么一起出现,要么都不出现。每个分 组中可以有 0 个或多个联合,但是不同的联合之间不应当有共享的维度(否则它们可以合并 成一个联合)。如果根据这个分组的业务逻辑,多个维度在查询中总是同时出现,则可以在该分组中把这些维度设置为联合维度。
聚合组的设计非常灵活,甚至可以用来描述一些极端的设计。假设我们的业务需求非常 单一,只需要某些特定的 Cuboid,那么可以创建多个聚合组,每个聚合组代表一个 Cuboid。 具体的方法是在聚合组中先包含某个 Cuboid 所需的所有维度,然后把这些维度都设置为强 制维度。这样当前的聚合组就只能产生我们想要的那一个 Cuboid 了。
再比如,有的时候我们的 Cube 中有一些基数非常大的维度,如果不做特殊处理,它就 会和其他的维度进行各种组合,从而产生一大堆包含它的 Cuboid。包含高基数维度的 Cuboid 在行数和体积上往往非常庞大,这会导致整个 Cube 的膨胀率变大。如果根据业务需求知道 这个高基数的维度只会与若干个维度(而不是所有维度)同时被查询到,那么就可以通过聚 合组对这个高基数维度做一定的“隔离”。我们把这个高基数的维度放入一个单独的聚合组, 再把所有可能会与这个高基数维度一起被查询到的其他维度也放进来。这样,这个高基数的 维度就被“隔离”在一个聚合组中了,所有不会与它一起被查询到的维度都没有和它一起出现 在任何一个分组中,因此也就不会有多余的 Cuboid 产生。这点也大大减少了包含该高基数 维度的 Cuboid 的数量,可以有效地控制 Cube 的膨胀率
4.3 Row Key 优化
Kylin 会把所有的维度按照顺序组合成一个完整的 Rowkey,并且按照这个 Rowkey 升序 排列 Cuboid 中所有的行。 设计良好的 Rowkey 将更有效地完成数据的查询过滤和定位,减少 IO 次数,提高查询 速度,维度在 rowkey 中的次序,对查询性能有显著的影响。
Row key 的设计原则如下:
(1)被用作过滤的维度放在前边。
(2)基数大的维度放在基数小的维度前边
4.4 并发粒度优化
当 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 最少或最多被划分成多少个分区。
Version:0.9 StartHTML:0000000105 EndHTML:0000005887 StartFragment:0000000141 EndFragment:0000005847 由于每个 Cube 的并发粒度控制不尽相同,因此建议在 Cube Designer 的 Configuration Overwrites(上图所示)中为每个 Cube 量身定制控制并发粒度的参数。假设将把当前 Cube 的 kylin.hbase.region.count.min 设置为 2,kylin.hbase.region.count.max 设置为 100。这样无论 Segment 的大小如何变化,它的分区数量最小都不会低于 2,最大都不会超过 100。相应地, 这个 Segment 背后的存储引擎(HBase)为了存储这个 Segment,也不会使用小于两个或超 过 100 个的分区。我们还调整了默认的 kylin.hbase.region.cut,这样 50GB 的 Segment 基本上 会被分配到 50 个分区,相比默认设置,我们的 Cuboid 可能最多会获得 5 倍的并发量。