Kyligence AI 服务 - 让大模型完成准确、可靠的数值计算和回答! 立即了解更多

Apache Kylin 在 58 集团的实践与优化

Author
杨正
58 同城大数据平台工程师,负责 58 海量数据实时存储及离线分析平台建设,基于HBase、Kylin等基础组件为集团各业务线和子公司提供海量数据存储、离线分析等工作。
2021年 2月 20日

查询响应时间 P90 0.5s,700 个 Cube,122 个 Project,16000 多个 Segment,单副本的存储 500T,日查询量 20w,日输入量 200 亿。从 16 年至今,58 集团已使用 Apache Kylin 近五年,目前 20 多个业务线和子公司都在使用 Kylin。同时,58 集团也持续对 Kylin 进行了一系列优化,并贡献到了社区,让更多 Kylin 用户从中受益。在 1 月 30 日结束的 Kylin Meetup 中,我们邀请到了来自 58 集团大数据平台的杨正,跟大家分享 Kylin 在 58 同城的实践与优化。

演讲大纲
  • 58 集团 Kylin 平台介绍
  • Kylin 版本统一、跨集群读写、Cube 治理等实践
  • Kylin 性能与易用性优化
  • 未来规划

以下为会议实录。

58 集团 Kylin 平台介绍

首先,介绍 58 集团的 Kylin 平台,Kylin 有很多很好的特性,这里列举了 5 点:

  • 亚秒级的响应
  • 标准 SQL 的支持
  • 与BI工具无缝整合
  • 高吞吐支持千亿级数据规模
  • 界面友好,使用简单

凭借这些特性,Kylin 在 58 集团得到了广泛的应用,目前在 58 集团服务于 20 多个业务线和子公司中,广泛应用于 BI 报表,用户行为分析、推荐、商业数据产品等场景中。

上图为 58 集团 Kylin 平台的架构,最底下是存储层,使用了 HDFS 和 HBase 作为 Cube 的存储;倒数第二层是计算层,同时支持使用 MR 和 Spark 进行构建 Cube;中间是 Kylin 的服务,包括 Kylin 的 Job 服务、查询服务、Web 和 Rest 服务,以及元数据的管理中心;再上面是运营监控,我们有一个 Kylin 的运营平台,包括 Kylin 的工单,Kylin 的任务、查询和资源的统计指标,以及 Cube 的治理功能。在监控中心包含 Kylin 的进程监控、查询监控以及任务监控。同时,我们也提供了 Kylin 的任务调度功能。最上面是 Kylin 的应用,应用于 BI 报表、推荐、行为分析、商业数据产品以及其他的场景中。

Kylin 是在 2015 年的 11 月份正式成为 Apache 顶级项目,在 16 年 7 月份正式发布了 Kylin 1.5.3,在 58 集团是 16 年 8 月份正式上线了 Kylin 1.5.3,在 19 年 3 月份又上线了 Kylin 2.6.0,在 2020 年的 5 月份我们将 1.5 中的所有 Cube 迁移到 2.6.0,至此我们版本统一为 2.6.0 版本,现在我们正在调研和测试 Kylin 4.0。

这是我们的平台现状,目前一共有 700 个 Cube,122 个 Project,16000 多个 Segment,单副本的存储有 500T,日查询量有 20w,平均响应时间是 0.4s,P90 和 P99 分别是 0.5s 和 4s,日输入量有 200 亿。

Kylin 在 58 集团的实践与优化

下面介绍我们的一些实践和优化经验。

1)版本统一实践

首先介绍的是 Kylin 的版本统一实践,背景是在 58 集团有多个 Kylin 集群,并且是不同版本,维护成本比较高。并且 Kylin 1.5 的版本比较老,很多特性不支持,它不支持使用 Spark 构建,不支持雪花模型,也不支持 Cube Planner 自动剪枝等高级特性,Kylin 1.5 的整体性能也不如 Kylin 2.6,因此我们决定将 Kylin 1.5 中的所有 Cube 迁移到 Kylin 2.6 中。

