Druid 架构总览:六大核心组件及其职责

说实话,我第一次接触 Druid 的时候,也被它那一堆角色搞晕过。Overlord、Coordinator、Broker、Historical、MiddleManager、IndexingService…… 名字都挺唬人。但后来我在生产环境里摸爬滚打了一年多,慢慢就摸清了它们的脾气。

说白了,Druid 的架构设计思路很清晰——把查询和写入彻底拆开,再把各自的任务细分给不同的组件。你想想看,一个系统既要扛住海量实时写入,又要保证秒级查询,不分家怎么行?

下面我就按自己的理解,把这六个组件拆开来讲。我会结合一些实际踩过的坑,帮你快速建立整体认知。

2.1 组件全景图

先上一张我手绘的架构图。这张图我每次给团队新人培训都会用,因为它把数据流和控制流分得很清楚。

Druid 六大核心组件架构图 数据写入链路 Indexing Service MiddleManager Historical 控制/调度层 Overlord Coordinator 查询链路 Broker Historical 数据流与控制流说明 • 实线箭头 = 数据写入路径:Indexing Service → MiddleManager → Historical • 虚线箭头 = 查询路径:Broker 向 Historical 分发查询请求 • Overlord 管理 MiddleManager 的任务分配,Coordinator 管理 Historical 的段加载 • 所有组件通过 ZooKeeper 或 HTTP 进行心跳与元数据同步

这张图里,我特意用不同颜色区分了三条线:蓝色→红色→绿色是数据写入路径,青色→绿色是查询路径,紫色和橙色是控制调度层。记住这个颜色逻辑,后面就好理解了。

2.2 Overlord —— 任务调度的大脑

Overlord 这个名字起得好,它确实是整个集群的「霸主」。它的核心职责就一句话:决定谁来干活。

具体来说,Overlord 负责:

  • 接收 Indexing Service 提交的索引任务
  • 把任务分配给合适的 MiddleManager
  • 监控任务执行状态,失败时重试
  • 管理任务的优先级和队列
我的经验: 早期版本 Overlord 是单点,挂掉整个集群就写不了数据。后来 Druid 支持了 Overlord 高可用部署,我建议至少配两个节点,配合 ZooKeeper 做 leader 选举。我曾经因为只部署了一个 Overlord,半夜被报警电话叫醒…… 从那以后,高可用就成了我的标配。

2.3 Coordinator —— 数据分布的管家

Coordinator 和 Overlord 经常被搞混。我习惯这么区分:Overlord 管「写」,Coordinator 管「存」。

Coordinator 的日常工作:

  • 监控所有 Historical 节点的负载情况
  • 决定每个 segment 应该放在哪个 Historical 节点上
  • 触发 segment 的合并、复制、淘汰
  • 保证数据的副本数满足配置要求

Coordinator 每隔一段时间(默认 60 秒)会跑一次「集群状态同步」流程。它会读取元数据库中的 segment 信息,然后对比当前集群的实际分布,做出调整决策。

避坑指南: Coordinator 的调整是异步的,不会立即生效。我曾经遇到过一个问题:刚删了一批 segment,但查询还是能查到旧数据。后来才发现是 Coordinator 还没触发淘汰。解决方案是手动调用 Coordinator 的 API 触发一次立即同步。

2.4 Broker —— 查询的入口与分发器

Broker 是用户接触最多的组件。你发的每一个 SQL 查询,第一个到达的就是 Broker。

Broker 的工作流程:

  1. 接收客户端查询请求
  2. 解析查询,确定需要访问哪些 segment
  3. 向对应的 Historical 节点分发子查询
  4. 合并所有子查询结果,返回给客户端

Broker 最核心的能力是查询路由。它维护了一张 segment 到 Historical 节点的映射表,知道哪个数据在哪个节点上。这样它就能把查询只发到有数据的节点,而不是全集群广播。

注意: Broker 本身不存储任何数据,它只是一个「聪明的中间人」。如果 Broker 挂了,整个集群就查不了数据。我建议至少部署 2 个 Broker,前面挂一个负载均衡器。另外,Broker 的内存要配大一点,因为它需要缓存 segment 的元数据信息。

