4. 消费者组状态机:Stable、PreparingRebalance、CompletingRebalance、Dead 状态详解

消费者组的状态流转,是整个负载均衡的核心骨架。说白了,Kafka 就是靠这套状态机来管理「谁该消费哪个分区」这件事。

我刚开始接触 Kafka 时,觉得这玩意儿就是个简单的「分配-消费」循环。后来线上出了几次诡异的消费停滞,追查下来才发现——状态机理解不透,遇到异常场景根本没法快速定位。

今天咱们就把这四种状态掰开揉碎,看看它们到底在干什么。

4.1 状态总览:一张图看懂流转

先看整体结构。这张图我画了好几个版本,最终觉得这样最清晰:

消费者组状态机流转图 Stable 稳定消费中 PreparingRebalance 准备重平衡 CompletingRebalance 完成重平衡 Dead 组已消亡 触发条件 收集完 JoinGroup SyncGroup 完成 超时/异常 全部成员离开 正常流转:Stable → PreparingRebalance → CompletingRebalance → Stable

正常情况下的流转路径是:Stable → PreparingRebalance → CompletingRebalance → Stable。这是一个闭环,周而复始。

但 Dead 状态是个例外——它是个终态,进去就出不来了。

4.2 Stable 状态:岁月静好

Stable 是消费者组的「正常态」。所有成员都在按分配方案消费分区,Group Coordinator 定期收到心跳,一切看起来都很美好。

这个状态下,Coordinator 主要做三件事:

  • 心跳监控:每个成员必须定期发送心跳。默认 session.timeout.ms 是 45 秒,超过这个时间没心跳,Coordinator 就会认为该成员挂了。
  • 元数据维护:记录当前组成员列表、分配方案、Generation ID。
  • 位移管理:接收消费者提交的消费位移,写入内部主题 __consumer_offsets。
我的一点经验:Stable 状态下的心跳超时,是线上最常见的触发重平衡的原因。我个人习惯把 session.timeout.ms 设成 60 秒,给 GC 停顿留点余量。但别设太大——超过 2 分钟,分区长时间无人消费,业务可能受不了。

4.3 PreparingRebalance 状态:风暴前夜

一旦触发条件满足,组就进入 PreparingRebalance 状态。这个状态的名字很形象——「准备重平衡」,但还没真正开始分配。

触发条件有哪些?我列一下:

触发条件 说明 常见场景
成员加入/离开 新消费者加入,或现有消费者主动关闭 应用扩缩容、滚动重启
心跳超时 成员未在 session.timeout.ms 内发送心跳 Full GC、网络抖动、消费者线程卡死
分区数量变化 Topic 的分区数被管理员增加 业务量增长,手动扩容分区
订阅 Topic 变更 消费者组修改了订阅的正则或 Topic 列表 业务重构,切换数据源

进入这个状态后,Coordinator 会做两件事:

  1. 广播通知:给所有成员发送 REBALANCE_IN_PROGRESS 错误码。消费者收到后,会停止消费,准备重新加入。
  2. 等待 JoinGroup 请求:所有成员收到通知后,会发送 JoinGroup 请求给 Coordinator。
注意:PreparingRebalance 阶段,消费者会暂停消费。如果重平衡过程耗时较长(比如成员很多、网络延迟大),这段时间分区是无人消费的。我曾经遇到过一个大组(200+ 成员),重平衡花了 30 多秒,业务方直接报警了。

4.4 CompletingRebalance 状态:尘埃落定

当 Coordinator 收集到所有成员的 JoinGroup 请求后,状态切换到 CompletingRebalance。

这个阶段的核心工作就是——选 Leader 和执行分配。

流程是这样的:

  1. Coordinator 从成员列表中选出一个 Leader(通常是第一个加入的成员)。
  2. Coordinator 把当前所有成员信息和订阅信息发给 Leader。
  3. Leader 根据分区分配策略(Range、RoundRobin、Sticky 等)计算分配方案。
  4. Leader 把方案通过 SyncGroup 请求返回给 Coordinator。
  5. Coordinator 把方案广播给所有成员。

这里有个细节:只有 Leader 执行分配逻辑,其他成员只是被动接收结果。这样做的好处是避免每个成员都算一遍,浪费 CPU 还容易不一致。

关键点:CompletingRebalance 阶段有一个超时时间 rebalance.timeout.ms(默认 60 秒)。如果 Leader 在这个时间内没返回分配方案,Coordinator 会认为 Leader 挂了,把组踢回 PreparingRebalance 重新开始。

4.5 Dead 状态:游戏结束

Dead 是消费者组的终态。进入这个状态后,Coordinator 会清理该组的所有元数据,不再处理任何请求。

什么情况下会进入 Dead?

  • 所有成员都离开了:组里一个消费者都不剩了。
  • 组被手动删除:通过 kafka-consumer-groups --delete 命令。
  • 元数据过期:如果组在 offsets.retention.minutes(默认 7 天)内没有任何活动,Coordinator 会把它标记为 Dead 并清理。

嗯,这里要注意:Dead 状态不可逆。一旦进入 Dead,这个组就彻底消失了。下次再有消费者用同样的 group.id 启动,会创建一个全新的组,Generation ID 从 0 开始。

避坑指南:我曾经遇到一个场景——消费者组频繁进入 Dead 状态。排查下来发现是消费者代码里有个 bug,启动后几秒就异常退出,导致组里一直没人。Coordinator 检测到空组后直接标记 Dead。后来加了重试机制,问题才解决。

4.6 状态机与 Generation ID

每次重平衡完成,Generation ID 都会 +1。这个 ID 很重要,它用来防止「幽灵请求」:

  • 消费者 A 在 Generation 1 时提交位移。
  • 重平衡发生,Generation 变成 2。
  • 如果 Generation 1 的位移请求在 Generation 2 之后才到达,Coordinator 会直接拒绝——因为 Generation 不匹配。

说白了,Generation ID 就是个版本号,保证请求不会乱序执行。

4.7 状态机异常场景

实际生产中,状态机流转不会总那么顺利。我总结几个常见异常:

异常场景 表现 原因
反复重平衡 组在 PreparingRebalance 和 CompletingRebalance 之间来回跳 成员频繁加入/离开,或心跳超时
重平衡超时 Leader 在 rebalance.timeout.ms 内没返回方案 Leader 负载过高、GC 停顿、网络分区
成员失联 部分成员在重平衡过程中掉线 消费者进程崩溃、网络中断
状态不一致 Coordinator 认为组在 Stable,但消费者收到 REBALANCE_IN_PROGRESS Coordinator 切换(Leader 选举)导致状态短暂混乱
重要提醒:反复重平衡(俗称「震荡」)是线上最头疼的问题之一。我曾经排查过一个案例,组在 5 分钟内重平衡了 20 多次,消费延迟从 1 秒飙升到 10 分钟。最后发现是消费者线程池配置不合理,处理消息太慢导致心跳超时。调大 max.poll.interval.ms 后恢复正常。

4.8 小结

消费者组状态机,说白了就是一套「感知变化 → 重新分配 → 恢复稳定」的闭环机制。理解这四种状态,你就能看懂 Kafka 在背后做了什么。

Stable 是常态,PreparingRebalance 是触发阶段,CompletingRebalance 是执行阶段,Dead 是终态。大部分问题都出在 PreparingRebalance 和 CompletingRebalance 的衔接上——要么触发太频繁,要么执行太慢。

嗯,记住一点:状态机是 Kafka 保证消费一致性的基石。搞懂它,你离 Kafka 专家又近了一步。