我们的方案是将 1.5 上的所有 Cube 元数据迁移到 2.6 集群上,然后在 2.6 集群上重新构建历史数据。迁移步骤第一步是元数据的迁移,Kylin 提供了元数据的迁移工具,但是 Kylin 1.5 和 Kylin 2.6 的元数据是不兼容的,我们的做法是将 Kylin 1.5 的元数据先克隆到本地,我们开发了元数据的适配工具,将它适配成 Kylin 2.6 可以识别的元数据文件,然后再 put 到 Kylin 2.6 集群。当元数据全部迁移到 2.6 集群上以后,我们会基于这些元数据去对 Cube 做一些优化,常用的一些优化手段包括使用 Spark 构建引擎、维度顺序、维度编码、分片、聚合组、TTL、合并阈值、并发粒度等配置的调整。

Cube 优化后,我们开始构建 Cube,这里分为历史数据的构建和增量数据的构建。历史数据我们会尽可能的构建时间跨度比较大的 Segment,这样的话相当于我们对原来的 Cube 做了合并操作。构建完之后再对比同一个 Cube 在两个集群中它们的 Segment 的日期,以及我们会做一些查询的验证,保证查询数据一致性,再让用户切换 Kylin 的域名,运行一段时间没有问题后,再清理 Kylin 1.5 中的数据,通过以上的迁移流程,我们花了一个月的时间将 Kylin 1.5 上 400 多个 Cube 成功迁移到了 Kylin 2.6 集群上。

2)跨集群存储与查询实践

接下来介绍的是 Kylin 跨集群存储与查询实践,背景是 Kylin 所使用的 HBase 集群所在的机房的机架数达到了物理上限,没有办法再扩容。

方案是我们有多套 HBase 集群,并且是在不同的机房的,我们想同时去使用这多套 HBase 集群,去作为一个 Kylin 集群的 Cube 存储。上图是我们的一个 Cube 的两个 Segment 元数据展示,可以看到它们存储在不同的 HBase 集群。

下面要介绍的是 Kylin 的跨集群的存储原理。介绍跨集群存储原理之前我先简单介绍一下 Cube 的构建和合并流程。

Cube 的构建流程如下:

  1. 抽取数据,从 Hive 或者是 Kafka 中抽取数据到一张临时表中;
  2. 构建字典,这里包含维度字典和全局字典;
  3. 创建 HBase 表;
  4. 构建 Cuboids,Cuboids 就是各个维度组合下的聚合值这样的数据,然后再转化成 HBase 的底层文件,HFile 文件;
  5. 最后通过 BulkLoad 程序将 HFile 文件导入到 HBase 中。

以上就是 Cube 的构建流程。Cube 的合并是基于之前Cube 构建的时候保存下来的字典文件和 Cuboid 数据,首先对字典进行合并,再创建 HBase 表,再合并 Cuboids 数据,后面的流程和 Cube 构建的流程一致,这就是 Cube 合并的流程。

在了解 Cube 构建和合并的流程之后,我们罗列了整个流程中所有与 HBase 连接的步骤:

  • 创建 HBase 表;
  • Cuboids 数据转 HFile 文件;
  • BulkLoad 任务;
  • Lookup 表的存储与查询;
  • Merge 任务垃圾清理时,删除无用的 HTable;
  • 获取 HBase 集群的 HDFS 存储路径

我们要实现 Kylin 的跨集群存储,就是在这些步骤执行之前去获取当前构建的 Cube 的 HBase 存储配置,根据获取到的配置去选择对应的 HBase 集群创建连接,再进行相应的操作,比如创建 HBase 表,执行 BulkLoad 任务,这样的话就实现了跨 HBase 集群的存储。

我们实现的跨集群存储支持 Cube、Project 以及全局的配置,也就是说我们可以配置不同的 Cube 可以存储在不同的集群,也可以配置不同的 Project 下所有的 Cube 存储在不同的集群,也可以有一个全局的配置,配置比较灵活,其原理是 Cube 的配置优先级会大于 Project 的配置,Project 的配置优先级大于系统配置。

