凌晨告警:Kafka Controller 节点脑裂,我把集群从不可用状态救回来的 40 分钟实录
凌晨告警Kafka Controller 节点脑裂我把集群从不可用状态救回来的 40 分钟实录凌晨 2:47PagerDuty 又响了。这次不是消费延迟不是磁盘满了而是整个 Kafka 集群直接挂了——Producer 写不进去Consumer 拉不出来监控大屏一片红。我爬起来连上 VPN看了眼日志心里一沉Controller 选举死循环三个 broker 都在争着当老大又都当不上。脑裂了。2:47 到底发生了什么我们集群是 3 个 brokerKRaft 模式没有 ZooKeeper版本 Kafka 3.6。平时挺稳的已经跑了 8 个月。出问题前没有任何发布变更就是正常的业务高峰。但刚好有一台 brokerbroker-2的 SSD 在 2:45 触发了 firmware 级别的瞬时掉盘持续 3 秒后又恢复。就这 3 秒broker-2 被其他两个节点标记为不可靠触发了 Controller 重新选举。但 broker-2 恢复后发现自己的 epoch 比新选举的 Controller 还高因为掉盘前它刚 bump 了 epoch于是它拒绝承认新 Controller自己又发起了一轮选举。三个节点三个 epoch谁都说自己是正的。这就是 KRaft 模式下最恶心的故障之一Controller 脑裂导致的元数据不一致。排查时间线2:47— 告警触发Kafka 集群可用性降到 0%2:52— 我登上 broker-0先看server.log[2026-07-16 02:45:12,345] ERROR [Controller 2] Unable to relinquish controller role [2026-07-16 02:45:12,678] INFO [Controller 0] Starting controller election with epoch 47 [2026-07-16 02:45:12,890] WARN [Controller 1] Rejected controller request from 2 with stale epoch 46epoch 打架了。broker-2 是 46broker-0 和 broker-1 已经走到 47但 broker-2 不认。2:58— 检查 KRaft 的元数据一致性状态# 看每个节点的元数据日志最后一条记录kafka-metadata-quorum --bootstrap-server localhost:9092\--command-config admin.conf describe--status输出显示三个节点的high-water-mark不一致broker-0: 1847293broker-1: 1847293broker-2: 1847289差 4 条记录。就是这 4 条让 broker-2 觉得自己更新的拒绝 follower。3:05— 决定手动降级 broker-2 的 epoch让它强制同步 broker-0 的元数据。但 Kafka 3.6 没有直接重置 epoch 的命令。我查了一圈发现只能走元数据快照替换这条路。修复方案三步把集群拉回来第一步隔离故障节点先把 broker-2 从集群流量里摘掉避免它继续干扰选举# 在 broker-2 上停止 Kafka 进程systemctl stop kafka# 从其他节点确认它已下线kafka-metadata-quorum --bootstrap-server broker-0:9092\--command-config admin.conf describe--status确认 broker-2 状态变成FOLLOWER缺失只剩下 broker-0 和 broker-1 在跑。但问题来了2 个节点达不到 KRaft 的 quorum 要求3 节点需要至少 2 个活跃但选举需要多数同意。broker-0 和 broker-1 之间没问题但broker-2的残留状态导致它们还在等 broker-2 的响应。实际上KRaft 的 quorum 是(n1)/23 节点集群需要 2 个节点就能继续工作。但前提是这两个节点要达成共识。问题是 broker-0 和 broker-1 已经达成共识了都是 epoch 47但 broker-2 的网络恢复后还在发 epoch 46 的请求。这些请求被 firewall 拦住了但 Kafka 内部的拒绝处理逻辑有个 bug每收到一个拒绝Controller 就会重试一次选举。所以集群在能工作和重新选举之间反复横跳。第二步清理 broker-2 的元数据状态最安全的做法是让 broker-2 以全新的 follower 身份重新加入集群。# 在 broker-2 上备份元数据日志mv/var/lib/kafka/raft-logs /var/lib/kafka/raft-logs.bak.$(date%s)# 清空元数据目录让它重新从 snapshot 恢复rm-rf/var/lib/kafka/raft-logs/*# 复制 broker-0 的最新 snapshot 过来rsync-avz--progress\rootbroker-0:/var/lib/kafka/raft-logs/\/var/lib/kafka/raft-logs/这里有个坑直接复制元数据日志文件要求 broker-2 的node.id配置不变。KRaft 的元数据日志里嵌了 node.id如果我改 node.id 就会报错Inconsistent node ID。所以我保持 broker-2 的node.id2不变只重置日志。第三步重启并验证# 在 broker-2 上启动 Kafkasystemctl start kafka# 观察日志确认它从 follower 开始同步tail-f/var/log/kafka/server.log|grep-E(became follower|catching up|high-water-mark)大概 2 分钟后broker-2 的 high-water-mark 追上 broker-0[2026-07-16 03:12:45,123] INFO [RaftManager nodeId2] Became follower for leader 0 [2026-07-16 03:14:02,567] INFO [RaftManager nodeId2] High watermark updated to 1847293元数据一致了。Controller 稳定在 broker-0 上不再跳。3:27— 验证 Producer 和 Consumerkafka-producer-perf-test--topictest-recovery\--num-records10000--record-size1024\--throughput-1--producer-propsbootstrap.serversbroker-0:9092 kafka-consumer-perf-test--topictest-recovery\--messages10000--bootstrap-server broker-0:9092吞吐正常没有丢数据。复盘为什么会脑裂KRaft 模式虽然干掉了 ZooKeeper但分布式共识的本质没变。我们这次踩了三个坑1. 磁盘瞬时故障被低估了SSD firmware 的 3 秒掉盘在 OS 层可能只表现为一次 iostat 抖动但 Kafka 的 KRaft 层会把它当作节点不可靠信号。我们之前只监控了磁盘使用率没监控disk io timeout和firmware error。2. Controller 选举的拒绝处理有缺陷Kafka 3.6.0 有个已知问题KAFKA-14500当 follower 收到 stale epoch 拒绝时会触发不必要的重新选举。这个问题在 3.6.1 修复了但我们还在 3.6.0。3. 没有元数据快照的自动备份策略如果 broker-0 的 snapshot 也坏了我就只能走更暴力的日志截断方案。建议每个 broker 定期把/var/lib/kafka/raft-logs/快照到对象存储。监控加固 checklist修完后我补了三个监控规则# 1. 监控 Controller 选举频率-alert:KafkaControllerElectionFrequentexpr:increase(kafka_controller_controller_state_change_total[5m])2for:1mlabels:severity:criticalannotations:summary:Kafka Controller 选举过于频繁# 2. 监控 KRaft 元数据水位差-alert:KRaftMetadataLagexpr:|max(kafka_server_raft_log_high_watermark) - min(kafka_server_raft_log_high_watermark) 10for:2mlabels:severity:warningannotations:summary:KRaft 元数据日志存在明显滞后# 3. 监控磁盘 firmware 错误-alert:DiskFirmwareErrorexpr:increase(node_hwmon_disk_errors_total[1h])0for:0mlabels:severity:warningannotations:summary:磁盘 firmware 出现错误写在最后KRaft 模式确实比 ZooKeeper 轻量但轻量不代表不需要理解共识机制。很多人以为去掉 ZK 就简单了结果 Controller 脑裂的时候根本不知道从哪下手。我的建议是升级到 Kafka 3.6.1避开 KAFKA-14500元数据目录一定要放在独立磁盘且做快照备份监控不要只盯着吞吐和延迟Controller 选举次数、元数据水位差、磁盘 firmware 健康都要看这次从告警到恢复用了 40 分钟其中 20 分钟花在确定能不能直接重置 epoch上。如果能提前把元数据快照备份到 S3时间可以压缩到 15 分钟以内。如果你也在用 KRaft 模式建议现在就去检查一下你的 Controller 选举日志——说不定你的某个 broker 的 epoch 已经在边缘试探了。