Apache Druid在阿里巴巴应⽤监控系统中的应⽤

背景

阿⾥巴巴本地⽣活商家 SaaS (Software as a Service) 技术中⼼致⼒于为商家提供点餐、收银、营销等技术服务。⽽技术中⼼基础架构团队则为业务团队(Business Unit)提供基础组件(如⾼性能⽹关、微服务解决⽅案、⾦丝雀灰度发布解决⽅案、消息中间件等),同时也负责应⽤性能监控系统与分布式调⽤链路系统的开发和维护,为快速 发现问题、定位问题、解决问题提供有利⽀持。 

商家SaaS技术中⼼应⽤监控系统的建设要追溯到2017年,应⽤的技术发展呈现两个明显的特征: 

  1. 微服务概念的已经普及开来,Spring Cloud为主的架构体系逐渐成熟,在这样的背景下,⼤量的单体应⽤被拆分为粒度较⼩的微服务应⽤,⽣产环境上部署的应⽤数快速增⻓ 
  2. 随着业务的发展,应⽤中引⼊了越来越多中间件系统,⽐如MySQL、Redis、 MongoDB、Kafka、Elastic Search、定时组件等,应⽤变得越来越复杂 

如何快速发现应⽤中存在的性能问题、定位⽣产环境中暴露的问题;应⽤出现问题时,如何快 速发现是哪⾥出的问题,成为每⼀个应⽤上线后⾯临的主要问题。传统的问题定位靠经验猜 测、上环境调试、打⽇志的⽅法,效率越来越低下,往往花费数⼩时、数天仍然不得结果。 

应⽤监控系统正是在这样⼀个背景下应运而生。 

业务模型与需求

应⽤监控主要是通过监控应⽤的⼀系列的运⾏指标(⽐如CPU占⽤率、QPS、数据库访问 TPS)来观测和判断系统的运⾏状况。 

这⼀系列的运⾏指标数据呈现⾮常明显时序数据特征: 

1. 总是由时间+⼀组可筛选的维度+⼀系列指标数据组成

以CPU占⽤率为例,⼀个指标数据可以表示如下: 

time appName instance cpuUsage
2021-06-20T12:08:39.000Z gateway 192.168.1.1 2.37%
2021-06-20T12:08:40.000Z gateway 192.168.1.2 1.89%

其中 appName , instance 是可筛选的维度,⽽ cpuUsage 则为具体的指标项 

2. 指标数据的查看、聚合统计总是围绕这三者进⾏ 

⽐如,查看今天中午某个时段某个应⽤平均QPS: 

  • “今天中午某个时段”,是时间范围 
  • “某个应⽤”是筛选维度 
  • “平均”是数据聚合⽅式 
  • “QPS”是具体指标 

3. 数据⼀旦产⽣,不会更改 

从⽇常使⽤的需求来看,我们对应⽤监控包括⼏⽅⾯的诉求: 

1. 实时监控。即能够实时地观测到某个应⽤某⼀类指标的实时数据,也就是说从数据收集 到数据能够被观测,时延尽可能⼩; 

2. 数据指标的聚合查询和明细查询。⽐如⼀个应⽤通常有多个运⾏实例,我们不仅需要知道这个应⽤总的QPS,也需要知道单个运⾏实例的QPS; 

3. 数据回溯。能够回溯到历史任何时刻观测彼时的历史数据,此功能可为⽣产问题事后复盘 提供完整的数据支持; 

4. 数据统计分析。⽐如过去某个节假⽇流量⾼峰数据是多少,今天与昨天同⽐、上周同⽐的变化情况,为稳定性保障、未来扩容方案等提供数据⽀撑; 

5. 快速查询。即使是查询过去1个⽉、2个⽉甚⾄更⻓时段的某个指标,仍然能够在⽤户可接受的查询响应时间内以相对实时的⽅式得到结果,⽽不是需要通过漫长的离线计算等待结果; 