下面介绍 Kylin 的跨集群查询原理,先简单介绍一下 Kylin 的查询流程,首先第一步是使用 Calcite 对 SQL 进行解析,解析成抽象语法树,再将抽象语法树转换成逻辑计划,再对逻辑计划进行优化,然后是 Calcite 代码生成和编译,第五步是封装 HBase 的 Scan 请求,一个 Kylin 的查询可能是跨 Segment的,会去查 HBase 的多张表,这样一个查询可能会发送多个 Scan 请求去请求不同的表,当 RegionServer 接收到 Scan 请求之后,会使用协处理器去扫描数据、过滤和聚合数据,处理完成之后再返回给 Kylin,Kylin 接收到数据之后会对这些数据进行解码,再提交给 Calcite 去做进一步的迭代计算,这就是 Kylin 的整个查询流程。

我们要实现跨 HBase 集群的查询,就是在封装 HBase Scan 请求的时候,在向对应的 Segment 去发送请求之前,会去从元数据中获取 Segment 所属的 HBase 集群,然后与对应的 HBase 集群去创建 Connection,再发送请求,多个 Scan 请求发送到多个 HBase 集群。多个 HBase 集群处理完成后,再将结果统一的返回给 Kylin,Kylin 再做后续的处理,从而实现 Kylin 的跨集群查询。

3)查询管控与诊断优化

这里要介绍的是我们在 Kylin 的查询管控以及诊断上面的一些优化。我们第一步是对查询链路进行了阶段的划分,我们一共划分成了 5 个阶段。

  1. Calcite 的处理阶段,这里包含了前面说的 Calcite 的解析转化与优化等步骤,我们统一成了一个 Calcite 的处理阶段;
  2. 封装 HBase Scan 请求的阶段;
  3. 启动多个线程去发送请求的阶段;
  4. 在 RegionServer 上去扫描数据、过滤数据、聚合数据阶段;
  5. Kylin 节点对 HBase 返回的结果进行二次聚合以及合并排序阶段。

我们会把各个阶段的耗时情况给保存下来,如果后续用户反馈查询比较慢,我们就可以快速地诊断出是在哪一个阶段出现了问题,可以快速地定位性能问题。

我们做了以下工作:

第一个是完善了查询信息,这里不仅包括刚才所说的各个阶段的耗时情况,我们还完善了一些其他信息,比如说 Kylin 默认只保存了 SQL 所击中的 Cuboid id 信息,我们是把用户的 SQL 所对应的 Cuboid id 也保存下来,诊断时会将这个信息和它所击中的 Cuboid 做一个对比。同时我们把这个 id 由十进制转换成二进制,这样的话我们可以清楚的看到用户查询了哪些维度,以及这些维度在 Rowkey 中的顺序。

第二个是我们对慢查询进行了收集,我们会把慢查询信息保持在 HBase 表中,并且展示在 Kylin 的日志、慢查询页面以及 Kylin 的日报中。Kylin 默认的慢查询收集是通过巡检的方式实现的,这样会存在一些不确定性因素,导致有一些查询不会被收集,我们改造了这一块逻辑,改成当一个查询达到了我们配置的延迟阈值之后,会主动的进行处理,比如我们配置了当一个查询的延迟大于 10 秒之后会收集这个慢查询,大于 60 秒之后会去中断这个查询。

上图是 Kylin 的慢查询页面,我们完善了一些信息,比如击中的 Cube 名称,Cuboid 信息,以及各个阶段的查询耗时。通过完善这些信息,我们提高了慢查询的诊断效率,便于进行查询的治理,提高查询的性能。

我们对查询做了一些限制,Kylin 提供了一些查询限制的参数,重点提一下前面两个参数,因为他们的默认值是 0,也就是没有限制,这样当一个用户他有一个不合理的查询请求,可能会扫 HBase 全表,这样是一个很危险的操作,很容易将的 HBase 节点或者是 Kylin 节点打挂。通过以上的这些参数配置,可以有效的避免用户的不合理 Cube 设计或者是查询导致集群的性能和稳定性受影响的现象。

接下来分享我们在 Kylin 查询监控上做的一些工作,我们将 Kylin 的各个节点内存中的 Query Metrics 给暴露到 HTTP API 中,这个方案我们也贡献给了社区,再通过 tcollect 程序将JMX中的数据实时写入到 OpenTSDB 中,OpenTSDB 也是一个基于 HBase 的高性能高吞吐的时序数据库,然后在 Grafana 上进行配置各种监控指标。

