4. Controller故障与选举异常
在 Kafka 集群中,Controller 是元数据的唯一权威写入者。它负责分区 Leader 选举、ISR 变更、Topic 创建删除、副本重分配等关键操作。无论是 ZooKeeper 模式下的 Controller Broker,还是 KRaft 模式下的 Active Controller,一旦 Controller 出现切换、脑裂或元数据不一致,集群会表现为分区不可用、Leader 频繁漂移、客户端超时甚至数据丢失。本节围绕四类典型异常展开:Controller 切换、脑裂、元数据不一致、KRaft 仲裁异常,并给出可落地的诊断与修复路径。
一、先分清两种 Controller 架构
ZooKeeper 模式下,Controller 由 Broker 通过抢占 /controller 临时节点选出,同一时刻只有一个 Controller,其余为候选。KRaft 模式下,Controller 是独立的 Raft 仲裁组(通常 3 或 5 个 Controller 节点),通过 Raft 协议选出 Active Controller,元数据以日志形式复制到多数派。两者的故障表现相似,但诊断入口完全不同:前者看 ZooKeeper 会话与 Controller Epoch,后者看 Raft Quorum、Leader Epoch 与元数据日志。
二、Controller 切换:从现象到根因
Controller 切换本身是正常机制,但频繁切换就是故障。典型现象是客户端看到分区 Leader 反复变化、__consumer_offsets 分区不可用、AdminClient 操作超时。诊断第一步是确认切换频率与原因。在 ZooKeeper 模式下,查看 Controller 日志中的 Controller moved to another broker 或 Resigned as controller,同时观察 ZooKeeper 的 /controller 节点变化。常见根因有三类:Broker 长时间 Full GC 导致 ZooKeeper 会话超时;Controller 与 ZooKeeper 之间网络抖动;ZooKeeper 集群自身不稳定。
修复动作要针对根因。若是 GC 问题,检查 kafka-server-gc.log 中的停顿时间,调整堆大小或 GC 策略;若是网络问题,用 mtr 或 tcpdump 确认丢包与延迟;若是 ZooKeeper 问题,检查其 fsync 延迟与磁盘 IO。一个容易被忽略的点是 zookeeper.session.timeout.ms 默认 18000ms,在高负载集群中可适当延长到 30000ms,但不要超过 ZooKeeper 的 maxSessionTimeout。
三、脑裂:最危险的状态
脑裂指两个 Broker 同时认为自己是 Controller。在 ZooKeeper 模式下,这通常由 ZooKeeper 会话假死引起:旧 Controller 的会话尚未过期,新 Controller 已被选出,旧 Controller 恢复后仍按自己的元数据发号施令。Kafka 通过 Controller Epoch 和 Fencing 机制防御:新 Controller 会递增 Epoch,Broker 拒绝旧 Epoch 的请求。但若旧 Controller 在 Epoch 递增前已发出部分请求,仍可能造成元数据冲突。
诊断脑裂的关键是比对 Controller Epoch。在 ZooKeeper 中执行 get /controller 查看 version 与 brokerid,同时在各 Broker 日志中搜索 Epoch 相关记录。若发现同一时间窗口内两个 Broker 都输出 Becoming the controller,即可确认。修复时优先隔离旧 Controller:停止其 Kafka 进程,确认 ZooKeeper 会话过期后再重启。切勿直接删除 /controller 节点,这会导致更长时间的不可用。
四、元数据不一致:分区状态错乱
元数据不一致表现为:ZooKeeper 中记录的 Leader 与 Broker 实际持有的 Leader 不同;ISR 列表与副本实际同步状态不符;分区处于 Offline 或 UnderReplicated 但副本实际正常。常见诱因是 Controller 切换过程中 LeaderAndIsr 请求丢失,或副本重分配任务未完成时 Controller 变更。
诊断时先用 kafka-topics.sh --describe 查看分区状态,再与 ZooKeeper 中 /brokers/topics/<topic>/partitions/<n>/state 的内容比对。若不一致,可触发一次 Leader 重选举:kafka-leader-election.sh --election-type PREFERRED --topic <topic> --partition <n>。对于卡住的副本重分配,检查 /admin/reassign_partitions 节点,必要时用 kafka-reassign-partitions.sh --verify 确认进度,再决定是否取消或继续。
五、KRaft 仲裁异常:Quorum 丢失
KRaft 模式下,Controller 仲裁组依赖多数派。3 节点仲裁组可容忍 1 节点故障,5 节点可容忍 2 节点。若多数派丢失,Active Controller 无法提交元数据日志,集群进入只读甚至不可用状态。现象是 kafka-metadata-quorum.sh --describe 显示 CurrentVoters 中多数节点 Lag 持续增大,或 LeaderId 为空。
诊断入口是 Controller 日志中的 NotLeader、Quorum lost、Epoch 相关记录,以及 kafka-metadata-quorum.sh --describe --status 的输出。修复时优先恢复故障节点:检查其 controller.quorum.voters 配置是否一致、metadata.log.dir 磁盘是否可写、节点间 9093 端口是否连通。若某节点元数据日志损坏,可从健康节点复制 __cluster_metadata 日志目录后重启,但必须确保复制的是已提交的日志段。
关键原则:任何 Controller 相关修复,都必须先确认当前 Controller Epoch 与仲裁状态,再执行操作。盲目重启 Broker 或删除 ZooKeeper 节点,可能把可恢复的切换异常升级为脑裂或元数据永久不一致。
六、诊断命令速查
# ZooKeeper 模式:查看当前 Controller
echo "get /controller" | zookeeper-shell.sh zk1:2181
# 查看 Controller Epoch 与切换记录
grep -E "Controller moved|Resigned|Becoming the controller" \
/var/log/kafka/controller.log
# KRaft 模式:查看仲裁状态
kafka-metadata-quorum.sh --bootstrap-server ctrl1:9093 \
--describe --status
# 查看元数据日志 Lag
kafka-metadata-quorum.sh --bootstrap-server ctrl1:9093 \
--describe --replication
# 触发分区 Leader 重选举
kafka-leader-election.sh --bootstrap-server broker1:9092 \
--election-type PREFERRED --topic orders --partition 0
# 校验副本重分配进度
kafka-reassign-partitions.sh --bootstrap-server broker1:9092 \
--verify --reassignment-json-file reassign.json
七、修复后的验证闭环
修复动作完成后,必须验证三件事:Controller 是否稳定(观察 10 分钟内无再次切换);元数据是否一致(kafka-topics.sh --describe 与 ZooKeeper/KRaft 状态比对);客户端是否恢复(生产消费延迟回到基线)。建议在变更窗口内持续采集 ControllerEpoch、ActiveControllerCount、MetadataQuorumLag 三个指标,形成趋势图。若 30 分钟内指标平稳,方可判定修复完成。
最后强调一个运维习惯:Controller 故障往往不是孤立事件,而是集群资源瓶颈、网络抖动或配置不当的集中体现。每次 Controller 异常都应记录时间线、Epoch 变化、根因与修复动作,逐步形成本集群的故障模式库,这比任何单次修复都更有长期价值。