考察点
Rebalance 是 Kafka 消费端线上事故的高发区,面试官想知道你有没有被它坑过。考察三层:触发条件(成员变化、订阅变化、心跳超时)、分配协议(Eager 与 Cooperative 的区别)、缓解手段(参数调优、静态成员、Sticky 分配器)。能讲出"session.timeout 和 max.poll.interval 分别防什么"是加分项。
参考答案
什么是 Rebalance,什么时候触发
消费者组(Consumer Group)里,分区和消费者实例的对应关系不是固定的。当这个对应关系需要重新洗牌时,整个组进入 Rebalance:所有成员暂停消费,向 Group Coordinator 重新报到,由 Group Leader(组内第一个加入的消费者,注意不是 Broker)制定分配方案,Coordinator 下发,大家按新方案重新认领分区。
触发条件主要有三类:
- 组成员变化:新实例加入、实例主动退出、实例崩溃或心跳超时被踢。
- 订阅的主题或分区数变化:比如给主题加了分区。
- 消费进度超时:
max.poll.interval.ms(默认 5 分钟)内没有发起下一次 poll,Coordinator 认为这个实例"死了",强制踢出。这条是最容易被业务代码坑到的——单批消息处理超过 5 分钟,消费者明明活得好好的却被踢,然后循环 Rebalance。
两个关键超时参数别搞混
session.timeout.ms(默认 45 秒,老版本 10 秒)管心跳:Consumer 后台线程定期发心跳,超过这个时间没心跳就被认为宕机,触发 Rebalance。它防的是实例真死。
max.poll.interval.ms 管的是"两次 poll 之间的最大间隔",它防的是实例活着但消费卡死(比如处理逻辑死循环)。区分清楚:心跳正常不代表消费在推进。
线上调优常动这两个参数,但要讲出道理:session.timeout 调大可以扛住 GC 停顿带来的误踢,代价是故障发现变慢;max.poll.interval 调大配合减批次(max.poll.records 调小)能解决处理慢的问题,但更正确的做法是把重处理逻辑挪出 poll 线程,异步化。
Eager 协议:Stop The World 的代价
老的分配协议叫 Eager(Range、RoundRobin、Sticky 分配器都走它),流程分三步:
- 所有成员放弃自己手里的全部分区(revoke),停止消费。
- 全员重新 JoinGroup,Coordinator 选出 Group Leader。
- Group Leader 计算分配方案,经 Coordinator SyncGroup 下发,各成员认领新分区。
问题是明显的:第 1 步全员停摆,哪怕这次 Rebalance 只是新增一个实例、只涉及一个分区的挪动,整个组的消费都要停下来。组里实例多的时候,一次 Rebalance 几十秒,消费延迟尖刺、重复消费(revoke 时没来得及提交位移的消息被新 owner 重拉)都来了。滚动重启一个大组,Rebalance 风暴能把下游打挂。
缓解手段
Cooperative Sticky 协议(2.4 引入):增量式再均衡。Rebalance 时成员只交出"要被挪走"的那部分分区,其余分区继续消费,不停摆;一次挪不完就多轮 Rebalance 逐步收敛。代价是收敛时间略长,但消除了全局 STW,大规模消费者组强烈推荐。
静态组成员(2.3 引入,group.instance.id):给每个实例配固定 ID,实例重启后带原 ID 回来,Coordinator 认出它是"老熟人"就不触发 Rebalance,直接恢复原来的分配。滚动发布场景的效果立竿见影。
其他常规手段:分配器选 Sticky 减少分区不必要的搬家;保证消费逻辑不阻塞 poll 线程;合理设置 session.timeout 与 heartbeat.interval(后者默认 3 秒,远小于前者);客户端和 Broker 版本对齐,老客户端有很多已修复的 Rebalance bug。
监控上怎么发现
Broker 端和消费者端都有 Rebalance 相关指标:rebalance-rate、rebalance-latency、assigned-partitions。线上组频繁 Rebalance(每小时好几次以上)就该查了,按触发条件逐个排:是不是有实例处理太慢被 max.poll.interval 踢、是不是有 Full GC 导致心跳超时、是不是有人在动态加减实例。
可能的追问
- Rebalance 时一定会重复消费吗? 大概率会有少量:revoke 时已拉取未提交位移的消息会被新 owner 重新消费。缓解靠 revoke 回调里同步提交位移,兜底靠下游幂等。
- Sticky 分配器解决了什么问题? 两次 Rebalance 之间尽量保留原有分配,只挪动必要的分区,减少消息重复和状态迁移(对带本地状态的流处理尤其重要)。
- 一个消费者组频繁 Rebalance,你的排查顺序? 先看日志里 Rebalance 原因(revoke 原因字段),再对照:处理时长是否超 max.poll.interval → GC 日志看 STW → 是否有扩缩容/发布 → 分区数是否被人改过。