这幅图就是我们的监控页面,包含对各个 Kylin 节点和 Cube 的监控。包括各个节点、各个 Cube 的 P99、P95 等信息,以及各个 Cube Scan 的耗时和数据量等监控。

4)构建性能优化

接下里介绍一下我们对构建性能上的一些优化。首先我们优先让用户使用 Spark 构建引擎, Kylin 常用的构建算法是层级构建算法,如果使用 MR 进行构建的话,每一层都会去创建一个 MR 任务,MR 之间数据需要多次落地和读取,如果使用 Spark 进行构建的话,RDD 之间的转换都是在内存中进行的,因此构建速度可以大大提升,右边这幅图是我们做一些测试,可以发现使用 Spark 进行构建,构建的性能提升了约 25%。

同时,我们对全局字典也进行了一些优化,首先介绍一下为什么需要全局字典,Kylin 同时支持精确去重和近似去重,Kylin 的精确去重是基于 Bitmap 实现的,而 Bitmap 只能接受 Int 型参数,要想对字符串型数据进行精确去重,就要借助全局字典来对字符串映射成 Int 型的值,这样就可以对字符串类型的数据进行精确去重。

全局字典在 Kylin 中有两种实现方案,第一种是基于 Trie 树的实现方案,第二种是基于 Hive 表的全局字典实现方案。

  • 在构建方式上,基于 Trie 树的全局字典,只能在单一节点上进行构建,多个全局字典之间只能串行构建,而基于 Hive 表的全局字典可以多节点的并行构建;
  • 在弹性方面,如果开启 UHC(超高基数列),基于 Trie 树的全局字典的多个字典可以通过 MR 进行并行构建,但是每个全局字典只能分配到一个 Reduce 中进行构建,基于Hive表的全局字典可以通过增加资源提升性能;
  • 在资源使用上,使用 Trie 树相对节省资源,但是会占用的是 Kylin 的实例资源,而 Kylin 的实例资源是比较稀缺的。基于 Hive 表的实现方案,会执行多个 MR 和 Hive SQL,中间会有包含多次 Shuffle 阶段,资源开销比较大,但是他不占用 Kylin 的资源。
  • 在瓶颈上,基于 Trie 树的全局字典在遇到高基数和多个去重列时,内存很容易成为瓶颈,基于 Hive 表的全局字典在性能上在某些情况下会遇到数据倾斜的情况,导致整体构建性能比较慢。

基于 Trie 树的全局字典是在 Kylin 1.5.3 上发布的,Hive 全局字典是在 Kylin 3.1.0 上发布的,经过对比之后发现基于 Hive 的全局字典有明显的优势,于是我们就将 Kylin 3.1.0 这个特性合入到内部的 2.6.0 这个版本上。顺便提一下 Kylin 4.0 拥有一个新的全局字典实现方案,这个等我们后面 4.0 落地后再去做一些对比。

基于 Trie 树的全局字典的优化手段如下:

  • 构建时切分小字典
  • 开启 UHC,增加 Reduce 内存
  • 考虑 Segment 级别全局字典
  • 复用全局字典
  • 拆分 Cube

基于以上优化手段,Trie 树全局字典可以满足我们集团大部分的场景,但是在有些场景中使用 Trie 树构建的性能会非常慢,或者是会出现 OOM,导致无法构建,这个时候我们会推荐用户去使用 Hive 全局字典,Hive 全局字典也有一些优化手段,比如说使用全局字典去跨 Cube 复用字典,这样可以避免有一些资源的浪费,还有使用 Map join 去解决数据倾斜的问题;还可以增加资源,提升并发力度。

上图是我们对比了两种全局字典进行构建的时间消耗,在两次测试案例中使用 Hive 全局字典相比 Trie 树全局字典,Cube 的构建效率提升了大约 40%。

5)任务调度