6. 并发查询。应⽤监控系统目标是提供给所有技术⼈员使⽤,因此需要具备⾼并发查询能⼒。特别是在⼤型促销活动盯盘或者线上问题定位时,并发查询能⼒尤为重要,绝对不能在⽤户⾼频使⽤时造成较差的使⽤体验; 

7. ⾼可⽤。应⽤监控系统是整个⽣产环境的眼睛,绝对不能在关键时刻掉链⼦,否则整个⽣ 产环境就成了⿊盒,所有⼈都抓瞎; 

8. ⽔平扩容。应⽤的性能指标数据由应⽤产⽣,⽽应⽤数、应⽤的实例数,随着业务的发展 都会快速增⻓,最终需要存储和查询的指标数据都会快速增⻓。所以整个解决方案要能够⽔平扩容⽀撑业务的发展。

从零到⼀,快速试错

架构

既然我们要存储的数据具备明显的时序数据特征,项⽬初期我们⾸选的就是在时序数据库领域 排名第⼀的数据库InfluxDB。同时考虑这是⼀个从零到⼀的过程,在设计时定位为⼀个原型验 证系统,快速试错,找准⽅向。在技术栈的选择上采⽤成熟的系统: 

集成Spring Boot Actuator组件实现指标的收集 

/health: 收集应⽤运⾏状态 

/metrics: 收集JVM和WEB请求指标 

InfluxDB作为时间序列指标存储 

Grafana实现指标的可视化 

架构如下所示:

问题

这样⼀个原型架构经过验证后,我们发现,要想在⽣产环境⼤规模的应⽤起来,系统中的各个 部分,包括指标采集、指标存储、指标可视化均存在诸多问题。 

就指标存储⽽⾔,由于开源的InfluxDB不⽀持集群,我们不得不通过⼀致性Hash算法来实现分⽚存储,但这个⽅案⼜导致了新的问题: 

  1. 集群不能动态⽆缝扩展,通过加InfluxDB节点存在数据迁移带来的成本 
  2. 添加InfluxDB节点后,需要修改节点前的分⽚节点配置(即上图中的Collector位置应⽤), 才能将数据负载均衡到新的InfluxDB节点 
  3. 数据按照什么维度来分⽚?按照应⽤名称来分⽚,不同应⽤指标数据量差异⾮常⼤,会导 致InfluxDB节点读写⾮常不均匀 

找准⽅向,打好地基

通过第⼀阶段的原型验证暴露的问题,我们对指标数据存储进⾏重新考量,将⽬光转向了 Apache Druid。 

之所以选择Apache Druid,不仅仅是要解决上述集群⾼可⽤问题,更重要的是⾯向未来⼤规 模应⽤部署后,数据规模膨胀后可能引发的性能问题。 

再回到我们对应⽤监控的系统的⼋⼤诉求,我们整理了Apache Druid的关键特性如何对应满 ⾜我们的需求。

Apache Druid特性 能够⽀撑的需求
分布式,列存储,实时数据分析存储系统 1.实时监控
实时流式数据⽆锁摄取,导⼊后可⽴即查询 1.实时监控
多维度筛选分析 2.数据指标的聚合查询和明细查询 4.数据统计分析
实时(亚秒级)在线查询海量(PB级)数据 3.数据回溯 5.快速查询
⾯向⽤户千万级并发查询分析 6.并发查询
服务100%在线,可以做到永不宕机 7.⾼可⽤
⾼可⽤,分布式容错架构 7.⾼可⽤
Share Nothing 8.⽔平扩容
在线热升级 7.⾼可⽤ 8.⽔平扩容
横向⾃由扩展,每秒可处理万亿事件,数PB和数千个查询 8.⽔平扩容

当时我们也做了⼀个InfluxDB与Apache Druid的简单对⽐(这个对⽐是在2017年左右基于当 时的数据库版本功能给出的,随着功能的迭代演进,并不代表当前最新的情况)

