2. Confluent Kafka架构深度解析
要设计一个高可用的 Confluent Kafka 集群,必须先看清它内部“谁在管什么、谁在存什么、谁在决定什么”。Kafka 的高可用并不是单一机制的结果,而是由 Broker 的数据承载、Controller 的元数据决策、分区与副本的分布策略共同叠加出来的。本节从架构角色出发,逐层拆解这些组件在高可用中的真实职责,并对比 ZooKeeper 模式与 KRaft 模式的差异。
一、Broker:数据与请求的实际承载者
Broker 是 Kafka 集群中真正存储消息、响应生产与消费请求的进程。每个 Broker 有唯一的 broker.id,并监听客户端流量与集群内部流量。它负责:接收 Producer 写入、把消息追加到分区日志段(Log Segment)、响应 Consumer 拉取、参与副本同步、向 Controller 汇报自身状态。
Broker 本身是“无状态决策、有状态存储”的:它不决定谁是 Leader,但保存分区数据;它不管理全局元数据,但缓存元数据用于路由请求。因此单个 Broker 宕机时,只要其上的分区在其他 Broker 有副本,集群仍可继续读写。这就是高可用的第一层保障——数据冗余。
二、Controller:集群的大脑
Controller 是集群中负责元数据决策的特殊 Broker(在 ZooKeeper 模式下由选举产生,在 KRaft 模式下由 Controller Quorum 承担)。它的核心职责包括:
- 分区 Leader 选举:当某个分区 Leader 所在 Broker 失效时,从 ISR(In-Sync Replicas)中选出新 Leader。
- 副本分配与重分配:决定新 Topic 的分区副本落在哪些 Broker 上。
- Broker 上下线处理:感知 Broker 加入或离开,触发分区迁移或 Leader 切换。
- 元数据变更广播:把 Topic 创建、分区扩展、配置变更等推送给所有 Broker。
Controller 一旦不可用,集群不会立刻停止读写(已有 Leader 仍可服务),但无法处理 Broker 故障后的 Leader 切换、无法创建 Topic、无法做副本重分配。因此 Controller 的高可用是“运维可用性”的关键,而不是“数据可用性”的第一道防线。
三、ZooKeeper 模式 vs KRaft 模式
传统 Kafka 依赖外部 ZooKeeper 集群存储元数据:Broker 注册、Controller 选举、Topic 配置、ACL、分区状态等都放在 ZK 的 znode 中。Controller 从 ZK 读取并监听变化,再向 Broker 推送。这种模式的问题在于:元数据规模受 ZK 限制、Controller 故障恢复慢、需要额外维护一套 ZK 集群、元数据变更路径长。
KRaft(Kafka Raft)模式把元数据管理内置到 Kafka 自身:由一组 Controller 节点组成 Raft Quorum,使用内部 __cluster_metadata 主题以日志方式复制元数据。Broker 不再依赖 ZK,Controller 选举、元数据变更都通过 Raft 协议完成。其优势是:元数据变更更快、Controller 故障切换更短、集群规模上限更高、部署组件更少。
| 对比维度 | ZooKeeper 模式 | KRaft 模式 |
|---|---|---|
| 元数据存储 | 外部 ZK znode | 内部 __cluster_metadata 日志 |
| Controller 选举 | ZK 临时节点抢占 | Raft Quorum 选举 |
| 故障切换速度 | 较慢(秒级到十秒级) | 较快(通常亚秒到秒级) |
| 运维组件 | Kafka + ZooKeeper | 仅 Kafka |
| 元数据规模 | 受 ZK 限制 | 可支撑更大分区数 |
对于新建的高可用集群,Confluent 与 Apache Kafka 社区均推荐 KRaft 模式;ZooKeeper 模式主要用于存量集群或与旧版本兼容的场景。
四、元数据管理:从 ZK 到 Raft 日志
元数据的本质是“集群当前长什么样”:有哪些 Topic、每个 Topic 有多少分区、每个分区的 Leader 是谁、ISR 有哪些、副本分布在哪、配置是什么。在 ZooKeeper 模式下,这些信息分散在多个 znode 路径下,Controller 通过 Watch 机制感知变化。在 KRaft 模式下,所有元数据变更被序列化为记录,追加到 __cluster_metadata 日志,由 Controller Quorum 复制,Broker 作为 Follower 消费该日志来更新本地元数据缓存。
这种“日志即元数据”的设计带来一个重要特性:元数据变更具有顺序性和可重放性。Controller 切换后,新 Controller 可以从日志中恢复完整状态,而不需要像 ZK 模式那样重新从多个 znode 拼装。这直接缩短了故障恢复时间,也提升了大规模集群的稳定性。
五、分区与副本:高可用的核心机制
Topic 被拆分为多个 Partition,每个 Partition 是一个有序、不可变的日志。Partition 是 Kafka 并行读写的基本单位,也是副本复制的基本单位。每个 Partition 有 1 个 Leader 和 0 到多个 Follower。Producer 只写 Leader,Consumer 默认只读 Leader;Follower 主动向 Leader 拉取数据保持同步。
ISR(In-Sync Replicas)是“与 Leader 保持同步的副本集合”。只有 ISR 中的副本才有资格被选为新 Leader。当 acks=all 且 min.insync.replicas 满足时,Producer 写入需要 ISR 中足够副本确认,才能保证消息不丢。若 ISR 收缩到低于 min.insync.replicas,写入会被拒绝,这是用可用性换一致性的保护机制。
副本因子(replication.factor)决定每个分区有多少份数据。常见生产建议是 3 副本,配合 min.insync.replicas=2,可以容忍 1 个 Broker 故障而不丢数据、不停写。若副本因子为 1,则该分区所在 Broker 宕机即数据不可用,高可用无从谈起。
六、高可用设计中的关键参数与判断
把上述机制落到生产部署,需要关注几个参数组合:
replication.factor=3:每个分区 3 副本,容忍 1 个 Broker 故障。min.insync.replicas=2:ISR 至少 2 个副本才允许 acks=all 写入,防止单副本写入后丢数据。unclean.leader.election.enable=false:禁止非 ISR 副本当选 Leader,避免数据不一致。default.replication.factor与num.partitions:控制新建 Topic 的默认副本与分区数。
判断一个集群是否真正高可用,可以按以下顺序检查:Broker 数量是否大于副本因子;Controller Quorum 是否为奇数且跨故障域;ISR 是否长期稳定;是否存在副本因子为 1 的 Topic;min.insync.replicas 是否与副本因子匹配。任何一项不满足,都可能在单点故障时演变为数据不可用或数据丢失。
总结来看,Broker 提供数据承载与冗余,Controller 提供元数据决策与故障切换,KRaft 把元数据管理内化为日志复制,分区与副本则把“冗余”落到每个 Topic 的每个分区上。四者叠加,才构成 Confluent Kafka 高可用集群的完整底座。