2.5 Historical —— 数据的存储与查询执行者

Historical 是真正「扛数据」的组件。它负责:

  • 加载和管理 segment 文件
  • 处理 Broker 下发的查询请求
  • 定期向 Coordinator 汇报自身状态
  • 执行 segment 的合并与清理

Historical 的设计很有意思。它把 segment 加载到内存或 mmap 映射文件中,查询时直接在内存里跑。这就是 Druid 查询快的原因之一——数据预热到内存,避免磁盘 I/O。

我个人的经验是,Historical 的节点数量决定了集群的查询吞吐能力。如果查询压力大,优先加 Historical 节点。但要注意,Historical 的 segment 分布要均匀,否则会出现「热点节点」。

配置项 推荐值 说明
druid.processing.numThreads CPU 核数 - 1 查询处理线程数,留一个给系统
druid.server.http.numThreads CPU 核数 × 2 HTTP 请求处理线程
druid.segmentCache.locations 磁盘总空间的 80% segment 缓存目录,别塞满

2.6 MiddleManager —— 实时数据的加工厂

MiddleManager 是实时写入链路中的关键角色。它负责:

  • 启动和管理 Peon 进程(实际干活的工人)
  • 接收 Overlord 分配的任务
  • 将实时数据转换为 segment
  • 把 segment 推送到 Deep Storage 并通知 Historical 加载

每个 MiddleManager 可以同时运行多个 Peon,每个 Peon 处理一个数据源的一个时间窗口。Peon 是真正的「临时工」——任务完成后就销毁。

调优建议: MiddleManager 的 druid.worker.capacity 参数控制它能同时运行多少个 Peon。我一般按 CPU 核数来设,每个 Peon 占用 1 个核。设太多会导致 CPU 争抢,设太少又浪费资源。另外,Peon 的 JVM 堆内存要单独配,别和 MiddleManager 本身抢内存。

2.7 Indexing Service —— 任务的提交入口

Indexing Service 其实不是一个独立的进程,而是一组 API 和任务类型的统称。它负责:

  • 接收用户提交的索引任务(批量导入、实时流、原生批处理等)
  • 将任务提交给 Overlord
  • 返回任务状态给调用方

常见的任务类型包括:

  • KafkaIndexTask:从 Kafka 消费实时数据
  • BatchIndexTask:批量导入离线数据
  • CompactionTask:合并小 segment

Indexing Service 的 API 通常暴露在 Overlord 的 HTTP 端口上。你可以通过 RESTful 接口提交任务、查询进度、取消任务。

核心要点: Indexing Service 是 Druid 的「入口」,但它不干活。它只负责把任务描述转成 Overlord 能理解的格式。真正的数据处理逻辑在 MiddleManager 的 Peon 里。这个分层设计让 Druid 的写入链路非常灵活——你可以随时替换任务类型,而不影响底层执行引擎。

2.8 组件协作流程总结

最后,我用一个简单的流程来总结这六个组件是怎么配合的:

  1. 提交任务:用户通过 Indexing Service API 提交一个 Kafka 实时摄入任务
  2. 分配任务:Overlord 收到任务,查看哪个 MiddleManager 有空闲 Peon,把任务分配过去
  3. 执行任务:MiddleManager 启动一个 Peon,Peon 从 Kafka 拉数据,实时构建 segment
  4. 发布 segment:Peon 把 segment 写到 Deep Storage(如 HDFS、S3),然后通知 Coordinator
  5. 加载 segment:Coordinator 告诉某个 Historical 节点去加载这个 segment
  6. 提供服务:Historical 加载 segment 到内存,Broker 就可以把查询路由到这个节点了

整个过程环环相扣,每个组件各司其职。Overlord 管调度,Coordinator 管分布,Broker 管查询,Historical 管存储,MiddleManager 管加工,IndexingService 管入口。记住这个分工,后面学具体配置和调优的时候就不会迷路了。