Kylin 作为汽车之家的核心 OLAP 引擎,服务于多个业务线与商业数据产品,应用于流量、线索、用户行为、推荐效果等方面的数据分析场景。目前已有 500+ 个 Kylin Cube,存储约 300 T,整体 Segment 数约1.6 万;单个 Cube 原始数据过万亿,单个 Cube 最多 31 个维度;12万 HBase region,查询响应时间 TP 95 稳定在 2 秒以内。
本文导读
1. Kylin 在汽车之家的发展历程及现状:
Kylin 简介、架构与原理
使用现状
发展历程
2. Kylin 在商业化数据产品中的应用与实践:
业务场景
技术选型(Kylin vs Druid vs ES)
战略级数据产品-车智云
开发流程
Kylin 的常规优化经验(Cuboid 剪枝、查询性能、最大维度组合、超大字典问题、精确去重....)
其他实践
KylinSide系统-集群信息统计、集群管理
3. 集群升级和迁移的一些经验分享:
背景与挑战
整体方案
整体架构
4. 未来的规划:
实时 OLAP
云原生
01
Kylin 在汽车之家的发展历程及现状
1. Kylin 简介
Apache Kylin 是一个可扩展的超快的大数据分析型数据仓库,它有友好的 web 界面,有交互式查询能力,性能非常好,还有标准的 SQL 接口,支持 JDBC 查询。Kylin 的原理是基于预计算模型,是多维立方体的模式,在 Kylin 3.0 之后已经支持实时 OLAP 了,同时 Kylin 可以和现有的 BI 工具无缝结合。
2. Kylin 架构
这是 Kylin 官网的一张图,简单给大家说一下,左边是数据源,可以是 Hive 表,是可以离线的数据,也可以是 Kafka 里面的一些实时数据,还可以是普通关系型数据库里面的数据,中间这一块其实是核心的内容,最上层其实就是对外暴露的一些 REST 接口,REST API,除了 WEB 界面,还有 JDBC/ODBC 这些查询也都是访问的 REST 接口。再往下就是一个查询引擎,Kylin 其实是基于 Apache Calcite 来实现的 SQL 解析,包括生成执行计划。再往下是路由层,经过 SQL 解析后我们可以知道查询到的哪个表,用到了哪几个维度,用到哪些度量,根据这些信息就可以路由到提前构建好的 Cube 上,理想情况会精确命中到某一个维度组合上,来实现高效的查询。
元数据这一块包括 Hive 表的元数据和 Kylin 的元数据,Kylin 的元数据会包括 Project、Model、Cube、Segment 多种元数据信息,大多都会在内存中维护缓存。最下层其实是一个构建引擎,主要也是分两块,一块离线的,一块实时的,最终都会生成 HBase 表,存储在 HBase 里面,Kylin 大概的架构就是这样的。
3. Cube 预计算原理
Cube 本身的中文含义是立方体,其实右边这个图就是很形象的说明了 Cube 的含义,假如说我们有一张表,这个表里面有 ABCD 四个维度,所有的维度组合就构建出了这么一个立方体,就是 Cube,然后每一种维度组合在 Kylin 的概念里面就是 Cuboid,也就是对应图里面的一个点,ABCD 其实是一个最基础的维度组合,也就是 BaseCuboid,是我们可以预聚合的最细粒度,然后下面包括 ABC、ABA 等这些更粗粒度的维度组合,其实都可以基于 ABCD 的预计算结果再去构建出来。
我们举一个简单的例子,刚才说的这个表里面有 ABCD 四个维度,假设A字段有值度是 A1,B 字段有 B1 和 B2,C 字段和 D 字段分别对应 C1 和 D1,这个时候就引入一个基数的概念,就是维度 A 它其实只有一个值,就是 A1,我们说他的基数就为 1,维度B因为有 B1 和 B2 两个值,所以他的基数是 2。左边下面这两个表格对应的是字典,Kylin 为了加速查询,节省存储空间,它会对每个维度上的值去做编码,编码是从 0 开始,所以 A1 对应的编码为 0,维度 B 的值是 B1 和 B2,对应的编码就是 0 和 1。
我们来看一下最终的数据存储到 HBase 里面是什么样的,RowKey 中用绿色标注出来的四位数字,其实代表了四个维度,比如说黄色这一行数据是 1000,它其实代表的是 A 这个 Cuboid,也就是说维度为 A,这个组合里面只有 A 的这种情况。RowKey 首先是由四位数字标记出对应的 Cuboid 是哪一个,再后面这个 0 其实是维度 A 对应的值,这个例子里 A 只有一个值 A1(对应的字典编码为0),所以最终只在 HBase 里对应一行数据。
最后说下 Value,我们这个例子举的是 Count 的例子,这个表里面 A1 对应三条数据,所以 1000 这个 Cuboid 对应的 Count Value 就是3。
再看一下下面对应 AB 的这个 Cuboid,AB 这个组合,前两位都是 1,后两位 CD 是不包含的,所以是 0。因为 A 对应的只有 A1,B 对应 B1、B2,所以他们构建出来的值只有两行记录,也就是 00 和 01,对应的 Count 的值就是 2 和 1。以上就是 Kylin 预计算的基本原理,这里的 RowKey 是一个示意,和最终的 RowKey 会有些差别。
4. Kylin 的使用现状
Kylin 作为汽车之家的核心 OLAP 引擎,服务于多个业务线,支持多个商业数据产品,比如后面我们介绍的战略级商业数据产品-车智云,就是主要基于 Kylin 来建设的。
Kylin 在汽车之家支持包括流量、线索、用户行为、推荐效果等方面的数据分析场景。我们有 500 多个 Cube ,存储在 300 T 左右,整体 Segment 数在 1.6 万;单个 Cube 原始数据过万亿,单个 Cube 最多 31 个维度;12 万 HBase region,查询响应时间 TP 95 稳定在 2 秒以内。
5. Kylin 在汽车之家的发展历程
2016 年:我们开始调研 Kylin,当时是 1.5.4 的版本,最初只是组内使用,支持一些统计场景。
2017 年:车智云项目发起,此时我们借助这个商业数据产品的契机,深度地使用了 Kylin,同时也升级到 1.6,这个阶段 Cube 的规模在 100 多个,我们主要支持车智云项目的需求,并在Kylin的外围加了一些额外的监控和自动拉起的服务。
2018 年:主要是帮助业务团队去优化模型,以及提升整体的Kylin的稳定性,这个时候 Cube 已经在 200 多个了,已经支持多个业务线了,同时部署了 3 个集群。因为支持了商业数据产品,对稳定性及可用性要求很高,所以增加了HBase灾备的能力。
2019 年:我们升级到 2.6.3 这个版本,主要做了些集群升级和机房迁移的工作。升级之后,很多 Cube 切换到 Spark 构建引擎上。同时之家内部有了自己的 BI 产品——AutoBI,也在逐步和 Kylin 做对接,使 Kylin 的应用场景更加丰富。
02
Kylin 在商业化数据产品中的应用与实践
1. 业务及场景特点
汽车之家最就是以内容为主的,汽车之家的内容通常是全网是最早发布的,现在还融入了大量的自媒体以及小视频等板块。流量这一块是大家最关注的,主要是提升运营这一块,还有大量的用户行为数据和销售线索数据,大体上是这几类数据。
数据规模是千亿级,稳定性是我们很看重的一点,响应时间是秒级或者是亚秒级,数据主要以离线为主,还需要支持高并发。
2. 对比及选型
当时选型时,我们对比了 Kylin、Druid 还有 ES(Elasticsearch)。
性能:因为 Kylin 和 Druid 都是基于预计算的,所以他们性能会比较好。
支持数据量级:因为 Kylin 和 Druid 都是预计算,所以数据量级这一块是没有多大影响的,就算数据量级到万亿级也不需要去线性的扩展服务器,这一点是比较大的优势,ES 就是需要做线性扩展的。
稳定性:这几个都是比较成熟的开源产品,稳定性都是没有问题的。
高并发:高并发其实是相对来说的,在 OLAP 场景里面应该还没有真正意义上的高并发,Kylin 是基于预计算的,相对来说并发支持比较好。
SQL:因为 Kylin 原生就是支持 SQL 的,Druid 和 ES 早期都是不支持的。
易用性:Kylin 是有明显优势的,Kylin 有很友好的 Web 界面的,用户可以在界面上去做建模和运维,同时权限这一块原生就支持了。
明细查询:Kylin 是不支持的,是比较弱的一块。
经过试用和对比,我们最终选择了 Kylin,能满足我们大部分的场景。
3. 战略级数据产品-车智云
车智云的定位主要是推动车企的战略、营销、研发等全价值链的升级。汽车之家有海量的用户行为数据和态度数据,我们最开始是论坛起家的,这上面有用户针对每一个车型正面或者是负面的真实评价。数据规模占线上汽车媒体 73%,这是一个很大的优势,还融入了媒体、金融、电商、生活多维度的数据。我们首创了一个 UVN 的用户分群模型,并且以“用户营销漏斗”为核心打造大数据产品。
作为实时、智能的营销数据平台,车智云提供一体化营销闭环解决方案,为车企研发、营销、服务赋能,主要是从研发和营销这两个角度,未来还会在渠道方面,助力车企全面提升。
4. 面临的挑战
我们面临的挑战,首先是海量的数据。其次我们对查询性能是有很高要求的,因为我们最终要做商业化的数据产品。稳定性这一块也是比较重要的,也是商业化的基本要求。
5. 一个备选方案
其实最开始和开发人员对接的时候,了解到他们也有一个备选的方案,最简单的方案完全基于自研的:基于 Hive 表里面的明细数据,也是提前预计算好各种所需要的指标,然后把他们存到 HBase 存储里面,然后通过这个 MR 去把这些结果写到一个 HBase 存储引擎里面,在 Web 层去自己写接口,去查询这个结果数据。
这个方案其实有很多问题,首先是说开发成本是很高的,因为如果是完全自研的话没有界面操作,人肉去维护大量运行脚步,还需要编写代码,对代码质量要求很高,还有自行开发排重指标,还有维度组合过多,这是难以维护的,还有 HBase 表后续也会很多,这个时候 Kylin 已经挺有名气的了,很多公司已经在用了,经过一番对比最终我们选择 Kylin 作为核心 OLAP 分析引擎。
6. 基于 Kylin 开发,大大降低开发及运维成本
Kylin 开发流程的第一步就是在界面上去同步 Hive 表的元数据,然后基于这个元数据去创建模型,然后再进一步建 Cube 并构建,然后就可以用 JDBC 查询数据了。
关于调度,我们写了一个 Kylin 的调度的脚本,然后把调度脚本上传到我们内部的调度系统上,并配置上游依赖就可以了。
使用 Kylin 开发的优势其实很明显,Kylin 直接基于 Hive 做建模,并且有完善易用的界面,我们不再需要写代码,普通数据开发人员就可以做数据的建模、构建及维护。同时构建过程可以是资源隔离的,可以用他们自己的队列去做构建,不必担心构建资源被其他业务抢占。Kylin 完美支持 SQL 和 JDBC,对后端开发人员非常友好。Kylin 提供了丰富的配置,可以通过修改配置,提升构建及查询性能。Kylin 还在 Cube 级别支持针对构建状态设置报警,可以第一时间发现构建失败的作业,并通知给 Cube 的负责人。Kylin 还会默认做慢查询统计,开发人员可以针对这些明显低效的查询进行优化。
7. Kylin 的常规优化
再说一下 Kylin 的一些常规优化手段,其实这种多维立方体的模式下,对 Cuboid 的剪枝是非常重要的,最理想的情况就是需要查询什么,我们就提前预计算什么,不查就不算,这从存储和查询的角度都是最优的,所以 Kylin 在 Cuboid 剪枝这一块提供了丰富的优化手段。
首先是常规维度和衍生维度,因为 Kylin 本身是支持星型模型的,维表里面的维度都是衍生维度,其实这些维度是不参与 Cuboid 生成的,所以合理使用衍生维度是一个很好的优化手段。还有就是设置聚合组,这也是为了精细化的去定义哪些维度组合是有必要的,哪些是没必要的;还可以声明必要维度、层级维度、联合维度来确保在减少 Cuboid 个数的同时,尽量不影响查询性能。
然后是查询性能的优化,Kylin 原生提供了对字典的支持,通过对原始数据做编码,不仅可以解决存储空间,还可以用来过滤 segment 提升查询性能;但是前面的例子里面也提到了如果维度基数很高的话,字典也会很大,所以需要尽量避免高基维使用字典。最后是 RowKey 的顺序,HBase 本身的特性决定了 RowKey 顺序的重要性,通常是需要把常用的高频的维度要放在前面的。Kylin 的优化手段网上有很多资料,这里就不做过多的介绍了。
优化 1:最大维度组合
接下来说一下我们做的一些优化,第一个就是维度的最大组合数控制。当我们开始宣称 Kylin 这个技术很牛,针对上千亿的数据,可以秒级返回查询结果时,一些运营人员就找过来了。他们说这个东西这么牛,给我们分析流量用吧,我们这个表里有几十个维度,想要用 Kylin 加速查询。用他们的话描述就是“需求很简单”,只要支持任意维度做交叉 GROUP BY 统计就行。乍一听几十个维度,感觉这需求很难实现,但是稍微思考一下,他们每次查询会用到几个维度呢?通过了解下来,通常来说每次查询最多也就用到三四个维度。拿我们的一个 17 个维度的 Cube 举例,假设单次查询最多就用到三个维度,我们不做任何优化的话,其实这个 Cuboid 维度组合的数量,其实是 2 的 17 次方,也就是 13 万,这其实是一个很大的数字,Kylin 现在默认支持的最大 Cuboid 数是 32768(可以通过配置调整),也就是 2 的 15 次方,超过这个数就不允许构建了,需要借助前面提到的剪枝手段做优化才行。
这个场景下面我优化的思路就是我们设置一个最大可以同时查询的维度的个数,然后结合必选维度,生成多个聚合组,来减少 Cuboid 的数量。这是最多构建三个维度的时候,Cuboid 的个数其实就是 C(17,3),即Cuboid数目为4080个,通常会有一个时间维度是必选字段,其实就只有C(16,3),即Cuboid数目为 3360,这个优化效果是很明显的。新版 Kylin 中已经完美的支持了这一特性,可直接在cube中设置“Max Dimension Combination” 。
注:函数C(维度数量,MDX)的结果含义为通过MDX来剪枝得到的最终cuboid数量。
优化 2:优化 Segment 过滤,解决超大字典问题
我们早期没有限制高基数维度对字典的使用,而且业务开发是由另外一个业务团队负责的,他们也没有关注高基维这一块,造成高基数维度使用普通字典,没有使用全局字典,并且上线了,导致查询server偶尔会占满堆内存,影响查询性能。
后来我们了解到他们其实是按月去构建的,每个月对应一个Segment,并且业务上不会跨Segment查询,每次只查询一个月的数据。Kylin的每个Segment都会对应一份独立的字典,而每次查询时都会扫描多个Segment,也就是会加载多个字典,这会导致内存占用彪高。
这个问题我们的紧急优化了一版代码,思路就是只查询有效的Segment,避免查询无效的Segment,自然就避免加载无效的字典了。我们怎么过滤呢,就是根据用户指定的时间范围,比如SQL中指定的是19年1月的数据,那么我们只需要查一个Segment就可以了,我们不需要加载无效的字典到内存里面,所以我们定义了一个参数叫cube.time.dimensions,同时引入了一个选择器去过滤Segment,具体的实现其实就是拿到TupleFilter对象,根据其中的时间条件,以及每个Segment的DateRange去判断这个Segment是否符合条件,如果不符合条件我们就直接不查了。
优化 3:非精确去重、精确去重
第三个优化是说我们有一个比较特殊的场景,可能大家都没有碰到过。我们之前有一个模块最开始是非精确去重的,运行良好,查询也比较稳定,但是上线一年后,产品突然说要改用精确去重,同时历史数据不能重刷,也就是历史数据保持非精确去重,新数据是需要做精确去重,还有一个前提,这个Cube也是不会跨Segment去聚合的。
其实最简单的思路,就是创建一个全新的Cube,设置使用精确去重,用来构建新数据,再通过创建Hybrid把新Cube和旧Cube “绑定”在一起,同时支持新老数据的查询。但是这里遇到一个问题,就是Hybrid不支持同一个字段在两个Cube中度量不同这种情况。
我们做了一点改造,就是根据SQL里面的时间条件去动态选择使用新Cube还是旧Cube,并且设置正确的Measure类型,也就是HyperLogLog或Bitmap。这里也用到前面提到的DateRangeMatcher工具类,根据SQL中的时间条件和Cube对应的时间区间,来选择正确的Cube并设置正确的度量类型。
优化 4:调度性能优化
再说调度性能方面的优化,背景其实是我们机房迁移过程当中有一段时间是需要跨机房去访问HBase集群的,我们发现跨机房访问HBase的时候,每次调度构建任时候很慢,调度一次要十几分钟。
通过定位我们发现是因为每次调度的时候会查询job的信息,也就是频繁的访问HBase,当时跨机房的网络延迟在5毫秒以上,已经超过HBase本身的查询时间了,所以会导致调度性能很慢。这一块优化比较简单,就是在调度的过程中,跳过所有的成功状态的job,而成功状态的作业占到了99%以上,所以做过这个改动后,调度性能就没有问题了。
优化 5:设置 Cube 为不可查询状态
这个是优化是我们把Cube设置成临时不可查询的状态。背景是当业务需求发生变化时,比如增加维度,通常需要创建新的Cube并且构建数据,比如说需要构建两年的数据,但是我们无法一次构建出两年的数据,肯定是一个月一个月的去构建,当第一个月的数据构建成功时,Kylin就会默认把这个Cube的状态调整为Ready状态,此时用户查询就可能会路由到这个数据还不完整的新Cube上,会导致这个结果不符合预期。
应对这种情况,我们增加了一个Cube级别的配置,标记这个Cube不参与查询,当数据没有刷完的时候把这个配置设置成false,Cube就不参与路由了,当数据全部构建成功后,再设置为true,同时把旧的Cube disable掉就可以了。
优化 6:监控及自动拉起
我们监控主要是基于Kylin原生的Metric,Kylin提供了丰富的Metric,结合之家云监控平台提供的prometheus和grafana可以配置相对全面的监控图表。
在前期没有加健康检测的时候,服务有的时候还是会不稳定的,比如说之前提到的问题,可能会导致查询很慢,所以我们自己开发了监控程序,部署在每个Kylin节点上,用于监控Kylin服务的健康状况,并视情况进行重启。
首先就是我们会检查Kylin的进程是否存在,如果是存活状态,再去调一个REST API,判断响应时间是否符合预期,同时还会监控堆内存的占用,如果有一个指标连续几次不符合预期,就会上报metric并报警,同时重启Kylin。比如我们线上堆内存连续3次检测都超过95%,那么就会自动重起Kylin。另一方面,重启本身也会带来风险,所以需要合理设置每个阈值,避免误报,影响服务;同时需要指定合适的最小重启间隔,避免无限重启;同时监控程序本身也要负责监控自身的工作线程是否正常运行;最后监控程序本身会监听一个端口,方便其他的服务区监控自己。
这个是我们监控程序的一个示例,最上面通过pidCmd、startCmd、KillCmd用来声明检测进程存活状态的命令以及重启的命令,接下来是Kylin实例的一些基本信息,还有JMXExport端口的配置,最下面就是检测周期和报警的相关配置。
优化 7:HBase 主从集群
然后还有一个比较重要的点,就是HBase主从集群,因为我们是提供商业化的数据产品,所以我们需要保障SLA。所以我们把这个集群做了一个T+1的备份,我们为什么不用原生的储存备份?我们用的是HBase1.2.4版本,这个版本的主从复制是不支持Kylin使用的Bulkload方式的,所以我们自己开发了一套程序去做备份。
首先就是对比HBase对应的HDFS上的文件,增量的去拷贝这些文件,也就是用distcp去做文件同步,然后第二步就是让HBase去识别这些表,最后我们要把从集群上多余的表给定期Drop掉。因为我们主要的数据都是T+1的数据,都是离线的数据,每天都是在早上9点之前去把前一天的数据都构建好,所以我们每天十点的时候会调起的备份程序,把数据备份到HBase从集群上,尽量让从集群的数据和主集群保持一致。
这个从集群还有一个好处,就是我们可以把这个服务开放给分析师用,因为这个集群上的数据很重要,也比较全面,分析师会用这份数据产出一些行业报告,同时也会做一些探索性的查询。由于这部分都是手写的SQL查询,包含很多不确定性,如果这些操作直接在主集群上去做的话,有可能会把HBase给搞坏了,整个Kylin服务都会挂掉。有了这个从集群之后,顺便也把分析师的需求也支持了,让他们去查这个从集群,不用担心对线上的服务造成影响。
8. 其他实践
再说一下其他的一些实践,最基础的就是定期去备份Kylin的元数据,定期调用Kylin的清理脚本,删除无用的数据。同时定期清理Kylin生成的一些临时文件,不然他有的时候可能会报inode数超出限制的异常。我们还加了一个SQL黑名单的功能,就是前期的时候有些查询会导致HBase的region server批量挂掉,这个时候负责HBase的同学会定位到是因为哪个Query ID导致的,我们会找到对应的SQL,然后把这个SQL加入到黑名单里面去,临时把这个异常查询排掉。同时我们也加了一个功能,就是强制去要求用户必须制定Where条件,这也是为的保证稳定性的。然后我们去完善了一下原来的REST API,比如支持查询更长时间的job。还有完善了慢查询的告警,以及使用Filebeat将Kylin日志统一收集到Elasticsearch中,便于查询及分析。
9. KylinSide - 集群信息统计、集群管理
我们内部还有一个系统,我们叫做 KylinSide,他主要是生成一些统计信息,便于我们对集群做管理和维护,还有一些迁移和升级过程中用到的工具,也是 KylinSide 提供的。这个截图是 KylinSide 生成的一些集群统计数据,通过之家的 AutoBI 系统配置的 Dashboard。新版本的 Kylin已经自带了强大的 Dashboard 功能,目前我们两者都在使用。
03
集群升级的一些经验分享
1. 背景及挑战
集群升级的背景主要是说我们当时用到1.6的版本,其实是比较早期的一个版本了,功能也相对比较少,比如说Spark构建不支持,job server不支持高可用,我们很早就想升级了。因为是支持商业数据产品,服务不能长时间去中断服务,并且升级之后数据必须和原来保持一致,同时我们要规避升级过程带来的风险。
2. 整体方案
从1.6到2.6的版本,我们觉得升级跨度比较大,可能会有未知的风险,所以我们当时的方案是基于新版本的Kylin去搭建新的集群,然后并行运行,稳定运行后,直接切换域名解析。
整个过程分这几步,首先肯定是要把元数据同步到新集群,然后我们要把历史的这些Segment都构建起来,并且增量的构建要同步到新集群上进行自动构建,最后要对比新老集群的Segment数据量和大小以及连续情况。同时我们需要去收集老集群的一些SQL,然后把他们回放到新集群上,对比新老集群的SQL查询的结果并且生成对比报告。
3. 整体架构
这个是我们升级的一个整体架构,其实中间是基于KylinSide的工具,自动去构建和回放的一些功能。首先我们是对元数据的备份,相当于我们是直接对取HBase的元数据表,去把他同步到另一个HBase里面,其次是历史数据构建及增量构建,这也是一个自动的过程,还有就是我们收集老集群的SQL,写到kafka里面,然后存到MySql里面,然后KylinSide定期去回放这些SQL到新的集群里面去执行,最终会生成SQL结果的对比报告。
04
未来规划
1. 实时 OLAP
其实我们组是一个实时计算的小组,除了负责Kylin,我们还负责实时接入分发平台,消息中间件,实时计算平台这几块内容。我们目前遇到很多实时需求都是可以看成是实时OLAP需求,大部分场景都是让用户在我们平台上去写Flink SQL作业,将结果存储到Redis Sink或MySql Sink中。用Flink SQL开发,虽然一定程度上减轻他们的开发压力了,但还是没有Kylin的多维建模来的自然,同时Kylin本身支持lambda模式,可以很自然的实现实时和离线计算的口径统一。
目前我们也在调研了 Kylin 的实时OLAP的能力,我们内部也有几个场景在试用,虽然目前还没有正式上生产,但我们相信Flink SQL 的实时ETL加上Kylin的实时多维建模是一个不错的选择。
2. 云原生
我们很期待4.0云原生时代的Kylin,尤其是实时OLAP这一块,实时OLAP目前的运行方式,Streaming Receiver的维护成本还是有点高的,相信4.0时代,可以支持在Cube级别动态部署Streaming Receivers,让实时模块更加易用。
作者:邸星星,汽车之家实时计算平台负责人,长期从事实时计算与 OLAP 领域的平台建设工作,致力于为公司提供大规模、高效、稳定的计算与查询服务。