接下来介绍 Kylin 任务调度的功能,Kylin 本身没有提供任务调度功能,只提供了构建相关的 RESTful API,我们最早期的方案是基于 Crontab+Shell 脚本去实现的,这样会有很多弊端,比如任务很难管理,第二个是 Shell 脚本维护比较困难,后来我们基于集团内部的调度系统实现的 Kylin 的任务调度。

左边这幅图是添加任务调度的工单,用户提交工单,管理员审批之后就可以创建这个任务,任务可以进行可视化的管理,不仅有基于时间的调度,还可以配置依赖任务,这样可以第一时间产出报表,还一些重试和报警机制,保证了任务调度的稳定可靠性。

6)Cube 治理

下面要介绍的是 Cube 治理功能,我们可以快速定位到一些不合理的 Cube,我们可以通过以上这些过滤条件,比如上线状态,未构建天数,未查询天数,Cube 存储大小,查询延迟以及膨胀率等,去快速的筛选出不合理的 Cube,然后再进行相应的治理。我们会对每个 Cube 的所有 Segment 进行状态评估,会判断 Cube 的 Segment 是否有空洞,是否有空的 Segment,Segment 的大小评估,以及合并阈值的检查,综合以上这些因素,会对每个 Cube 进行状态评分,然后给出相应的治理建议。

7)多租户优化

接下来,介绍的是我们对 Kylin 多租户的优化,背景是 Kylin 只能使用默认用户去使用存储和计算资源,在多业务支持的场景下会存在不能有效的进行资源隔离和成本核算的问题。

我们的方案基于 HBase 的 UGI 和 Hadoop 的代理去实现 Kylin 在 58 集团的多租户打通。下面这幅图可以看出不同的用户在 HDFS、YARN 和 HBase 中都有相应的隔离:在 HDFS 中不同的用户会有不同的目录以及相应的权限进行隔离;在 YARN 中不同的用户会对应一个资源队列进行计算隔离;对于 HBase 每个用户会有对应的 NameSpace 进行权限隔离,并且每个用户会对应一个 RsGroup 进行物理隔离,并且 HDSF,YARN 和 HBase 都有一套 Quota 的机制,这样方便我们对各个用户进行资源限制和成本核算。

最后要介绍的是我们在 HBase 上的优化,首先第一个是使用 RSGroup 进行物理隔离,这样可以保障一些重点业务的查询性能;第二个是我们会定时的清理 Kylin 的垃圾数据,合并小的 Segment,可以减少一些小表的数量;第三个是我们开启了短路读,在 HBase 表本地率比较高的情况下,可以有效的减少网络 IO,第四个开启了 Hedged Read 特性,可以有效的降低读毛刺,第五个是我们关闭了所有 Kylin Scan 的BlockCache,第六个是我们将 BulkLoad HFile 的 copy 模式改为 move 模式,减少一些不必要的磁盘 IO,最后一个是我们对 HBase 做了读写分离,避免读写资源的抢占与饥饿。

这是我们的一些其他优化,也贡献给了 Kylin 社区。

Kylin 未来愿景

最后介绍一下我们对未来的展望。

首先是 Kylin 4.0 的落地,我们目前正在基于 Kylin 4.0.0-alpha 版本进行调研和测试。

Kylin 4.0 简化了 Kylin 的架构,使用 Spark 去构建和查询,并且完全去除了 HBase,使用 Parquet 进行存储,更加轻量化,由于是列式存储,在查询和存储占用上都比较友好。除此之外,是 Schema 的动态更新,现在给 Cube 添加或删除维度都是需要去回刷数据,这样代价会比较大,所以这个特性我们比较期待。

第二个方向是自助治理,目前的 Cube 治理还是需要一些人为的参与,未来我们希望可以完全的自动处理。第三个是 Kylin 4.0 这个架构非常适合上云,未来我们也会考虑上云。最后一个是我们目前使用的版本是 Kylin 2.6.0,还不支持实时 OLAP 功能,未来我们希望在 Kylin 4.1 发布后支持 Kylin 的实时 OLAP。

更多 Apache Kylin 最新动态以及案例分享,请扫描二维码,关注 Apache Kylin 公众号。

添加企微

kyligence
关注我们

kyligence