Druid:实时分析数据存储


Apache Druid是一个开源数据库,专为低延迟的近实时和历史数据分析而设计,Druid 被NetflixConfluentLyft等公司用于各种不同的用例。

这个领域有Clickhouse、trino、kylin、bigquery、snowflake、Apache doris等很多竞争对手,但Druid 的速度惊人地快。

Druid 支持近实时和历史访问模式的目标使其独一无二,近乎实时的摄取允许像基于日志的生产警报(类似于Netflix的使用案例)这样的应用快速找到问题,同时也针对大量的历史数据进行执行。
相比之下,许多数据仓库产品是在重复的 "批处理 "基础上进行更新的,在指标被记录的时间和它们可用于分析的时间之间引入了滞后。

关键概念
1、分段Segments和数据源data source
分段是Druid的一个关键抽象:它们是一个不可变的(但有版本的)数据结构,存储着单个记录的集合。
分段的集合被组合成数据源,即Druid版本的数据库表。
每个分段都存储了在给定时间段内到达的所有记录,用于指定的数据源。

Druid部署在两种数据源中存储状态:

  1. MySQL,它包含配置和元数据,如现有网段的索引。
  2. Zookeeper,它存储了系统的当前状态(包括段的多个副本在系统中的机器上的分布情况)。

2、存储格式
如前所述,Druid的一个关键抽象是分段,一个用于存储数据的不可变的数据结构。每个段都与一个数据源(Druid对传统表格的概念)相关联,并包含特定时间段的数据。

存储在段中的数据由两种类型组成:维度和度量。维度是行聚合或过滤的值,而度量对应于数字数据(如计数)。

分段还包含一个版本号。如果一个段被改变了,版本号会被增加,并且段的新版本会被发布--这可能发生在一个先前被确定的段的延迟事件中。协调人节点通过指示历史节点获取新版本并放弃旧版本,来处理向新版本段的迁移。由于这种方法,据说Druid实现了多版本并发控制(MVCC)。这篇论文评论没有详细介绍MVCC,但是关于这个讲座中的一些想法有很好的资源。一个关键的想法是,数据有多个有效的版本(如快照),不同的读者可以查看一个数据集的不同版本。.

重要的是,分段以列而不是行来存储数据--这种方法被称为 "列式存储"。这种设计被用于其他一些数据库(如Redshift和Cassandra)和文件格式(如Parquet),因为它提供了性能优势。

例如,如果一个查询是选择一个列的子集,数据库只需要查询这些列的数据子集。基于行的解决方案将扫描每一行,选择出相关的列。虽然这两种扫描都会产生相同的结果,但基于行的扫描(几乎)保证会不必要地访问那些不需要回答查询的列,也不会出现在查询结果中。


架构
Druid通过摄取数据建立分段,然后在针对数据源的查询响应时访问这些分段。
Druid架构使用了四种类型的节点,较新版本的系统似乎打破了实现摄取数据和响应查询的功能TODO:

  • 实时节点、
  • 历史节点、
  • 经纪人节点
  • 协调者节点。

实时节点
实时节点有两个责任:从生产者那里摄取数据,以及实现用户对最近数据的请求的响应。

生产者向实时节点提供原始数据(如数据库中的行),或经过转换的数据(如流处理管道的输出)--一个常见的生产者模式依赖于Kafka主题。Kafka(或其他消息总线方法)有助于摄取的可用性和可扩展性--实时节点可以将它们所消耗的偏移量存储到流中,如果它们崩溃/重启,则重置为该偏移量。为了扩展摄取,多个实时节点可以读取同一消息总线的不同子集。

当一个实时节点从生产者那里消耗记录时,它会检查与记录相关的时间段和数据源,然后将传入的记录路由到具有相同(时间段、数据源)键的内存缓冲器。

除了摄取之外,每个实时节点对访问最近数据的查询作出回应。为了响应这些请求,各节点使用临时的内存索引进行扫描。

