4. 消费者组状态机:Stable、PreparingRebalance、CompletingRebalance、Dead 状态详解
消费者组的状态流转,是整个负载均衡的核心骨架。说白了,Kafka 就是靠这套状态机来管理「谁该消费哪个分区」这件事。
我刚开始接触 Kafka 时,觉得这玩意儿就是个简单的「分配-消费」循环。后来线上出了几次诡异的消费停滞,追查下来才发现——状态机理解不透,遇到异常场景根本没法快速定位。
今天咱们就把这四种状态掰开揉碎,看看它们到底在干什么。
4.1 状态总览:一张图看懂流转
先看整体结构。这张图我画了好几个版本,最终觉得这样最清晰:
正常情况下的流转路径是:Stable → PreparingRebalance → CompletingRebalance → Stable。这是一个闭环,周而复始。
但 Dead 状态是个例外——它是个终态,进去就出不来了。
4.2 Stable 状态:岁月静好
Stable 是消费者组的「正常态」。所有成员都在按分配方案消费分区,Group Coordinator 定期收到心跳,一切看起来都很美好。
这个状态下,Coordinator 主要做三件事:
- 心跳监控:每个成员必须定期发送心跳。默认
session.timeout.ms是 45 秒,超过这个时间没心跳,Coordinator 就会认为该成员挂了。 - 元数据维护:记录当前组成员列表、分配方案、Generation ID。
- 位移管理:接收消费者提交的消费位移,写入内部主题
__consumer_offsets。
session.timeout.ms 设成 60 秒,给 GC 停顿留点余量。但别设太大——超过 2 分钟,分区长时间无人消费,业务可能受不了。
4.3 PreparingRebalance 状态:风暴前夜
一旦触发条件满足,组就进入 PreparingRebalance 状态。这个状态的名字很形象——「准备重平衡」,但还没真正开始分配。
触发条件有哪些?我列一下:
| 触发条件 | 说明 | 常见场景 |
|---|---|---|
| 成员加入/离开 | 新消费者加入,或现有消费者主动关闭 | 应用扩缩容、滚动重启 |
| 心跳超时 | 成员未在 session.timeout.ms 内发送心跳 | Full GC、网络抖动、消费者线程卡死 |
| 分区数量变化 | Topic 的分区数被管理员增加 | 业务量增长,手动扩容分区 |
| 订阅 Topic 变更 | 消费者组修改了订阅的正则或 Topic 列表 | 业务重构,切换数据源 |
进入这个状态后,Coordinator 会做两件事:
- 广播通知:给所有成员发送
REBALANCE_IN_PROGRESS错误码。消费者收到后,会停止消费,准备重新加入。 - 等待 JoinGroup 请求:所有成员收到通知后,会发送
JoinGroup请求给 Coordinator。
4.4 CompletingRebalance 状态:尘埃落定
当 Coordinator 收集到所有成员的 JoinGroup 请求后,状态切换到 CompletingRebalance。
这个阶段的核心工作就是——选 Leader 和执行分配。
流程是这样的:
- Coordinator 从成员列表中选出一个 Leader(通常是第一个加入的成员)。
- Coordinator 把当前所有成员信息和订阅信息发给 Leader。
- Leader 根据分区分配策略(Range、RoundRobin、Sticky 等)计算分配方案。
- Leader 把方案通过
SyncGroup请求返回给 Coordinator。 - 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 开始。
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 选举)导致状态短暂混乱 |
max.poll.interval.ms 后恢复正常。
4.8 小结
消费者组状态机,说白了就是一套「感知变化 → 重新分配 → 恢复稳定」的闭环机制。理解这四种状态,你就能看懂 Kafka 在背后做了什么。
Stable 是常态,PreparingRebalance 是触发阶段,CompletingRebalance 是执行阶段,Dead 是终态。大部分问题都出在 PreparingRebalance 和 CompletingRebalance 的衔接上——要么触发太频繁,要么执行太慢。
嗯,记住一点:状态机是 Kafka 保证消费一致性的基石。搞懂它,你离 Kafka 专家又近了一步。