特性 InfluxDB Apache Druid
数据集 时间戳列,维度列,指标列 时间戳列,维度列,指标列
存储引擎 TSM Tree列存储列存储 Bitmap索引 所有列LZ4,字符串->整数ID,Bitmap 压,类似(枚举)字典表
Sharding ShardId⽂件夹,时间范围 Segments⽂件:时间间隔
数据聚合 CQ:类似物化视图,多间隔粒 度,但⽹友爆出有坑Roll-up:数据导⼊时聚合,⼀种最细粒度, 数据减少百倍万倍
数据保留 策略RetentionPolicy AutoDropRule
数据写⼊ HTTPBatchFile Stream:Kafka,Spark HTTP(Tranquility)
数据查询 HTTP+类SQL HTTP+JSON和SQL(⾮常好⽤)
集群 企业版本⽀持,开源版本不⽀持原⽣⽀持,每种类型节点可任意横向扩 展 热升级,⽆单点故障

从功能特性来看,在2017年Apache Druid就已经具备了: 

  • ⾼可⽤容错架构 
  • ⾼并发查询能⼒ 
  • ⽔平扩容能⼒ 
  • 海量数据存储与查询能⼒ 

上述这⼏个关键能⼒是我们选择其作为应⽤性能监控系统数据存储的重要原因。 

同时,从技术栈⽣态来看,Apache Druid能够⽀持从Kafka实时导⼊数据,这使得我们能够重 ⽤现有的部分技术栈,使得监控数据能够以⼀种流式的⽅式在监控系统中的各个组件中处理, ⽽不是封闭在某⼀个系统中。

因此这⼀阶段我们搭建了以Apache Druid为核⼼的系统,架构如下: 

通过将监控指标数据整体解决方案从InfluxDB迁移到Apache Druid之后,我的团队已经平稳地⽀撑了⼤规模的应⽤监控至今。⽬前集群⽇均处理指标数据⼏千亿条,落到Apache Druid的存储量从最早的数MB到⽬前的⼏百TB。 

经验

作为应⽤监控系统中的核⼼数据库,⽬前其⽇均查询量超过百万次,平稳地⽀撑了实时监控、 指标数据分析、报警等功能运⾏。这个过程也是⼀个对Apache Druid认识逐渐加深过程,逐渐优化的 过程:从使⽤到解决必要的BUG再到扩展Apache Druid的功能满⾜特定需求。 

零数据丢失

在早期的Aapche Druid版本中,直接从Kafka拉取数据实现精确⼀次数据导入功能还未成熟,我们 采⽤的是其提供的Tranqulity组件的⽅案,即由Tranquility组件从kafka拉取指标数据,再Push 给Druid。 

Tranquility的⽅案有两个缺陷: 

1. Tranquility组件有⼀个数据时间窗的概念,即从Kafka拉取的性能指标数据中的时间戳超过 了这个数据处理时间窗⼝,数据将会被丢弃。 

性能指标数据从采集到被Tranquility拉取到,是⼀个流式处理的过程,中间如果有部分节 点故障可能会导致指标数据的延迟,进⽽就会导致该问题的产⽣。要解决这个问题,必须 定时从离线集群中重做Segment来实现数据互补。

2. Tranquility与Apache Druid节点的通讯异常会导致重试,⽆论是重试成功或失败,都可能 导致数据丢失或者重复数据 

随着Aapche Druid中Kafka Indexing Service组件的成熟,我们在2019年将Tranquility组件进 ⾏了替换,保证了数据的”零”丢失:即使指标数据在流式处理环节中某个节点处理延迟,仍然 可以实现数据不丢失和补⻬;Kafka Indexing Service摄⼊同时也实现了精确⼀次消费语义, 保证不会重复消费(Consume)。 

Segment文件碎片与整理

在Apache Druid中,Segment⽂件数是影响集群性能的⼀个因素。在Kafka摄⼊的模式下, Segment⽂件数和对应Topic的Partition分区数呈正相关,如下所示,我们看到同⼀个时间段有24个分区的Segment⽂件。 

