Apache Kylin 在一点资讯的实践

发展历程

2016 年 9 月开始,一点资讯选择了综合性能优秀的 Druid 来承接大数据部门、算法部门和广告部门的多维分析查询需求。2017 年 9 月,接入刚刚开源的 Doris,承接明细查询和 SQL 分析业务。

至今年 5 月,随着业务增长和数据积累,冷数据占比增高,机器利用率降低。大部分数据月查询次数不超过 1 次,却需要长期存储,因而造成大量机器资源浪费。 如何提高有限资源的利用率,支持维度高达 27 个,日志量达 1 T/天,查询周期长达 1 年的业务呢?经过一系列调研,一点资讯决定使用 Kylin 系统。 Kylin 支持Hive、Kafka等形式的数据源,Cube存储及查询使用HBase,构建任务可以利用运行在Yarn上的MapReduce或Spark任务,这些都是一点资讯使用中的大数据组件,它们的存储计算均为PB级或以上级别,只需要再搭建轻量级 Kylin 实例即可。同时,Kylin 可以提供稳定高效的多维分析查询,对 JDBC 接入友好。

经过一个月的调研后, 一点资讯在今年 6 月份正式将第一个 Kylin cube 投入线上使用。到目前为止,一点资讯总 Cube 数为 75 个,总数据量达到 90 T,最大的一个 Cube 源数据条数达 2.6 万亿。部署方式为 K8S 部署,两个 All 实例,两个Query 实例。 K8S 部署大大减轻了运维负担,同时双角色双活保证任意一个实例故障都不会影响 Kylin 的正常使用。任务引擎高可用部署方案详见官网:http://kylin.apache.org/cn/docs/install/kylin_cluster.html

上图是一点资讯 OLAP 服务框架。为使公司内部各业务部门更方便地使用 OLAP 系统,一点资讯研发了自己的一站式 OLAP 分析平台,集数据源管理、任务调度、查询分析、权限管控、监控报警于一身。主要支撑了三个大方向业务:算法分析、数据分析、运维。

Kylin 实践经验

关于 Kylin 的实践经验,主要分享三个 Topic:大型 Cube 调优实践,HBase 稳定性提升实践和特殊数据导致任务失败案例。

01

大型 cube 调优实践

刚刚使用 Kylin 后遇到的第一个 Cube,数据源来自 27 个维度,日新增数据量高达 140 亿的离线任务。

业务方要求每天十点之前出报表,且不能对其他任务产生影响,同时查询成功率达95% 以上,支持一年数据存储。

我们首先按照需求创建 Cube,没有进行任何优化,尝试构建后发现该 Cube 无法满足业务需求。Cube 构建时长达 500 分钟,每天下午 2 点才能构建完成。任务构建时消耗计算资源会严重影响其他流水线运行,业务常用查询成功率不足 95%,空间存储达 500 G。

为了从各个方面入手优化 cube, 我们首先调研网上权威的优化方案,调整了维度关系,rowkey 顺序,一部分构建参数等。最后我们自研了智能分组策略。

我们首先找寻查询历史规律,发现当某维度组合 ABC 查询频率较高时,其子维度组合(AB,AC,BC,A,B,C)查询频率也会较高且都会高于原 ABC 组合。 于是根据这一规律,我们设计了如下分组策略。

Step1. 计算各维度权值,权值含义为将该维度添加至预分组后能够多覆盖的查询次数。

Step2. 将权值最高的维度加入预分组。

Step3. 重新计算权值,重复以上步骤,直到维度权值过小或预分组维度数达到预定值。

上图示例中,初始状态下预分组为空集,各维度权值即为其查询次数,首次将查询次数为105的维度A 加入预分组。更新各维度权值,C的权值增加为 90+80,成为当前权值最大维度,于是将 C 加入预分组,依此类推。

这一算法可以帮助我们找到各查询维度组合之间的关系,同时保持一定的可拓展性。我们按照分组策略结果设定与之对应的Aggreation group。同时将常用明确的查询作为 MandatoryCuboids,以确保常用查询维度组合cuboid 一定会被预计算。

最终效果如上图所示,cube 查询,构建,存储,稳定性都有明显提升。

02

HBase稳定性的调优的实践

随着 Kylin 在公司内部业务量不断攀升,在一段时间内页面稳定性降低,经常出现页面不响应或查询失败,但重新查询即可成功、构建失败但重启后会成功等问题。

