skip to content
Jayton's Blog

Kafka 故障复盘:消费者静默收不到数据,根因竟是重装时元数据错位

/ 14 min read

一个诡异的故障

SpringBoot 服务订阅的 Kafka Topic 收不到数据了。不是偶尔延迟,是持续收不到。更诡异的是,服务端没有任何报错,消费者像睡着了。

消费组 russian-gas-regulation-group1 订阅的多个 Topic(vehicle_real_time_infoalarm_event 等)全部静默。

这篇文章复盘这次 P1 故障:数据链路中断,历史数据丢失。重点是为什么”无报错”,以及根因如何一步步定位到”元数据错位”。

影响范围

项目影响
消费者组russian-gas-regulation-group1 等 5 个成员无法消费
__consumer_offsets故障态(修复前)50 分区全部 Replicas: 9Leader: none,group coordinator 不可用;修复后重建为 Replicas: 0Leader: 0
业务 Topic共 8 个全部 Leader: none
数据损失8 个业务 Topic 的历史数据永久丢失(broker 9 磁盘已不存在)

时间线

时间事件
8/26 之前旧 Kafka 集群(broker 9)正常运行,所有 Topic 建在 broker 9 上
8/26 15:44~16:54ZK 快照固化 broker 9 的 Topic 副本元数据
8/26 17:03重装 Kafka,broker.id 配为 0,复用旧 ZK → 所有 Topic 因 broker 9 已消失而 Offline
8/27 08:59首次尝试手动迁移 event.helix(副本 9→0),失败
8/28 10:34event.helix 迁移成功(Leader=0)
8/28 10:51~11:15迁移 alarm 系列 4 个 Topic,数据丢失(offset 从 0 起)
8/28 11:19broker 重启,alarm 系列被删除重建(Topic ID 变更)
8/28 12:00~12:26排查定位:确认幽灵 broker 9、__consumer_offsets 副本错位
8/28 12:26误操作①:在 broker 运行中直接 deleteall /brokers/topics/* 手删 ZK 节点 → 删不掉(controller 立即重建)
8/28 12:37__consumer_offsets 已重建为健康态(50 分区 Leader: 0),但 describeConsumerGroups 仍超时
8/28 12:40误操作②:在 broker 运行中又 deleteall 了健康的 __consumer_offsets 元数据 → 内存/ZK/磁盘三方不一致,报 Timed out waiting for a node assignment
8/28 12:40~12:49停应用 → 停 broker → 清理磁盘残留 → 干净重启,三方状态重新一致
8/28 12:49消费者组稳定(5 members, generation 2),恢复正常

前置知识:Group Coordinator 与 __consumer_offsets

Kafka 核心角色

组件职责
Broker存储分区数据、处理读写请求的服务节点
Topic / PartitionTopic 是逻辑分类,Partition 是物理分片;每个分区有多个副本(Replica),Leader 负责读写,Follower 同步备份
Controller某个 broker 兼任的”管理员”,负责分区 Leader 选举、副本管理、元数据变更
Group Coordinator消费者组的管理者:组成员管理(JoinGroup)、分区分配(rebalance)、offset 提交管理
__consumer_offsets内部 Topic(默认 50 分区),专门存储各消费者组的 offset 提交记录和组元数据

消费者消费任何 Topic 的前提:先”入组”

消费者不能直接读分区,必须先经过 Group Coordinator:

1. 消费者启动 → FIND_COORDINATOR 找到本组对应的 coordinator
2. 向 coordinator 发 JoinGroup → coordinator 触发 rebalance,给每个成员分配分区
3. 分配完成后,消费者才知道自己该读哪些分区,才开始拉取消息
4. 消费过程中定期 OffsetCommit,coordinator 把 offset 写入 __consumer_offsets

一个 group 的 coordinator 是谁,由 __consumer_offsets 决定:

groupId → hash → __consumer_offsets 的某个分区 → 该分区 Leader 所在的 broker 就是 coordinator

所以 Group Coordinator 的生命线就是 __consumer_offsets 的分区 Leader。

为什么 coordinator down 会拖垮业务 Topic

这是本故障最反直觉的地方,也是”无报错”的根源:

  • 业务 Topic 的数据、分区 Leader 可能完全正常,但消费者依然收不到。
  • 因为消费者卡在”入组”这一步:coordinator 不可用 → 无法 JoinGroup → 拿不到分区分配 → 永远不会开始拉取消息。
  • 类比:餐馆正常营业、菜品齐全,但发号叫号的前台坏了,客人排不上队、进不了店。
  • “无报错”是因为消费者只是在后台反复重试 FIND_COORDINATOR,日志里是重试信息而非业务错误,极易被忽略。

本案例的完整因果链

重装 Kafka(broker.id=0)复用旧 ZK
ZK 中 __consumer_offsets(及业务 Topic)副本仍指向已消失的 broker 9
controller 只能从"该分区的副本集合"里选 Leader,而副本集合被固化为 [9],
broker 9 又不在 /brokers/ids → 选不出 Leader → 分区 Offline
__consumer_offsets 50 分区全部 Leader:none
Group Coordinator 无法确定(依赖 __consumer_offsets 分区 Leader)
消费者 FIND_COORDINATOR 返回哨兵值 2147483647(Int.MaxValue,表示 coordinator 不可用),无法 JoinGroup
消费者不开始消费 → 业务 Topic 有数据也收不到
表现:无报错、静默收不到数据

根因分析

直接根因

8/26 重装 Kafka 时,broker.id 配置为 0,但复用了旧 ZooKeeper;ZK 中所有 Topic 的副本元数据仍指向已消失的 broker 9,导致这些 Topic 全部 Leader: none,group coordinator 无法选举,消费者静默收不到数据。

证据链

  1. kafka-consumer-groups.sh --describeFIND_COORDINATOR 超时 → coordinator 不可用。
  2. __consumer_offsets 50 分区全部 Leader: noneReplicas: 9,但 ZK /brokers/ids 中只有 [0]
  3. cluster.id 与 broker 0 的 meta.properties 完全一致 → 排除”接错 ZK”。
  4. broker 0 的 meta.properties 创建于 8/26 17:03,且当时 log.dirs 中仅有 event.helix-0 一个分区目录 → 证明 broker 0 是 8/26 重装的全新 broker,非 broker 9 改名。
  5. controller.log 从 broker 0 启动第一秒(8/26 17:03:41)起持续报 Topics not in preferred replica for broker 9 → broker 9 在 broker 0 上线前已不存在。
  6. state-change.log 中无任何 broker 9 上下线记录 → broker 9 是”旧集群的幽灵”。
  7. 全盘 find /data -name meta.properties 仅有一个(broker 0 的)→ broker 9 数据目录已彻底不存在。

关键结论

不是 ZooKeeper 进程故障,也不是 Kafka 进程故障,而是元数据错位(旧 ZK 数据 + 新 broker),属于”重装 Kafka 时未同步处理 ZK 元数据”的运维操作失误。

排查过程

  1. 分层定位:先用 kafka-console-consumer 验证数据链路,确认问题在消费者端。
  2. 查 coordinatorkafka-consumer-groups.sh --describeFIND_COORDINATOR 超时,定位到 group coordinator 不可用。
  3. __consumer_offsets--describe 发现 50 分区全部 Leader: none,副本在 broker 9。
  4. 确认 broker 现状:ZK /brokers/ids[0]cluster.id 一致;meta.properties 时间戳指向 8/26 重装。
  5. 查历史痕迹controller.logstate-change.log、ZK 事务日志,确认 broker 9 早已消失、且 event.helix 等 Topic 曾被部分迁移过。
  6. 确认数据不可恢复:全盘搜索 broker 9 数据目录,确认已不存在。

排查中的弯路(反面教材)

排查过程中一度在 broker 仍在运行的情况下,直接用 zookeeper-shell.sh ... deleteall /brokers/topics/<topic> 手删 ZK 元数据,走了两个弯路:

  1. 删不掉:删完 ls /brokers/topics 节点又在,因为 controller 感知到 ZK 与自身内存状态不一致,立即把节点重建了回来。ZK 里的 topic 节点只是元数据,broker 内存和磁盘日志都还在。
  2. 越删越乱:进一步把当时已经健康的 __consumer_offsets(50 分区 Leader: 0)也 deleteall 掉,导致 broker 内存 / ZK 元数据 / 磁盘日志三方不一致,kafka-consumer-groups.sh --describe 从原来的 FIND_COORDINATOR 超时恶化为 Timed out waiting for a node assignment(AdminClient 拿不到可用节点)。

正确姿势:删 topic 必须走 kafka-topics.sh --delete(需 delete.topic.enable=true);副本错位应走 kafka-reassign-partitions.sh 重分配;只有在必须彻底重建时,才「停应用 → 停 broker → 删 ZK 节点 → 删磁盘日志 → 重启」四步同步做,保证三方一致。

解决方案

前提判断:重分配(保数据)还是删除重建(丢数据)? 副本指向已下线 broker 时,首选 kafka-reassign-partitions.sh 把副本从 broker 9 迁到 broker 0,只要旧 broker 磁盘还在,数据不丢(时间线里 event.helix 那次就是在做迁移)。只有磁盘彻底丢失才被迫删除重建。本次 broker 9 磁盘已不存在,才无奈走了删除重建、接受数据丢失。

  1. 确认数据不可恢复:全盘搜索确认 broker 9 磁盘已不存在,历史数据无法找回,重分配路线不可行。
  2. 停应用:先停掉消费/生产应用,避免边清理边被自动创建/写入。
  3. 清理幽灵元数据:删除 ZK 中 /admin/delete_topics/brokers/topics/config/topics 下 9 个 Topic 的残留节点。
  4. 重建业务 Topic:重建 8 个业务 Topic(单分区、RF=1)。
  5. 干净重启 broker:停 broker → 清理 __consumer_offsets-* 磁盘残留(与已删的 ZK 元数据保持一致)→ 重启。broker 启动时按 offsets.topic.num.partitions(默认 50)自动重建 __consumer_offsets(Leader=0),GroupCoordinator 正确初始化。
  6. 验证恢复:消费者组成功 join,kafka-consumer-groups.sh --describe 正常返回 LAG。

经验教训

  1. 重装 Kafka 必须同步处理 ZK 元数据:broker.id 变更后,要么换干净 ZK,要么全量迁移/清理 ZK 中 Topic 副本分配,否则出现”幽灵 broker”。
  2. ZK 数据目录不能放 /tmp:本案例 ZK 实际 dataDir 在 /data/iot/third_party/zk/data(独立目录),但 Kafka 目录下的 zookeeper.properties 却指向 /tmp/zookeeper,配置与实际不符,增加了排查成本。
  3. “无报错收不到数据”先查 coordinator/offset:不要只看业务日志,先看 kafka-consumer-groups.sh --describe 的 LAG 和 coordinator 状态。
  4. 绝不在运行中的 broker 上手删 ZK 的 topic 节点:ZK 节点只是元数据,broker 内存和磁盘日志才是真身。手删要么被 controller 立即重建(表现为”删不掉”),要么造成内存/ZK/磁盘三方不一致,越删越乱。删 topic 走 kafka-topics.sh --delete;副本错位走 kafka-reassign-partitions.sh;必须彻底重建时,“停应用→停 broker→删 ZK→删磁盘→重启”四步同步做。
  5. 副本指向死 broker 优先重分配而非重建kafka-reassign-partitions.sh 在旧磁盘尚存时可保数据,删除重建必丢数据,删除重建应是最后手段。
  6. 运维操作必须留档:本案例的 Topic 迁移/删除重建无任何记录,导致”没人承认”、排查时需从日志反推时间线。
  7. 关键数据需备份:broker 9 数据丢失说明缺少 Kafka 数据备份/迁移预案。

改进措施(Action Items)

序号措施责任人优先级
1建立 Kafka 重装/迁移 SOP:变更 broker.id 前必须先处理 ZK 元数据运维
2统一 ZK 配置,杜绝 /tmp 与独立 dataDir 混用运维
3为 Kafka 业务 Topic 建立数据备份/多副本策略(生产环境 RF≥3,需先将集群扩容到 ≥3 broker,非单机改配置即可)架构
4建立运维操作日志/变更记录制度运维
5接入消费监控告警(LAG 持续为 0 或 coordinator 异常告警)研发
6补充 Kafka 故障排查手册(本案例可作为范本)运维

附录:关键排查命令

Terminal window
# 查消费者组状态(定位 coordinator 是否可用)
kafka-consumer-groups.sh --bootstrap-server <host>:9092 --group <group> --describe
# 查内部 topic 副本/leader 状态
kafka-topics.sh --bootstrap-server <host>:9092 --describe --topic __consumer_offsets
# 查 ZK 中注册的 broker
zookeeper-shell.sh <host>:2181 ls /brokers/ids
# 查 broker 注册的地址(确认 broker 存活 + advertised 地址是否可达)
zookeeper-shell.sh <host>:2181 get /brokers/ids/0
# 查 topic 元数据(确认副本分配,重点看副本是否指向已下线 broker)
zookeeper-shell.sh <host>:2181 get /brokers/topics/<topic>
# 副本指向死 broker 时的正确修复方式:重分配(磁盘在则不丢数据)
kafka-reassign-partitions.sh --bootstrap-server <host>:9092 --reassignment-json-file reassign.json --execute
# 查 broker 日志中 coordinator/副本状态
grep -iE 'GroupCoordinator|preferred replica|leader' logs/controller.log
grep -iE 'Leader|Offline|reassign' logs/state-change.log