同时在Apache Druid集群中,Middle Manager节点负责实时从Kafka摄⼊数据,这类节点本身负载就 相对较⾼,其查询能⼒相对Historical节点来说都有⼀定的差距。为了充分利⽤Historical节点 能⼒,部分摄⼊任务的持续时间(Duration)从默认的1⼩时设置成了半⼩时,这样可以使得数据 尽快落⼊到Historical节点中。但这样⼀来⼜加剧了Segment⽂件的碎⽚化。 

在下图中可以看到,最后⼀天的Segment⽂件数接近2000个。Segment⽂件过多会导致诸多 问题(下⾯会提到),也会对查询性能有⼀定影响。Apache Druid提供了Segment⽂件⾃动合并功 能,碎⽚化的⽂件将被合并,从图中也可以看到,开启合并后⽂件数⼤概被合并到了在800个 左右。

需要注意的是Segment⽂件合并后,合并前的⽂件并不会被⾃动删除,⽽是被标记成了 Unused。需要定时触发Kill Task删除,或者开启 druid.coordinator.kill.on 由 Coordinator定期清理Unused状态的Segment⽂件,防⽌数据库中Segment记录数过多影响性能。 

冷热分层和⾼可⽤

时序数据的⼀个重要特点就是越是离当前时间近,数据被查询的⼏率就越⾼。在我们应用监控的场景中,也满⾜这样⼀个规律。在实时监控时,我们通常关⼼的是最近半⼩时的指标数据。⽇常监控时发现 问题时,通常来说都需要当天解决,再结合历史数据对⽐,查询的数据通常在过去3天。通常 来说,1个⽉以前的数据很少被查询。 

因此利⽤Apache Druid提供的数据分层功能,将Historical节点分成hot和cold两类,hot节点保存最近两周的数据。考量到cold节点保存更⻓时间的数据,同时cold节点访问量极少,上⾯的保存的数据量⼜较⼤,在我们这个场景下,设置副本收益不⼤,所以cold节点只设置一份副本。即使是cold节点升级,也通常能够在数分钟内完全在线恢复服务,对⽤户的影响也微乎其微。 

⽽hot节点则不同,由于其访问量⾮常⾼,为了实现100%在线和平滑升级,我们为其设置了多个副本, 以确保服务的⾼可⽤性。 

⾃适应查询粒度 

指标监控查看中,通常会查看不同时间范围的指标数据趋势。从1分钟到30天不等。我们的指 标数据存储最⼩粒度是10s,如果查询30天的数据仍然按照10s的粒度返回数据,会导致返回 特别巨⼤的数据,消耗巨⼤的⽹络带宽和应⽤反序列化时间。 

为此我们根据查询的时间范围⾃适应调整时间粒度,在保证⼀定时间精读的下,提⾼聚合度。 查询时间范围越⼩,粒度越⼩,时间范围越⼤,粒度越⼤。这样就容易保证数据的上钻下探的 实时性。

时间范围 聚合粒度
1H内 10s
3H 30s
6H 30s
12H 1min
1D 2min
3D 10min
7D 30min

同时Apache Druid对时间字段的单⼀进⾏group by的聚合查询进⾏了优化,不会将这样⼀个查询转换成⼀个⽐较消耗资源的groupBy查询类型,⽽是⼀个效率更⾼的timeseries查询,以进⼀步保证查询效率。 

深度存储上云

Aapche Druid中对数据⽂件的存储提供了HDFS、Amazon S3对象存储、本地⽂件存储⼏种⽅式。国内⽤户在⽣产环境⼀般都以HDFS存储为主。在⼀个有⼤数据技术栈的组织中,使⽤ HDFS存储不存在太⼤的技术障碍。 

从应⽤监控系统这个场景出发,数据都是采⽤Kafka⽅式实时导⼊,并没有离线导⼊需求,因此仅仅为了使⽤HDFS⽽建⽴⼀套Hadoop集群实在有些暴殄天物。伴随这⼏年基础设施逐渐上云的浪潮,我们也将Apache Druid中的存储转向了阿⾥云的OSS(Object Storage Service)。 