历史节点
历史节点从存储中读取不可变的分段,并对访问这些段的查询作出响应--协调者节点(在下一节中讨论)控制历史节点获取哪些段。
当一个历史节点成功下载一个分段时,它会向系统的服务发现组件(Zookeeper)宣布这一事实,允许用户查询访问该片分段。
不幸的是,如果Zookeeper离线,系统将无法提供新的分段--历史节点将无法宣布成功获取片段,因此Druid中负责查询数据的组件将无法转发查询。

使用不可变分段的决定简化了历史节点的实现:

  • 首先,它简化了系统的扩展--如果有许多请求涵盖一个段,更多的历史节点可以存储该段的副本,导致查询分散在集群中。
  • 其次,在段上操作,而不是在较低层次的抽象上操作,意味着历史节点可以简单地等待被告知有一个新的数据版本需要提供服务,而不是需要倾听段本身的变化。

协调节点
协调节点配置哪些片段被存储在历史节点上一个片段的多个副本可以存储在集群中的不同历史节点上,以扩大查询范围并增加冗余度。从阅读Druid文档来看,似乎有一个新的、独立的节点类型负责控制数据消化,称为 "Overlord霸王"。

为了做出决定,协调者节点从两个地方读取数据:MySQL和Zookeeper。

  • MySQL持久地存储关于段的宇宙的信息本质上是存储(时间段、数据源、版本)--虽然一个段可以有多个副本,但在MySQL数据库中会有一个条目来代表它的类型,以及关于每个段类型的相关元数据,比如一个具有特定配置的段应该在历史节点上保留多长时间。
  • Zookeeper存储了系统提供的所有网段的当前状态--实时节点和历史节点使用它来宣布哪些网段可用的变化。协调人节点也会对段进行负载平衡。在Druid文档中对平衡段的负载进行了更详细的讨论。.

论文中提到,集群中有多个正在运行的协调者节点,但每次只有一个 "领导者"--其他节点用于故障转移。为了扩展协调功能,听起来有可能创建多组协调者节点,每个节点负责数据集的一个分区,尽管我在论文中没有看到这方面的讨论。. 如果协调者节点变得不可用(无论是由于MySQL或Zookeeper问题),历史和实时节点将继续运行,但可能会变得过载(由于不运行负载平衡功能)。此外,该文件指出,这种故障模式导致新的数据变得不可用。

经纪人节点
最后,Broker节点接收来自外部客户的请求,从Zookeeper读取状态,并根据情况将请求转发给历史和实时节点的组合。经纪人节点也可以在本地缓存段,以限制未来访问相同数据的查询的出站段请求的数量。

如果Zookeeper变得不可用,那么经纪商就会使用他们的 "最后已知良好状态 "来转发查询。


结论
Druid是 "Lambda架构 "的最早实现之一,其中数据是由批处理和流式系统组合提供的。最近的 "Kappa "和 "Delta "架构的方法,特别是Databricks的Delta Lake,似乎是Druid最初提议的演变。


网友回应:
1、我们在工作中大规模使用 Druid,而且它实际上不需要太多维护。事实上,在大多数情况下,除了我们自己的错误配置导致的一些事件之外,它会嗡嗡作响。在 Kubernetes 上运行。最烦人的事情是调整段压缩。

2、从我见过的几个尝试使用 Druid 的项目中,我的感觉是大规模管理集群需要大量的基础架构开销/DevOps 支持,并且需要相当复杂的摄取管道才能正确加载数据格式。
有趣的是,我听说从这个角度来看,ClickHouse 更容易部署,具有类似的性能,但我很乐意让其他人对这些和类似的数据存储有看法/经验。

3、与其竞争对手Clickhouse 和 Pinot相比,设置 Druid 集群是最简单的。
特别是因为摄取直接进入 S3。我们并不真正担心备份(只处理 PG 备份)。
只要确保你的 ZK 快乐,一切都会好起来的。
Druid 的难点在于调整:
- 摄取:规范定义、压缩、分片策略、RAM 消耗等。
- 和查询性能:RAM 消耗、线程数、超时等。