架构总览:Broker、Controller 与元数据管理
上一篇确立了追加日志是 Kafka 的核心抽象。这一篇展开这根日志运行在什么样的集群架构上。
一个常见的简化说法是"Kafka 是一组 broker"。更准确的描述是:Kafka 集群由若干 broker 进程组成,其中一个 broker 承担 controller 角色,负责元数据管理和 partition leader 选举。
本文只抓一个问题:从 topic 到磁盘目录,数据和元数据分别如何组织。
Broker:存储日志并服务读写请求
Broker 是 Kafka 集群中的工作进程。每个 broker 做两件事:
- 存储分配给它的 partition 的日志文件(.log、.index、.timeindex)。
- 服务 produce 请求(追加记录到日志尾部)和 fetch 请求(按 offset 从日志中读取记录)。
一个 broker 可以同时持有多个 topic 的多个 partition。每个 partition 在磁盘上是一个独立的目录,目录名格式为 {topic名}-{partition号}。
1 | |
每个 partition 目录内部的文件组织将在第 02 篇展开。这里只需要记住一个映射关系:
1 | |
Topic、Partition 与 Log 的映射
一个 topic 是一个逻辑名称。创建 topic 时指定 partition 数量,每个 partition 是一根独立的追加日志。Partition 之间没有全局 offset 关系——partition 0 的 offset 5 和 partition 1 的 offset 5 是完全无关的两条记录。
Partition 是 Kafka 并行度的基本单位:
- produce 端:不同 partition 可以由不同 broker 并行接收写入。
- consume 端:同一个 consumer group 内,每个 partition 只分配给一个 consumer 实例。
这意味着一个 topic 的 partition 数量决定了该 topic 的消费并行度上限。
1 | |
每个 partition 有一个 leader 副本和零到多个 follower 副本。Produce 和 fetch 请求只发往 leader 所在的 broker(截至 Kafka 2.4+,follower 也可以服务 fetch 请求,但需要显式配置 replica.selector.class)。
Controller:集群的元数据中枢
Kafka 集群中有且只有一个 broker 在任意时刻担任 controller 角色。Controller 不是一个独立的进程——它是某个 broker 进程内的一个角色模块。
Controller 的职责包括:
- partition leader 选举:当某个 partition 的 leader 所在 broker 下线时,controller 从 ISR(In-Sync Replicas)列表中选出新 leader。
- partition 分配:当创建新 topic 或扩展 partition 时,controller 决定每个 partition 的副本放在哪些 broker 上。
- 元数据传播:controller 将 partition 的 leader 分布、ISR 列表等元数据推送给集群中的所有 broker。
1 | |
客户端发起 Metadata Request 时,可以发给任意一个 broker。每个 broker 本地维护一份元数据缓存,这份缓存由 controller 通过 UpdateMetadata 请求保持同步。客户端拿到 metadata response 之后,就知道每个 partition 的 leader 在哪个 broker 上,后续的 produce/fetch 请求直接发给对应的 leader broker。
ZooKeeper 模式与 KRaft 模式
Controller 的选举和元数据存储有两种模式,取决于 Kafka 的部署方式。
ZooKeeper 模式(Kafka 3.3 之前的默认模式):
- Controller 选举通过 ZooKeeper 的临时节点(ephemeral node)实现:所有 broker 竞争创建
/controller节点,创建成功的 broker 成为 controller。 - 元数据存储在 ZooKeeper 的 znode 路径中:
/brokers/ids/存储 broker 注册信息,/brokers/topics/存储 topic 配置和 partition 分配。 - Broker 下线时,ZooKeeper 通过 session 过期检测到变化,通知 controller 触发 leader 重新选举。
KRaft 模式(Kafka 3.3+ 引入,3.5+ 生产就绪,自 Kafka 4.0 起 ZooKeeper 模式不再支持):
- 不依赖外部 ZooKeeper 集群。元数据管理由一组专门的 controller 节点通过 Raft 共识协议完成。
- 元数据存储在内部 topic
__cluster_metadata中,这个 topic 本身就是一根追加日志——元数据的变更记录以日志的形式持久化,与 Kafka 数据存储的核心抽象一致。 - Controller 节点可以与 broker 共进程(combined 模式),也可以独立部署(isolated 模式)。生产环境推荐 isolated 模式,让 controller 的资源消耗不影响数据面。
1 | |
两种模式下 broker 服务 produce/fetch 请求的方式完全相同。区别仅在于元数据的存储和 controller 的选举机制。
实验:启动 3-broker 集群并观察日志目录
以下实验使用 KRaft 模式启动一个 3-broker 集群,创建一个 6 partition 的 topic,然后观察日志目录的分布。
准备三份配置文件,关键差异参数:
1 | |
格式化并启动:
1 | |
创建 topic 并观察分布:
1 | |
输出类似:
1 | |
检查每个 broker 的日志目录:
1 | |
可以验证:每个 broker 上出现的 partition 目录数量与 --describe 输出中该 broker 出现在 Replicas 列表中的次数一致。Leader 和 follower 的日志目录结构相同——区别仅在于 leader 接受写入,follower 从 leader 拉取数据。
模式提炼
Kafka 的 controller 是一种集中式元数据管理器(centralized metadata controller)模式。这个模式在分布式系统中反复出现:
- 一个专门的角色负责全局决策(leader 选举、分片分配、元数据一致性)。
- 数据面节点从元数据管理器获取路由信息,直接点对点传输数据。
- 元数据管理器的可用性通过冗余(多节点选举或 quorum)保证。
这个模式把"全局协调的一致性需求"集中在一个小集群上,让数据面节点可以无状态地横向扩展。代价是元数据管理器成为潜在的单点——需要额外的选举和容错机制来保护。
工程迁移表
| 维度 | Kafka Controller | RocketMQ NameServer | HDFS NameNode |
|---|---|---|---|
| 角色 | partition leader 选举、元数据管理 | 路由信息管理(不做选举) | block 映射、namespace 管理 |
| 选举机制 | ZK 临时节点 / KRaft Raft 选举 | 无选举,NameServer 对等部署 | HA:ZK 选举或 Observer 模式 |
| 元数据存储 | ZK znode / __cluster_metadata topic | 内存(broker 定期上报) | fsimage + editlog |
| 元数据一致性 | 强一致(ZK/Raft) | 最终一致(各 NameServer 独立接收心跳) | 强一致(HA editlog 同步) |
| 数据面路由 | client 缓存 metadata,直连 leader broker | client 缓存路由表,直连 broker | client 从 NameNode 获取 block 位置,直连 DataNode |
| 单点风险 | 是(通过选举恢复) | 否(对等部署) | 是(通过 HA 恢复) |
常见误解
误解一:“Controller 是一个独立的服务。”
在 ZooKeeper 模式下,controller 是某个 broker 进程内的角色,与普通 broker 共享同一个 JVM。在 KRaft 模式的 combined 部署下同理。只有 KRaft 的 isolated 模式才会将 controller 部署为独立进程,但即便如此,controller 进程运行的也是 Kafka broker 的代码,只是 process.roles 配置不同。
误解二:“ZooKeeper 存储消息。”
ZooKeeper 只存储集群元数据(broker 注册信息、topic 配置、partition 分配、consumer group offset 的旧版存储)。消息数据存储在 broker 本地磁盘的日志文件中,不经过 ZooKeeper。ZooKeeper 的数据量通常在 MB 级别,而 Kafka 的消息存储可以达到 TB 级别。
误解三:“所有请求都经过 controller。”
Produce 和 fetch 请求直接发送给 partition 的 leader broker,不经过 controller。Controller 只处理管理面操作:leader 选举、partition 重分配、topic 创建/删除。数据面的吞吐不受 controller 性能制约。
练习
-
在上面的 3-broker 实验中,kill 掉其中一个 broker 进程,然后再次执行
kafka-topics.sh --describe。观察哪些 partition 的 leader 发生了变化,ISR 列表有何变化。重启该 broker 后再次观察 ISR 的恢复过程。 -
使用
kafka-metadata.sh工具(KRaft 模式下可用)查看__cluster_metadatatopic 的内容,找到 topic 创建和 partition 分配的记录。思考这些记录与 ZooKeeper znode 的对应关系。 -
对比 RocketMQ 的 NameServer 设计:RocketMQ 选择让所有 NameServer 对等、无选举、最终一致。Kafka 选择单 controller 加强一致性选举。列出两种方案各自的优势和代价。
系列导航
| 序号 | 主题 |
|---|---|
| 00 | 导读:为什么 Kafka 的核心是一根日志 |
| 01 | 架构总览:Broker、Controller 与元数据管理 |
| 02 | 日志存储:Segment、Index 与零拷贝 |
| 03 | Producer 内部机制:攒批、分区与 acks |
| 04 | 幂等 Producer 与序列号:消息不重不丢的第一层 |
| 05 | Consumer Group 协议:分配、重平衡与静态成员 |
| 06 | Offset 管理:提交、重置与消费语义 |
| 07 | 副本与 ISR:高可用的代价和折中 |
| 08 | Controller 与 KRaft:从 ZooKeeper 到内置共识 |
| 09 | Exactly-Once 与事务:跨 partition 的原子写入 |
| 10 | 日志压缩:把 topic 当 KV 表用 |
| 11 | Kafka Streams:在日志之上构建流处理 |
| 12 | Kafka Connect:标准化的数据管道 |
| 13 | Schema Registry 与数据治理:给消息加上契约 |
| 14 | 性能模型:吞吐、延迟与调优思路 |
| 15 | 安全体系:认证、授权与加密 |
| 16 | 生产运维:集群扩缩、监控指标与故障排查 |
| 17 | Kafka 与 RocketMQ:两种消息系统的设计选择 |
参考资料
- Apache Kafka Documentation — Design: Replication. https://kafka.apache.org/documentation/#replication
- KIP-500: Replace ZooKeeper with a Self-Managed Metadata Quorum. https://cwiki.apache.org/confluence/display/KAFKA/KIP-500%3A+Replace+ZooKeeper+with+a+Self-Managed+Metadata+Quorum
- Apache Kafka Documentation — KRaft Configuration. https://kafka.apache.org/documentation/#kraft
- Neha Narkhede, Gwen Shapira, Todd Palino. Kafka: The Definitive Guide. O’Reilly. Chapter 5: Kafka Internals.
- Apache Kafka Source Code —
kafka.controller.KafkaController. https://github.com/apache/kafka