由于阿⾥云OSS与Amazon S3的在客户端API⾼度类似,同时Apache Druid架构中提供了对深度存储 的及其良好的抽象与扩展⽀持, 使得能够在不侵⼊Druid核⼼框架的情况下⽐较容易的实现基于阿⾥云OSS的原⽣⽀持扩展插件,最终使得不再需要单独维护⼀套的Hadoop集群,极⼤的降低了端到端技术栈的复杂度和运维成本。 

SQL函数扩展

在应⽤监控系统中我们有⼀个名为Apdex的综合指标,它将多个指标通过某种计算公式综合成⼀个指标,以反映当前应⽤的健康程度。传统的实现⽅式是通过Apache Druid查询出相关数据之后,再按照Apdex指标的计算公式,计算出该指标结果,很显然这个计算过程不得不依赖⼀个前置的应⽤程序来完成。 

⼜⽐如,在应⽤监控系统中有⼤量的统计指标,在可视化层⾯需要格式化成类似128 MiB这种

带单位的⽅式显示,⽽不是显示成为134217728这样⼀个不方便理解的数字。在传统模式下,这些 需求都需要借助⼀个专⻔的查询应⽤程序来满足。

幸运的是,在Apache Druid中,⽆论是SQL查询还是Native查询层⾯,都有良好的扩展性,通过直接扩展 Apache Druid的聚合函数或格式化函数,在Apache Druid层⾯原⽣⽀持这些需求,能够极⼤地简化监控可视化 层⾯的复杂度。 

Apache Druid不足和使用建议

Apache Druid也并⾮⼀个完美的数据库,或多或少存在⼀些不足之处。 

功能性BUG

这并⾮是说Apache Druid的代码质量不好,通常情况下碰到BUG的情况还是⽐较少。相反Apache Druid的 源码有单元测试(UT, Unit Test)和集成测试(IT, Integration Test)两级⽤例,PR(Pull Request)的提交有严格的测试用例要求,总体质量还是⽐较可靠。但是由于Apache Druid可应⽤的场景太⼴,UT和UT难免会覆盖不全。 

所以如果在⽣产环境碰到了绕不开的BUG,通常来说还是需要⾃⼰动⼿解决,尽快根据⾃⼰业务需求做出调整,⽽不是坐等社区解决。 

举例来说,我们本打算在Historical节点上使⽤JBOD(Just a Bunch of Disks)模式磁盘,⽽不是⼀块单⼀的⼤容量磁盘。 多磁盘模式在我们的Kafka集群已经验证能够⾮常有效提⾼集群的吞吐量,有理由相信在Apache Druid的Historical节点中也能提升性能。在实践过程中,我们发现0.17/0.18版本虽然提供了多磁盘的⽀ 持,但由于BUG⽆法使⽤除默认模式外的其他磁盘选择策略(如Round Robin),所以我们不得 不⾃⾏修复了这个BUG; 

另一个例子,由于0.17版本重构了输⼊数据解析模块代码,导致⽆法解析Kafka中带缩进格式的 JSON消息,也迫使我们不得不暂时搁置升级计划转⽽先修复该问题,保障生产环境的平稳运行; 

外部依赖多,上⼿较慢 

  • Apache Druid依赖ZooKeeper提供高可用性, 以及关系型数据库(⽐如MySQL) 存放元数据
  • Apache Druid依赖Kafka集群 实现实时摄⼊数据
  • 如果要搭建集群,⼜要依赖Hadoop集群提供的HDFS或者其他存储系统 

⽬前来说,这块并没有什么优化。如果仅仅想评估或试⽤Apache Druid,虽然社区提供了Docker镜像可以⼀键启动服务。但是如果是⽣产环境部署,就必须要逐⼀部署其依赖的服务。 

运维略微复杂

与其他OLAP引擎或时序数据库相⽐,Apache Druid节点类型较多,不同节点⼜有很多不 同的配置参数,初始部署上⼿、集群参数调整优化有⼀定的复杂度。

这个问题建议多结合⾃⼰业务使⽤场景、硬件资源情况,再反复参考官⽅提供的 配置建议档,了解参数的含义之后再进⾏调整优化。 

