系统学习 学习路径总入口:先选领域,再建立模型,最后用故障演练验证

在副本不足、磁盘故障或整集群损坏后,恢复 Kafka 的 topic 元数据、分区副本与消费者位点

适用场景

Kafka 没有 etcd 那种内置的一致性快照命令。这里说的“快照”是文件系统、云盘或存储层快照,必须与 broker 停机或文件系统冻结配合;在线直接复制 log.dirs 可能得到撕裂的日志段和索引。更常见的恢复路径不是恢复快照,而是利用副本重建;只有副本全部丢失、元数据损坏或需要回到历史时点时才使用快照。

本流程的 RPO 由最后一次可用副本或快照决定。Kafka 不是事务数据库,恢复后通常只能保证“至少一次”,消费者必须能处理重复消息。

前置检查

  1. 先判断故障边界,不要一开始就恢复整个集群:
kafka-topics.sh --bootstrap-server "$BS" --describe --under-replicated-partitions
kafka-metadata-quorum.sh --bootstrap-server "$BS" describe --status
kafka-log-dirs.sh --bootstrap-server "$BS" --describe

记录哪些 broker 在线、哪些分区没有 leader、哪些分区 ISR 不足,以及 controller quorum 是否仍然存在。如果还有一份完整的同步副本,优先修复 broker 和副本,不要用快照覆盖健康数据。

  1. 选择恢复路径:
  1. 记录元数据清单。只要集群还可读,就导出 topic、配置、ACL 和消费者位点:
kafka-topics.sh --bootstrap-server "$BS" --list > topics.txt
while read -r t; do
  kafka-topics.sh --bootstrap-server "$BS" --describe --topic "$t"
done < topics.txt > topics-describe.txt
while read -r t; do
  kafka-configs.sh --bootstrap-server "$BS" \
    --describe --entity-type topics --entity-name "$t"
done < topics.txt > topic-configs.txt
kafka-acls.sh --bootstrap-server "$BS" --list > acls.txt
kafka-consumer-groups.sh --bootstrap-server "$BS" --list > groups.txt

清单要包含分区数、副本因子、min.insync.replicas、保留策略、清理策略、压缩方式和消息大小限制。恢复时不能依赖自动建 topic,否则分区数和副本因子通常不符合生产要求。

  1. 记录快照时点的数据边界。对每个关键 topic 保存各分区最新 offset:
kafka-get-offsets.sh --bootstrap-server "$BS" --topic orders --time -1

如果快照是存储层打出的,记录快照 ID、拍摄时间、所有 broker 的 node ID、KRaft cluster ID 和 metadata.log.dir。只恢复数据日志、不恢复元数据日志,可能得到一个无法加入 quorum 的集群。

  1. 确认快照一致性和容量。快照必须覆盖所有相关 broker 的 log.dirs 与 KRaft 元数据目录;在线快照要确认存储层支持多卷一致性,或已经停止 broker。检查目标磁盘空间、文件权限、时间同步,以及目标环境的 listeners 和 advertised.listeners 是否能被客户端访问。

  2. 冻结生产者和消费者,停止消费者组。消费者仍在运行时,offset 会继续变化,无法判断恢复后从哪里继续;关闭自动提交后,仍可能有已处理未提交的消息,要按业务幂等性处理。

恢复步骤

1. 隔离故障集群

停止所有 broker 和 controller,保留旧数据目录、日志和快照。不要在未确认快照一致性的情况下直接覆盖生产 log.dirs,也不要把新旧日志目录混在一起启动。

如果是单 broker 故障且集群仍有 quorum,不要进入整集群恢复:把故障节点的数据目录移走,保留空目录和原 node ID,启动进程让副本重新同步。

2. 恢复 topic 元数据

元数据和数据必须成对。若元数据日志完好,直接恢复数据目录后检查 topic 描述,不要重建同名 topic;若元数据丢失,按导出的清单重建 topic、配置和 ACL:

kafka-topics.sh --bootstrap-server "$BS" --create \
  --topic orders \
  --partitions 12 \
  --replication-factor 3 \
  --config min.insync.replicas=2 \
  --config cleanup.policy=delete \
  --config retention.ms=604800000

分区数只能增加不能减少,恢复时应使用原分区数;副本因子不能超过目标 broker 数。ACL、SCRAM 凭据、配额和 topic 配置要单独恢复,不能只创建 topic 名称。内部 topic 如 __consumer_offsets、__transaction_state 不应手工随意重建,优先随快照恢复或让集群按配置自动创建。

3. 按副本或快照恢复数据

副本重建。 启动一个空日志目录的 broker,使用原 node ID 加入集群,让它从 leader 拉取分区副本。如果原节点无法恢复,用新 node ID 替换,并通过分区重分配把副本迁走:

kafka-reassign-partitions.sh --bootstrap-server "$BS" \
  --reassignment-json-file plan.json --execute
kafka-reassign-partitions.sh --bootstrap-server "$BS" \
  --reassignment-json-file plan.json --verify

重分配前先用 --generate(需同时提供 --topics-to-move-json-file 与 --broker-list)生成 plan.json,确认分区数不变,再执行并 verify。重分配会消耗大量磁盘和网络 I/O,应避开业务高峰,并一次只处理有限分区。不要为了追求速度把 unclean.leader.election.enable 设为 true,否则可能把未同步的副本选为 leader,直接丢数据。

快照恢复。 停止所有 broker 和 controller,把同一时点的快照恢复到各自节点;恢复 log.dirs 和 KRaft 元数据日志,核对 cluster ID、node ID、目录权限和配置文件。角色分离时先让 controller quorum 形成,再启动 broker;合并角色按节点配置启动。不要对恢复出来的元数据目录执行格式化,也不要把不同节点、不同快照的数据目录拼在一起。

重建加回放。 如果快照不可用,创建新集群,按清单重建 topic,再从上游 CDC、对象存储或业务补偿日志重放。重放前确认消费者幂等、消息键和分区路由不会改变顺序;这条路径无法保证零丢失,必须由业务确认 RPO。

4. 恢复消费者位点

如果 __consumer_offsets 随快照一起恢复,消费组通常会回到快照时点的提交位置。若 offset 丢失、指向已过期数据,或恢复后的日志终点早于已提交 offset,就要重置。先停止消费者,确认组内没有 active member:

kafka-consumer-groups.sh --bootstrap-server "$BS" --describe --group order-svc
kafka-consumer-groups.sh --bootstrap-server "$BS" --group order-svc \
  --topic orders --reset-offsets \
  --to-datetime 2026-10-18T02:30:00.000+08:00 --dry-run

确认结果后把 --dry-run 改成 --execute。重置到 earliest 会重复消费,重置到 latest 会跳过中间数据;选择哪一种取决于业务是否幂等、能否接受重复以及是否必须补齐历史。不要在没有 dry-run 和备份 offset 的情况下批量重置全部消费组。

验证

失败与回滚

broker 无法启动。 先看日志中的 cluster ID、node ID、元数据日志和 log.dirs 错误。不要用格式化命令“修复”元数据目录;格式化会生成新的 cluster ID,原有数据将无法加入。确认恢复目录的属主、磁盘挂载和配置文件与目标节点匹配。

ISR 长期不足。 检查副本拉取网络、磁盘 I/O、replica.fetch.max.bytes 和 ISR 超时配置。先让 broker 稳定运行,再做分区重分配;不要在副本未同步时删除 leader 或执行不安全的 leader 选举。

快照数据不一致。 立即停止所有 broker,保留失败目录和日志,重新从上一份通过校验的快照恢复全部节点。不能只替换某个节点的日志,也不能把旧目录中“看起来完整”的分区拼回新快照。

消费者位点错误。 停止消费组,记录当前 offset 和业务处理进度,再用 dry-run 确认重置目标。无法确定时宁可回到 earliest 配合幂等消费,也不要直接跳到 latest 造成静默漏数。

无法恢复原集群。 新建集群、重建元数据并从上游重放,明确记录丢失的时间窗口。不要让旧集群继续接受写入后再切回,否则两个集群的 offset 和业务状态无法对齐。

需要回滚。 在客户端切换前,保留旧集群和旧数据目录;若新集群验证失败,停止新集群写入,隔离网络,再把客户端切回仍一致的旧集群。若旧集群已经落后,先核对差异并补偿,不能简单改 DNS 了事。

相关命令

参考链接

读完自测

合上页面,先回答,再展开对照。

  1. 单个 broker 故障但 ISR 中仍有完整副本时,为什么通常不应直接恢复整集群快照?
  2. 为什么恢复 Kafka 文件系统快照时,必须同时恢复 KRaft 元数据并保证 cluster ID、node ID 一致?
  3. 恢复后发现消费者提交的 offset 超过恢复日志的终点,应该怎么处理?
答案
  1. 健康副本仍能提供服务,直接用快照覆盖会回滚所有 topic 并丢掉快照之后的写入;应恢复故障 broker,让副本重新同步。
  2. 数据日志必须与元数据对应,cluster ID 或 node ID 不一致会导致 broker 无法加入 quorum;只恢复日志、不恢复元数据,或混用不同快照,都会得到不一致的集群。
  3. 停止消费者,记录现状,用 kafka-consumer-groups.sh --reset-offsets 先 dry-run 再执行,选择 earliest 或目标时间;不能直接跳到 latest,否则会静默漏数。
微信小程序码:扫码打开命令速查小程序

小程序版

微信扫码,随时查命令