什么是Rebalance

为了提升消费效率,kafka会将Topic消息放到多个Partition,消费者Consumer可以以消费组(Consumer Group)的形式进行并行消费

在这个背景下,每个消费者节点负责一个或多个Partition。为了保证负载均衡,多个Partition能均匀分配给消费节点,Kafka需要会根据节点负载进行Rebalance以及维护这个映射

  • 为了提升单个Topic的并发性能,将单个Topic拆分为多个Partition
  • 为了提升横向扩展能力,将多个Partition分布在多个不同的Broker
  • 为了提升可用性,为每个Partition加了多个副本
  • 为了协调和管理kafka集群的数据消息,引入Zookeeper作为协调节点

Reblance过程不是瞬间完成的,而是要经历:注册旧分区👉选举Leader👉分配新分区👉初始化消费者 这一完整过程。在这个期间,所有消费者会停止消费

什么时候会触发Rebalance

  1. [最常见] 消费者数量发生变化(服务扩缩容的时候、某个节点出问题了重复重启)
  2. 调整了Topic的分区数量(kafka只支持增加),新增的分区需要分配消费者
  3. 相同消费组下订阅的Topic列表发生了变化
  4. [隐藏的坑] 消费者心跳超过了配置的session.timeout.ms(通常是45s)没有发,kafka会把这个消费者判定为死亡,从而踢出
  5. [隐藏的坑] 消费超时:通常单批消息超过max.poll.interval.ms(通常是5分钟)没有处理完(没有给ack),kafka会把这个消费者踢出

Rebalance期间会引发哪些问题

  1. 上述提到,Reblance过程不是瞬间完成的,且这个期间,所有消费者会停止消费,所以会产生消息积压

    所以产生积压的时候,优先看下是否发生了Rebalance

  2. 消息重复和丢失(见下方)
  3. Rebalance 要靠Coordinator协调,频繁触发会占用Kafka集群的CPU和网络资源
    • 而且Kafka默认的分区分配策略(Range 或 RoundRobin),很容易导致负载不均
    • 比如5个分区分配给2个消费者,可能出现 3个分区 vs 2个分区 的情况,其中一个消费者压力翻倍,处理速度变慢,又会触发新的Rebalance,陷入恶性循环

什么时候会丢消息

通常情况下,Reblance不会直接导致丢消息。消费者的处理和offset提交顺序没处理好,就容易出现丢消息的现象

  1. 消费者先提交offset(比如100),然后处理消息
  2. 开启了自动提交offset(比如100),也是没处理完就提交offset了 上述两种情况,当消费者在处理过程中断了(宕机、OOM、被kill等),导致Kafka触发Rebalance,其它节点接管这个Partition的消费,会从101开始消费
      sequenceDiagram
        participant A as Consumer A
        participant K as Kafka Broker
        participant B as Consumer B
    
        Note over A,K: 场景:自动提交 (Auto Commit)
        A->>K: Poll 消息 (Offset 100-200)
        activate A
        Note right of A: 放入本地队列/线程池
    
        Note over A,K: 触发自动提交 (5秒到了)
        A-->>K: 自动提交 Offset = 200
        Note right of K: 记录消费进度 = 200
    
        A->>A: 正在处理 Offset 150...
        Note right of A: 💥 节点突然宕机 (150-199 未处理完)
        deactivate A
    
        Note over K: 心跳丢失 -> Rebalance
        K->>B: 分配分区给 B
        B->>K: 获取已提交 Offset
        K-->>B: 返回 Offset = 200
        B->>K: Poll 下一批消息 (201+)
        Note right of B: 😱 Offset 150-199 永久丢失
    

正确的做法应该是先处理消息→再提交 offset,确保消息处理完才更新进度。

什么时候会重复消费

kafka Rebalance导致的重复消费更普遍,核心原因是offset提交晚于消息处理。

  1. 开启手动提交的情况下,若在处理完消息→提交 offset 的间隙触发 Rebalance,offset 没提交成功,新消费者会从上次提交的位置重新消费
      sequenceDiagram
        participant A as Consumer A
        participant K as Kafka Broker/Coordinator
        participant B as Consumer B
    
        Note over A,K: 初始状态:上次提交 Offset = 99
        A->>K: Poll消息 (Offset 100-200)
        A->>A: 业务处理消息 (100-200)
        Note right of A: 业务处理完毕,准备提交Offset
    
        K-->>A: 心跳超时/Session超时
        Note over K: 判定A死亡,触发Rebalance
        K->>B: 分配分区给 B
    
        B->>K: 获取已提交Offset
        K-->>B: 返回 Offset = 99 (A没来得及提交)
        B->>K: Poll消息 (Offset 100-200)
        Note right of B: 重复消费了 100-200
    
  2. 处理消息耗时超过 max.poll.interval.ms,消费者被判定死亡,但实际还在处理消息。
      sequenceDiagram
        participant A as Consumer A
        participant K as Kafka Broker/Coordinator
        participant B as Consumer B
    
        A->>K: Poll 这一批消息
        activate A
        Note right of A: 开始处理 (耗时较长)
    
        Note over A,K: 经过 5 分钟 (max.poll.interval.ms)
        Note over K: A 迟迟没发起下一次Poll<br/>判定A死亡,触发Rebalance
    
        K->>B: 分配分区给 B
        B->>K: 获取 Offset (旧值)
        activate B
        Note right of B: B 开始重复处理这批消息
    
        Note over A: 经过 6 分钟,A 终于处理完了
        A->>K: 尝试 Commit Offset
        K-->>A: 拒绝提交 (Fenced / Not Group Member)
        deactivate A
        deactivate B
    
  3. 如果消费者组的auto.offset.reset设为earliest(默认是 latest),Rebalance后找不到已提交的offset(比如offset数据损坏),会从Topic最早的消息开始消费,导致历史消息重复。

如何减少Rebalance以及带来的影响

  1. 相关参数优化
    • 调大消息处理耗时参数
    • 调大心跳判定时长,减少消费者被误判下线的几率
    • 选粘性分配策略:把partition.assignment.strategy设为StickyAssignor,Rebalance时尽量保留原有分配,减少分区变动。
  2. 优先手动提交,关闭自动提交(enable.auto.commit=false),在消息处理完成后再提交;
    • 必要时用事务,如果业务不允许重复消费,结合 Kafka 事务,确保消息处理 和 offset 提交原子性。
  3. 消息体内设置唯一字段,消费端根据此字段做好幂等处理