4. 从代码的⻆度来看,其使⽤的是Google Guice轻量级依赖注⼊框架,该框架并不为国内使 ⽤Spring Boot/Spring Cloud框架的开发者熟悉,有⼀定的上⼿难度。同时其⼤量利⽤ Jackson的多态反序列化特性、迭代器模式、Lamda特性,使得初始代码阅读略有障碍。 

管理⾯性能还需要进⼀步提⾼ 

从我们跟踪社区的反馈以及结合我们⾃⼰的使⽤来看,当Segment⽂件数达到百万级别 时,会出现⼀些性能瓶颈,⽐如Historical节点启动慢、Coordinator节点负载⾼、Web Console响应慢。 

所幸这些问题都并⾮集中在数据统计查询路径上,⽽是集中在Segment⽂件的管理上。这些性能的瓶颈有的有规避⽅法,有的社区正在解决,有的已经有了PR正在等待检视合 ⼊。相信随着Apache Druid在不同公司不同业务的不断深⼊应⽤,这类问题会越来越少。建议多关注社区的issuePR在这些问题上的解决进展。 

尽管Apache Druid并⾮尽善尽美,但其作为⼀款时序数据库所表现出的良好可扩展性以及 已被证明成功的架构,使得我们有理由相信其⽅向路线不存在太⼤问题。根据DB Engine的 最新排名,Apache Druid⽬前在时序数据领域排名第六,GitHub上Star数接近1.1万, Contributor超过400,整体上还是以⼀种⽐较繁荣的⽅式在发展。只要⽤户在反馈、社区在繁 荣,具体的问题肯定会得到解决。 

后续展望

Apache Druid作为⼀款通⽤的分析型分布式时序数据库,主要功能已经满⾜了应⽤监控这种 场景的绝⼤部分需求。结合当前的应⽤⽽⾔,后续有⼏个⽅向值得继续深⼊: 

  1. 监控系统的指标类型有固定模式,常见的是counter和gauge两大类型。这两类指标需要对应使用Apache Druid的sum和last聚合函数来实现rollup或聚合查询。 目前Apache Druid尚未⽀持实时数据摄⼊阶段的last聚合函数,导致gauge类型的metrics定义时⽐较别 扭。针对此问题,我们已自行实现了摄⼊阶段的last聚合函数和Native查询,但SQL层⾯对last 的聚合函数⽀持还存在⼀定问题,涉及改动⽐较复杂,⽬前还未解决。 
  2. 我们使⽤Apache Druid来存储应⽤的性能指标,由于这些指标的dimension和metrics都有明显的模式,我们可以通过预先定义好Apache Druid的Schema来实现数据导入和聚合。 但是,如果希望借助Apache Druid来实现⽤户业务性能指标的监控,那么这种预先定义Schema的模式就存在⼀定的限制,因为业务层⾯的性能指标的dimension各不相同,通 常不具备⽐较通⽤的模式。 我们注意到,这两年Prometheus在监控这⼀特定领域占有了较⼤的市场份额,其⽆Schema设计用以支撑业务层面多变的可视化模式是⼀个重要因素。 
  3. Apache Druid当前的物化视图功能”基本为零”。社区有物化视图的插件,但只⽀持通过建 ⽴Map-Reduce任务通过Hadoop导入数据⽽不⽀持本地导入(Native Ingestion)。 就应⽤监控系统⽽⾔,应⽤、实例这两级维度特别适合应⽤物化视图。因为⼤部分的查询都是集中在“应⽤”这个单⼀维度上查看聚合指标。只有某个指标出现特定问题时,我们才需要下沉到“应⽤”、“实例”两个维度查看具体的指标。如果有完善的物化视图⽀持,那么应⽤级的数据统计分析性能将能更进⼀步得到提升。⽐如我们有的应⽤部署了上百个实例,如果能应⽤单⼀维度的物化视图⽀持,就基本上能把数据量 减少到原来的1%,查询时间将明显改善。

PPT版本下载: