文章作者:爱奇艺大数据服务团队
内容来源:爱奇艺技术产品团队
导读:
最近几年大数据技术在各行各业得到广泛应用,为企业的运营决策和各种业务提供支持。随着数据的增长,业务对数据时效性的要求,给企业的大数据分析带来了巨大挑战。针对海量数据的实时分析需求,近年来市场上涌现出众多 OLAP 分析引擎。这些 OLAP 引擎有各自的适用场景和优缺点,如何选择一款合适的引擎来更快地分析数据、更高效地挖掘数据的潜在价值?
爱奇艺大数据服务团队评估了市面上主流的 OLAP 引擎,最终选择 Apache Druid 时序数据库来满足业务的实时分析需求。本文将介绍 Druid 在爱奇艺的实践情况、优化经验以及平台化建设的一些思考。
爱奇艺大数据 OLAP 服务在2015年前主要以离线分析为主,主要基于 Hive+MySQL、HBase 等。2016年起引入 Kylin 和 Impala 分别支持固定报表和 Ad-hoc 查询。2018年以来引入 Kudu 和 Druid 支持实时分析需求。
在引入 Druid 之前,业务的一些场景无法通过离线分析满足,如广告主想要实时基于投放效果调整投放策略、算法工程师调整模型推到线上 A/B 要隔天离线报表才能看到效果。这些场景都可以归纳为对海量事件流进行实时分析,经典的解决方案有如下几种:
1. 离线分析:
使用 Hive、Impala 或者 Kylin,它们一个共同的缺点是时效性差,即只能分析一天或者一小时前的数据,Kylin 还面临维度爆炸的问题
2. 实时分析:
用 ElasticSearch 或 OpenTSDB,由于数据结构本质是行存储,聚合分析速度都比较慢;可以通过查询缓存、OpenTSDB 预计算进行优化,但不根本解决问题;
用流任务 ( Spark/Flink ) 实时地计算最终结果,存储在 MySQL 提供进一步服务;问题是每当需求调整,如维度变更时,则需要写新的流任务代码;
使用 Kudu 和 Impala 结合能够做到实时分析。在实践过程中发现,Kudu 受限于内存和单机分区数,支撑海量数据成本很大。
3. Lambda 架构:
无论选用哪种实时或离线方案的组合,都会采用 Lambda 架构,用离线数据校准实时数据。这意味着从摄入、处理、查询都需要维护两套架构,新增一个维度,离线和实时均需对应修改,维护困难。
以上种种方案的不足,促使我们寻找新的解决方案,最终决定采用 Druid。
02
Apache Druid
Apache Druid 是针对海量事件流进行存储和实时多维分析的开源系统。它具有如下特性:
-
-
交互查询:查询延时在秒级,核心思想为内存计算和并行计算
-
维度灵活:支持几十个维度任意组合,仅在索引时指定的维度查询可见
-
-
流批一体:新版本 KIS 模式可实现 Exactly Once 语义
上图为 Druid 架构图,大体分为几个模块:
MiddleManager:索引节点,负责实时处理消息,将其转成列式存储,并通过 Rollup 精简数据量;索引节点定期将内存中数据持久化为不可修改的文件 ( Segment ),保存至 HDFS 保证数据不会丢失;
Historical:历史节点,将 Segment 加载到本地,负责大部分查询的计算;
Broker:查询节点,将查询分解为实时和离线部分,转发给索引节点和历史节点,并汇总最终的查询结果;
Overlord:负责索引任务管理;
Coordinator:负责负载均衡,确保 Segment 在历史节点之间尽量均衡。
Druid 很好地填补了爱奇艺在实时 OLAP 分析领域的空白,随着业务实时分析需求的增加,Druid 集群和业务规模也在稳步增长。目前集群规模在数百个结点,每天处理数千亿条消息,Rollup 效果在10倍以上。平均每分钟6千条查询,P99 延时一秒内,P90 延时在200毫秒内。在建设 Druid 服务过程中,我们也不断遇到规模增长带来的性能瓶颈和稳定性问题。
当时的挑战是实时索引任务经常被阻塞。Druid 的 Handoff 总结如下,索引节点将 Segment 持久化到 HDFS,然后 Coordinator 制定调度策略,将计划发布到 ZooKeeper。历史节点从 ZooKeeper 获取计划后异步地加载 Segment。当历史节点加载完 Segment 索引节点的 Handoff 过程才结束。这个过程中,由于 Coordinator 制定计划是单线程串行的,如果一次触发了大量 Segment 加载,执行计划制定就会很慢,从而会阻塞 Handoff 过程,进而索引节点所有的 Slot 均会被用满。
而以下过程均会触发大量 Segment 加载,在解决 Coordinator 调度性能瓶颈前, 很容易引发故障:
历史节点因硬件故障、GC、主动运维退出
调整 Segment 副本数、保留规则
通过火焰图对 Coordinator 进行 Profiling 最终定位了问题,如下图所示,将最耗时部分放大出来,是负载均衡策略对每个 Segment 要选择一个最佳的服务器。阅读源码可知其过程为,加载 Segment X,需要计算它和服务器的每个 Segment Y 的代价 Cost(X,Y),其和为服务器和 Segment X 的代价。假设集群有 N 个 Segment,M 个 Historical 节点,则一个节点宕机,有 N/M 个 Segment 需要加载,每个 Segment 都和剩余的 N 个节点计算一次代价,调度耗时和 N 成平方关系。
一个节点宕机调度耗时 = (N/M) 个 Segment * 每个 Segment 调度耗时 = (N/M) * N = O(N^2)
分析清楚原因后,很容易了解到 Druid 新很容易了解到 Druid 新版本提供了新的负载均衡策略:
druid.coordinator.balancer.strategy = CachingCostBalancerStrategy
应用后调度性能提升了10000倍,原先一个历史节点宕机会阻塞 Coordinator 1小时到2小时,现在30秒内即可完成。
Overlord 性能慢,我们发现升级到0.14后 Overlord API 性能较差,导致的后果是索引任务概率性因调用 API 超时而失败。通过 Jstack 分析,看到大部分的 HTTP 线程均为阻塞态,结合代码分析,定位到 API 慢的原因,如左图所示,Tranquility 会定期调用 Overlord API,获取所有 RunningTasks,Overlord 内部维护了和 MySQL 的连接池,该连接池默认值为8,该默认值值过小,阻塞了 API 处理。解决方法是增大 dbcp 连接池大小。
druid.metadata.storage.connector.dbcp.maxTotal = 64
调整后,Overlord 性能得到了大幅提升,Overlord 页面打开从几十秒降低到了几秒。但意料之外的事情发生了,API 处理能力增加带来了 CPU 的飙升,如右图所示,并且随着 Tranquility 任务增加 CPU 逐渐打满,Overlord 页面性能又逐步降低。通过火焰图 Profile 可知,CPU 主要花费在 getRunningTasks 的处理过程,进一步分析 Tranquility 源码后得知,Tranquility 有一个配置项:
druidBeam.overlordPollPeriod
可以控制 Tranquility 轮询该 API 的间隔,增大该间隔后问题得到了暂时缓解,但根本的解决方案还是将任务切换为 KIS 模式。
Druid 索引成本过高。基于 Druid 官方文档,一个 Druid 索引任务需要3个核,一个核用于索引消息,一个核用于处理查询,一个核用于 Handoff 过程。我们采用该建议配置索引任务,压测结果是3核配置下能够支撑百万/分钟的摄入。
在最初,集群所有的索引任务都是统一配置,但实际使用过程中,大部分的索引任务根本达不到百万/分钟的消息量,造成了资源大量浪费。如下图所示,我们按照索引任务的内存使用量从高到低排序,9 GB 为默认配置,80%的任务利用率低于1/3,即 3 GB。我们以 3 GB 绘制一条横线,以内存使用最接近的任务绘制一条竖线,定义 A 为实际使用的内存,B 为第二象限空白部分,C 为第四象限空白部分,D 为第一象限空白部分,则浪费的资源 = ( B+C+D ) 的面积。
我们思考能否采取索引任务分级的策略,定义一种新的类型索引节点 – Tiny 节点。Tiny 节点配置改为 1 core\3GB,能够满足80%小任务的资源需求,而 default 节点继续使用 3 core9 GB 的配置,满足20%大任务的需求,在这种新的配置下,浪费的资源 = ( B + C ) 的面积,D 这一大块被省下来。简单地计算可知,在不增加机器的情况下,总 Slots 能够增加1倍。
默认 slot 资源需求为1,Tiny 为1/3,调整后单位任务需要的资源 = 0.2 * 1 + 0.8 * 1/3 = 0.5
在实际操作层面,还需解决一个问题,即如何把 Datasource 指定给合适的 Worker 节点。在 Druid 低版本中,需要通过配置文件将每一个 Datasource 和 Worker 节点进行关联,假设有 N 个 Datasource,M 个 Worker 节点,这种配置的复杂度为 N * M,且无法较好地处理 Worker 节点负载均衡,Worker 宕机等场景。在 Druid 0.17中,引入了节点 Category 概念,只需将 Datasource 关联特定的 Category,再将 Category 和 Worker 绑定,新的配置方法有2个 Category,复杂度 = 2 * N + 2 * M。
4. Tranquility vs KIS
刚使用 Druid 时,当时主力模式是 Tranquility。Tranquility 本质上仍然是经典的 Lambda 架构,实时数据通过 Tranquility 摄入,离线数据通过 HDFS 索引覆盖。通过离线覆盖的方式解决消息延迟的问题,缺点是维护两套框架。对于节点失败的问题,Tranquility 的解决方案是链路冗余,即同时在两个索引节点各起一份索引任务,任一节点失败仍有一份能够成功,缺点是浪费了一倍的索引资源。自0.14版本起,Druid 官方建议使用KIS模式索引数据,它提供了 Exactly Once 语义,能够很好地实现流批一体。
和 Tranquility 的 Push 模式不同,KIS 采取 Pull 模式,索引任务从 Kafka 拉取消息,构建 Segment。关键点在于最后持久化 Segment 的时候,KIS 任务有一个数据结构记录了上一次持久化的 Offset 位置,如图例左下角所示,记录了每个 Kafka Partition 消费的 Offset。在持久化时会先检查 Segment 的开始 Offset 和元信息是否一致。如果不一致,则会放弃本次持久化,如果一致,则触发提交逻辑。提交中,会同时记录 Segment 元信息和 Kafka Offset,该提交过程为原子化操作,要么都成功,要么都失败。
KIS 如何处理各个节点失败的情况呢?假设 Kafka 集群失败,由于是 Pull 模式,Druid 在 Kafka 恢复后继续从上一个 Offset 开始消费;假设 Druid 索引节点失败,Overlord 后台的 Supervisor 会监控到相应任务状态,在新的索引节点启动 KIS 任务,由于内存中的状态丢失,新的 KIS 任务会读取元信息,从上一次的 Offset 开始消费。假设是 MySQL 或者更新元数据过程失败,则取决于提交的原子操作是否成功,若成功则 KIS 从新的 Offset 开始消费,失败则从上一次 Offset 开始消费。
进一步看一下 KIS 是如何保证 Exactly Once 语义。其核心是保证 Kafka 消费的 Offset 连续,且每个消息都有唯一 ID。Exactly Once 可以分为两个部分,一是 At Least Once,由 KIS 检查 Offset 的机制保证,一旦发现缺失了部分 Offset,KIS 会重新消费历史数据,该过程相当于传统的离线补数据,只是现在由 Druid 自动完成了。另一个是 At Most Once,只要保证 Offset 没有重叠部分,则每条消息只被处理了一次。
以下是 KIS 在爱奇艺的一个实例,左下图为业务消息量和昨天的对比图,其中一个小时任务持久化到 HDFS 失败了,看到监控曲线有一个缺口。之后 Druid 后台启动了一个新的 KIS 任务,一段时间后,随着 KIS 补录数据完成,曲线图恢复到右下图所示。那么,如果业务不是一直盯着曲线看,而是定期查看的话,完全感受不到当中发生了异常。
Druid 性能很好,但在初期推广中却遇到很大的阻力,主要原因是 Druid 的易用性差,体现在如下几个方面:
数据摄入需要撰写一个索引配置,除了对数据自身的描述 ( 时间戳、维度和度量 ),还需要配置 Kafka 信息、Druid 集群信息、任务优化信息等
查询的时候需要撰写一个 JSON 格式的查询,语法为 Druid 自定义,学习成本高
返回结果为一个 JSON 格式的数据,用户需自行将其处理成最终图表、告警
报错信息不友好,上述所有配置均通过 JSON 撰写,一个简单的逗号、格式错误都会引起报错,需花费大量时间排查
为解决 Druid 易用性差的问题,爱奇艺自研了实时分析平台 RAP ( Realtime Analysis Platform ),屏蔽了 Kafka、Druid、查询的细节,业务只需描述数据格式即可摄入数据,只需描述报表样式、告警规则,即可配置实时报表和实时告警。
RAP 实时分析平台目前已经在爱奇艺会员、推荐、BI 等多个业务落地,配置了上千张报表,帮助业务在实时监控报警、实时运营分析、实时 AB 测试对比等场景提升排障响应速度、运营决策效率。
进一步迭代完善 Druid 及 RAP,提升稳定性、服务能力,简化业务接入成本:
接入爱奇艺自研的 Pilot 智能 SQL 引擎,支持异常查询拦截、限流等功能
运维平台:包括元信息管理、任务管理、服务健康监测等,提升运维效率
离线索引:支持直接索引 Parquet 文件,通过 Rollup 进一步提升查询效率
支持 JOIN:支持更丰富的语义
今天的分享就到这里,谢谢大家。
如果您喜欢本文,欢迎点击右上角,把文章分享到朋友圈~~
欢迎加入
DataFunTalk 深度学习交流群
,跟同行零距离交流。识别下方二维码,
自动入群。
友情推荐:
参考资料:
[1] https://druid.apache.org/
爱奇艺大数据服务团队诚招Spark/Flink实时计算服务技术专家/架构师。爱奇艺大数据服务团队负责公司大数据基础设施的建设工作,提供涵盖数据采集、数据处理、数据开发、数据应用等整个大数据处理流程的一系列大数据开源服务及相应的开发平台,具备万台以上大规模分布式服务能力。邮件主题:大数据实时计算资深研发工程师-${姓名};简历发送:
liangjianhuang@qiyi.com
关于我们:
DataFunTalk
专注于大数据、人工智能技术应用的分享与交流。发起于2017年,在北京、上海、深圳、杭州等城市举办超过100场线下沙龙、论坛及峰会,已邀请近500位专家和学者参与分享。其公众号 DataFunTalk 累计生产原创文章300+,百万+阅读,6万+精准粉丝。
🤔你今天写 BUG 了么?👇