其真正的原因在于 HBase 稳定性降低。当时导致 HBase 稳定性差的一个重要原因是 region 数过多。生产集群的 HBase 总数据量 90 T,但大小 region 数共16 万+,region 划分存在严重浪费。 我们发现如下情况:

a)  region 划分不合理,单个 region 中数据量小于 100M 较多

b)  数据冗余。天级查询任务,按小时更新,不同 table 中存在相同的 rowkey。

针对以上情况我们使用了如下两种手段:

(1)开启自动 Merge 功能

针对数据冗余情况我们使用了 Kylin 的 Merge 功能,Merge会合并不同 segment的cuboid 数据,且不需要依赖原始数据。开启也非常方便,手动地提交一个 merge 任务或者设置 refresh settings 并开启 auto-merge 即可。

Merge 任务有效帮助我们缩减了 50% 空间占用,提高了 30% 查询速度,其主要原因在于合并了相同的 key。

Merge 时,发生了一个意外。某个Merge 任务持续运行 10 小时没有结束,频繁生成大量只有 54B 的小文件,导致我们 HDFS namenode 的节点内存不足。

经排查发现凡涉及使用HFileOutputFormat2工具类进行BlukLoad都可能遇到这一问题。 我们已将问题修复并提交至HBase社区。 问题的详细解析参考https://issues.apache.org/jira/browse/HBASE-22887。

(2)预估参数调整

对于 region 划分不合理的情况,我们调整预估参数。Kylin 依据其预估数据两判定生成 HTable region 数,这是因为创建HTable在生成 cuboid 数据之前,无法准确获知真实的数据量。

预估包括两个部分,行数预估和大小预估,涉及参数如上图。 其中普通指标和维度的占用空间相对固定,而 TOPN 和 count distinct 指标占用空间会依据数据形态产生较大的变化。

在我们的案例中,使用默认两个行数预估参数,预估非常准确,误差在 1% 以下。而大小预估误差较大,预估大小能达到实际大小的 6.5 倍以上,我们依据实际情况将 TOPN 和 count distinct 的参数做了如上表格调整。调整后预估大小为实际大小的 1.32 倍。

通过 merge 任务和预估参数调整的 region 数量 16 万个降到 7 万个,保证了 HBase 稳定运行。

03

特殊数据导致任务构建失败案例

这一特殊数据导致任务在提取事实表的唯一列(Extract Fact Table Distinct Columns)时失败。

提取事实表的唯一列是如何实现的呢?它将所有数据的每一列在 map 中提取出来,并分配给不同的 Reducer 去重。如上例这一步会把电影类型维度“动画”、“主旋律”、“动作”这些放到一个 Reducer 里面去重,大部分的情况下会在去重后构建字典。如果是 UHC(Ultra High Cardinality)列,则平均分配给不同的 Reducer 去重。另一个 Reducer 来专门负责计算预估行数等。

那么通过什么样的方法来决定该将“动作”或者“动画”分配给哪个 Reduce 呢

  • 维护每一个维度及其 Reduce 对应关系表,维度 A 就是 Reducer0,维度 B 是 Reducer1~3, 计算 预估行数 Reducer-1

  • 普通维度:“动画”、“主旋律”、“动作”等,都是“电影类型”维度按照对应关系会被分配给 Reduce0。

  • UHD 维度:而《熊出没》和《姜子牙》通过均匀分配给不同 Reduce 。 公式为:beginId+abs(hash_code(fieldValue))%span,其中beginId为该维度起始reducerId,span为该维度对应reducer数,fieldValue即为待分配的数据。

本案例中特殊数据为“0KM2dD66”,其特点在于 hash_code(“0KM2dD66”) =-2147483648,即最小的Int值,而abs(INT_MIN)=INT_MIN。 根据公式计算得出 ReducerId 为 -1,于是这一数据错误地进入了负责行数预估的 Reducer -1。 由于只有负责行数预估的 Reducer 中 value 不为空,当其中混入一个 value 为空的数据时,任务失败。

若 UHC 列 Reducer 数不为 3,或该维度起始 ReducerId 不为 1,则该特殊数据可能会被分配到其他任意 Reducer 中,甚至是不存在的 Reducer,从而导致任务在提取事实表的唯一列执行失败,必须找到该数据并剔除才能执行成功。

我们已经将问题修正提交至社区。详情见: https://issues.apache.org/jira/projects/KYLIN/issues/KYLIN-4106

未来展望

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章