欢迎光临
我们一直在努力

Kafka的Rebalance基础介绍

Kafka Rebalance 机制详解

一、Rebalance 基本概念

1. 什么是 Rebalance

Rebalance 是 Kafka 消费者组的一种分区重分配机制,当消费者组的状态发生变化时,触发所有分区在所有消费者之间重新分配,以达到负载均衡的目的。

#mermaid-svg-QFzb6Oldj2pv2YTK{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QFzb6Oldj2pv2YTK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QFzb6Oldj2pv2YTK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QFzb6Oldj2pv2YTK .error-icon{fill:#552222;}#mermaid-svg-QFzb6Oldj2pv2YTK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QFzb6Oldj2pv2YTK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QFzb6Oldj2pv2YTK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QFzb6Oldj2pv2YTK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QFzb6Oldj2pv2YTK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QFzb6Oldj2pv2YTK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QFzb6Oldj2pv2YTK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QFzb6Oldj2pv2YTK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QFzb6Oldj2pv2YTK .marker.cross{stroke:#333333;}#mermaid-svg-QFzb6Oldj2pv2YTK svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QFzb6Oldj2pv2YTK p{margin:0;}#mermaid-svg-QFzb6Oldj2pv2YTK .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-QFzb6Oldj2pv2YTK .cluster-label text{fill:#333;}#mermaid-svg-QFzb6Oldj2pv2YTK .cluster-label span{color:#333;}#mermaid-svg-QFzb6Oldj2pv2YTK .cluster-label span p{background-color:transparent;}#mermaid-svg-QFzb6Oldj2pv2YTK .label text,#mermaid-svg-QFzb6Oldj2pv2YTK span{fill:#333;color:#333;}#mermaid-svg-QFzb6Oldj2pv2YTK .node rect,#mermaid-svg-QFzb6Oldj2pv2YTK .node circle,#mermaid-svg-QFzb6Oldj2pv2YTK .node ellipse,#mermaid-svg-QFzb6Oldj2pv2YTK .node polygon,#mermaid-svg-QFzb6Oldj2pv2YTK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QFzb6Oldj2pv2YTK .rough-node .label text,#mermaid-svg-QFzb6Oldj2pv2YTK .node .label text,#mermaid-svg-QFzb6Oldj2pv2YTK .image-shape .label,#mermaid-svg-QFzb6Oldj2pv2YTK .icon-shape .label{text-anchor:middle;}#mermaid-svg-QFzb6Oldj2pv2YTK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QFzb6Oldj2pv2YTK .rough-node .label,#mermaid-svg-QFzb6Oldj2pv2YTK .node .label,#mermaid-svg-QFzb6Oldj2pv2YTK .image-shape .label,#mermaid-svg-QFzb6Oldj2pv2YTK .icon-shape .label{text-align:center;}#mermaid-svg-QFzb6Oldj2pv2YTK .node.clickable{cursor:pointer;}#mermaid-svg-QFzb6Oldj2pv2YTK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QFzb6Oldj2pv2YTK .arrowheadPath{fill:#333333;}#mermaid-svg-QFzb6Oldj2pv2YTK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QFzb6Oldj2pv2YTK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QFzb6Oldj2pv2YTK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QFzb6Oldj2pv2YTK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QFzb6Oldj2pv2YTK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QFzb6Oldj2pv2YTK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QFzb6Oldj2pv2YTK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QFzb6Oldj2pv2YTK .cluster text{fill:#333;}#mermaid-svg-QFzb6Oldj2pv2YTK .cluster span{color:#333;}#mermaid-svg-QFzb6Oldj2pv2YTK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QFzb6Oldj2pv2YTK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QFzb6Oldj2pv2YTK rect.text{fill:none;stroke-width:0;}#mermaid-svg-QFzb6Oldj2pv2YTK .icon-shape,#mermaid-svg-QFzb6Oldj2pv2YTK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QFzb6Oldj2pv2YTK .icon-shape p,#mermaid-svg-QFzb6Oldj2pv2YTK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QFzb6Oldj2pv2YTK .icon-shape rect,#mermaid-svg-QFzb6Oldj2pv2YTK .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QFzb6Oldj2pv2YTK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QFzb6Oldj2pv2YTK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QFzb6Oldj2pv2YTK :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

Rebalance 后 – 负载均衡

Consumer 1

Partition 0

Partition 1

Consumer 2

Partition 2

Partition 3

Consumer 3

Partition 4

Partition 5

Rebalance 中 – 暂停消费

暂停

暂停

暂停

暂停

暂停

暂停

重新分配

Consumer 1

Partition 0

Partition 1

Partition 2

Consumer 2

Partition 3

Partition 4

Consumer 3

Partition 5

Group Coordinator

ALL

Rebalance 前 – 负载不均

空闲

Consumer 1

Partition 0

Partition 1

Partition 2

Partition 3

Partition 4

Consumer 2

Partition 5

Consumer 3

无分区分配

2. Rebalance 核心目标

目标说明
负载均衡 确保每个消费者处理大致相等数量的分区
故障转移 消费者故障时,其分区被其他消费者接管
弹性伸缩 新增消费者时,自动分担负载
分区再分配 Topic 分区数变化时,重新分配

二、Rebalance 的三种核心策略

1. Range 策略(范围分配)

原理

Range 策略是基于单个 Topic 的分区分配策略,它将每个 Topic 的分区按照消费者顺序进行范围划分。

分配算法

// 伪代码:Range 分配算法
// 对于每个 Topic:
// 1. 对消费者按字典序排序 [C0, C1, C2]
// 2. 计算每个消费者分配的分区数 = 分区数 / 消费者数
// 3. 余数分配给前几个消费者

// 示例:TopicA 有 5 个分区 [0,1,2,3,4],3 个消费者
// 每个消费者应得 = 5/3 = 1 个分区,余数 2
// 分配结果:
// C0: [0,1] (多分一个)
// C1: [2,3] (多分一个)
// C2: [4] (少分一个)

配置方式

// 消费者配置
props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,
RangeAssignor.class.getName());

// Spring Boot 配置
spring:
kafka:
consumer:
properties:
partition.assignment.strategy: org.apache.kafka.clients.consumer.RangeAssignor

优缺点
优点缺点
实现简单,易于理解 存在分配不均问题(余数分配)
同一个 Topic 的分区尽量集中 多个 Topic 时可能造成某个消费者负载过重
适合分区数少的场景 消费者增减时影响范围大

2. RoundRobin 策略(轮询分配)

原理

RoundRobin 策略将所有 Topic 的所有分区视为一个列表,轮询分配给所有消费者。

分配算法

// 伪代码:RoundRobin 分配算法
// 1. 收集所有订阅 Topic 的所有分区
// 2. 消费者按字典序排序
// 3. 轮询分配每个分区给下一个消费者

// 示例:
// TopicA: [0,1,2], TopicB: [0,1], 消费者 [C0, C1]
// 所有分区列表: [A0, A1, A2, B0, B1]
// 轮询分配:
// A0 -> C0
// A1 -> C1
// A2 -> C0
// B0 -> C1
// B1 -> C0
// 结果: C0: [A0, A2, B1], C1: [A1, B0]

配置方式

// 消费者配置
props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,
RoundRobinAssignor.class.getName());

// Spring Boot 配置
spring:
kafka:
consumer:
properties:
partition.assignment.strategy: org.apache.kafka.clients.consumer.RoundRobinAssignor

优缺点
优点缺点
分配最均匀,负载均衡效果好 每次 Rebalance 都需要全量计算
跨 Topic 的负载均衡 消费者订阅不同 Topic 时可能无效
适合多 Topic 场景 计算复杂度较高

3. Sticky 策略(粘性分配)

原理

Sticky 策略在保证负载均衡的前提下,尽可能保留上一次的分配结果,最小化分区移动。

核心原则
  • 负载均衡:最终分配结果尽可能均匀
  • 粘性:尽量保持现有分区分配不变
  • 最小移动:只移动必要的最小集合
  • 分配算法

    // 伪代码:Sticky 分配算法
    // 1. 保留现有分配中仍然有效的部分
    // 2. 计算需要重新分配的剩余分区
    // 3. 按负载均衡原则分配剩余分区

    // 示例:
    // 初始分配: C0: [A0, A1], C1: [A2, B0]
    // 新增 C2 消费者
    // Sticky 策略会尽量保留:
    // C0: [A0, A1] (保持不动)
    // C1: [A2] (只移动 B0)
    // C2: [B0] (接收移动的分区)

    配置方式

    // 消费者配置
    props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,
    StickyAssignor.class.getName());

    // Spring Boot 配置
    spring:
    kafka:
    consumer:
    properties:
    partition.assignment.strategy: org.apache.kafka.clients.consumer.StickyAssignor

    // 协同式粘性分配(推荐)
    props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,
    CooperativeStickyAssignor.class.getName());

    优缺点
    优点缺点
    最小化分区移动,减少开销 算法复杂,实现难度大
    减少重复消费和空消费时间 需要消费者版本支持
    负载均衡效果好 协调开销略大
    协同式支持渐进式 Rebalance

    4. 三种策略对比总结

    特性RangeRoundRobinSticky
    分配粒度 按 Topic 全部分区 全部分区
    均匀性 一般 优秀 优秀
    移动成本
    计算复杂度
    适用场景 单 Topic 多 Topic 均匀 通用推荐
    Rebalance 时间
    消费者增减影响

    三、触发 Rebalance 的原因

    1. 消费者数量变化

    #mermaid-svg-XmOLcQHimRSTGH5h{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XmOLcQHimRSTGH5h .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XmOLcQHimRSTGH5h .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XmOLcQHimRSTGH5h .error-icon{fill:#552222;}#mermaid-svg-XmOLcQHimRSTGH5h .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XmOLcQHimRSTGH5h .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XmOLcQHimRSTGH5h .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XmOLcQHimRSTGH5h .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XmOLcQHimRSTGH5h .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XmOLcQHimRSTGH5h .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XmOLcQHimRSTGH5h .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XmOLcQHimRSTGH5h .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XmOLcQHimRSTGH5h .marker.cross{stroke:#333333;}#mermaid-svg-XmOLcQHimRSTGH5h svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XmOLcQHimRSTGH5h p{margin:0;}#mermaid-svg-XmOLcQHimRSTGH5h .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-XmOLcQHimRSTGH5h .cluster-label text{fill:#333;}#mermaid-svg-XmOLcQHimRSTGH5h .cluster-label span{color:#333;}#mermaid-svg-XmOLcQHimRSTGH5h .cluster-label span p{background-color:transparent;}#mermaid-svg-XmOLcQHimRSTGH5h .label text,#mermaid-svg-XmOLcQHimRSTGH5h span{fill:#333;color:#333;}#mermaid-svg-XmOLcQHimRSTGH5h .node rect,#mermaid-svg-XmOLcQHimRSTGH5h .node circle,#mermaid-svg-XmOLcQHimRSTGH5h .node ellipse,#mermaid-svg-XmOLcQHimRSTGH5h .node polygon,#mermaid-svg-XmOLcQHimRSTGH5h .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XmOLcQHimRSTGH5h .rough-node .label text,#mermaid-svg-XmOLcQHimRSTGH5h .node .label text,#mermaid-svg-XmOLcQHimRSTGH5h .image-shape .label,#mermaid-svg-XmOLcQHimRSTGH5h .icon-shape .label{text-anchor:middle;}#mermaid-svg-XmOLcQHimRSTGH5h .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XmOLcQHimRSTGH5h .rough-node .label,#mermaid-svg-XmOLcQHimRSTGH5h .node .label,#mermaid-svg-XmOLcQHimRSTGH5h .image-shape .label,#mermaid-svg-XmOLcQHimRSTGH5h .icon-shape .label{text-align:center;}#mermaid-svg-XmOLcQHimRSTGH5h .node.clickable{cursor:pointer;}#mermaid-svg-XmOLcQHimRSTGH5h .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XmOLcQHimRSTGH5h .arrowheadPath{fill:#333333;}#mermaid-svg-XmOLcQHimRSTGH5h .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XmOLcQHimRSTGH5h .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XmOLcQHimRSTGH5h .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XmOLcQHimRSTGH5h .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XmOLcQHimRSTGH5h .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XmOLcQHimRSTGH5h .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XmOLcQHimRSTGH5h .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XmOLcQHimRSTGH5h .cluster text{fill:#333;}#mermaid-svg-XmOLcQHimRSTGH5h .cluster span{color:#333;}#mermaid-svg-XmOLcQHimRSTGH5h div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XmOLcQHimRSTGH5h .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XmOLcQHimRSTGH5h rect.text{fill:none;stroke-width:0;}#mermaid-svg-XmOLcQHimRSTGH5h .icon-shape,#mermaid-svg-XmOLcQHimRSTGH5h .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XmOLcQHimRSTGH5h .icon-shape p,#mermaid-svg-XmOLcQHimRSTGH5h .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XmOLcQHimRSTGH5h .icon-shape rect,#mermaid-svg-XmOLcQHimRSTGH5h .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XmOLcQHimRSTGH5h .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XmOLcQHimRSTGH5h .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XmOLcQHimRSTGH5h :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    处理流程

    Group Coordinator 检测

    触发 Rebalance

    重新分配分区

    消费者恢复消费

    触发场景

    消费者数量变化

    新消费者加入

    消费者主动退出

    消费者崩溃/超时

    消费者取消订阅

    (1)新消费者加入

    // 场景:新增消费者实例
    // 触发条件
    props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group");
    props.put(ConsumerConfig.CLIENT_ID_CONFIG, "consumer-3");

    // 加入组流程
    // 1. 消费者向 Coordinator 发送 JoinGroup 请求
    // 2. Coordinator 检测到组变化
    // 3. 触发 Rebalance,重新分配分区

    (2)消费者离开/故障
    离开类型检测机制超时时间影响
    主动关闭 发送 LeaveGroup 请求 立即 立即触发 Rebalance
    会话超时 心跳超时 session.timeout.ms (默认45秒) 超时后触发
    Poll 超时 poll() 间隔超时 max.poll.interval.ms (默认5分钟) 超时后触发
    网络分区 网络不可达 取决于网络配置 超时后触发

    2. Topic 分区数变化

    # 场景:增加 Topic 分区
    bin/kafka-topics.sh –alter \\
    –topic my-topic \\
    –bootstrap-server localhost:9092 \\
    –partitions 6

    # 触发效果
    # 1. 新增分区没有消费者
    # 2. Group Coordinator 检测到分区变化
    # 3. 触发 Rebalance 分配新增分区

    3. 订阅关系变化

    // 场景:动态修改订阅
    consumer.subscribe(Arrays.asList("topic1", "topic2")); // 初始订阅

    // 修改订阅
    consumer.subscribe(Arrays.asList("topic1", "topic3")); // 触发 Rebalance

    // 取消订阅
    consumer.unsubscribe(); // 触发 Rebalance

    4. Group Coordinator 变更

    #mermaid-svg-UfrLZo5w976wdlgq{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UfrLZo5w976wdlgq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UfrLZo5w976wdlgq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UfrLZo5w976wdlgq .error-icon{fill:#552222;}#mermaid-svg-UfrLZo5w976wdlgq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UfrLZo5w976wdlgq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UfrLZo5w976wdlgq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UfrLZo5w976wdlgq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UfrLZo5w976wdlgq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UfrLZo5w976wdlgq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UfrLZo5w976wdlgq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UfrLZo5w976wdlgq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UfrLZo5w976wdlgq .marker.cross{stroke:#333333;}#mermaid-svg-UfrLZo5w976wdlgq svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UfrLZo5w976wdlgq p{margin:0;}#mermaid-svg-UfrLZo5w976wdlgq .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-UfrLZo5w976wdlgq .cluster-label text{fill:#333;}#mermaid-svg-UfrLZo5w976wdlgq .cluster-label span{color:#333;}#mermaid-svg-UfrLZo5w976wdlgq .cluster-label span p{background-color:transparent;}#mermaid-svg-UfrLZo5w976wdlgq .label text,#mermaid-svg-UfrLZo5w976wdlgq span{fill:#333;color:#333;}#mermaid-svg-UfrLZo5w976wdlgq .node rect,#mermaid-svg-UfrLZo5w976wdlgq .node circle,#mermaid-svg-UfrLZo5w976wdlgq .node ellipse,#mermaid-svg-UfrLZo5w976wdlgq .node polygon,#mermaid-svg-UfrLZo5w976wdlgq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UfrLZo5w976wdlgq .rough-node .label text,#mermaid-svg-UfrLZo5w976wdlgq .node .label text,#mermaid-svg-UfrLZo5w976wdlgq .image-shape .label,#mermaid-svg-UfrLZo5w976wdlgq .icon-shape .label{text-anchor:middle;}#mermaid-svg-UfrLZo5w976wdlgq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UfrLZo5w976wdlgq .rough-node .label,#mermaid-svg-UfrLZo5w976wdlgq .node .label,#mermaid-svg-UfrLZo5w976wdlgq .image-shape .label,#mermaid-svg-UfrLZo5w976wdlgq .icon-shape .label{text-align:center;}#mermaid-svg-UfrLZo5w976wdlgq .node.clickable{cursor:pointer;}#mermaid-svg-UfrLZo5w976wdlgq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UfrLZo5w976wdlgq .arrowheadPath{fill:#333333;}#mermaid-svg-UfrLZo5w976wdlgq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UfrLZo5w976wdlgq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UfrLZo5w976wdlgq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UfrLZo5w976wdlgq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UfrLZo5w976wdlgq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UfrLZo5w976wdlgq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UfrLZo5w976wdlgq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UfrLZo5w976wdlgq .cluster text{fill:#333;}#mermaid-svg-UfrLZo5w976wdlgq .cluster span{color:#333;}#mermaid-svg-UfrLZo5w976wdlgq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UfrLZo5w976wdlgq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UfrLZo5w976wdlgq rect.text{fill:none;stroke-width:0;}#mermaid-svg-UfrLZo5w976wdlgq .icon-shape,#mermaid-svg-UfrLZo5w976wdlgq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UfrLZo5w976wdlgq .icon-shape p,#mermaid-svg-UfrLZo5w976wdlgq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UfrLZo5w976wdlgq .icon-shape rect,#mermaid-svg-UfrLZo5w976wdlgq .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UfrLZo5w976wdlgq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UfrLZo5w976wdlgq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UfrLZo5w976wdlgq :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    Group Coordinator 节点故障

    新的 Broker 接管

    加载消费者组元数据

    触发 Rebalance

    所有消费者重新连接


    四、Rebalance 详细流程

    1. 完整 Rebalance 时序图

    Metadata StoreGroup CoordinatorConsumer 3Consumer 2Consumer 1Metadata StoreGroup CoordinatorConsumer 3Consumer 2Consumer 1#mermaid-svg-YsEfNSJEgVgJUVp2{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YsEfNSJEgVgJUVp2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YsEfNSJEgVgJUVp2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YsEfNSJEgVgJUVp2 .error-icon{fill:#552222;}#mermaid-svg-YsEfNSJEgVgJUVp2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YsEfNSJEgVgJUVp2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YsEfNSJEgVgJUVp2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YsEfNSJEgVgJUVp2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YsEfNSJEgVgJUVp2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YsEfNSJEgVgJUVp2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YsEfNSJEgVgJUVp2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YsEfNSJEgVgJUVp2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YsEfNSJEgVgJUVp2 .marker.cross{stroke:#333333;}#mermaid-svg-YsEfNSJEgVgJUVp2 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YsEfNSJEgVgJUVp2 p{margin:0;}#mermaid-svg-YsEfNSJEgVgJUVp2 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YsEfNSJEgVgJUVp2 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-YsEfNSJEgVgJUVp2 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-YsEfNSJEgVgJUVp2 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-YsEfNSJEgVgJUVp2 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-YsEfNSJEgVgJUVp2 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-YsEfNSJEgVgJUVp2 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-YsEfNSJEgVgJUVp2 .sequenceNumber{fill:white;}#mermaid-svg-YsEfNSJEgVgJUVp2 #sequencenumber{fill:#333;}#mermaid-svg-YsEfNSJEgVgJUVp2 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-YsEfNSJEgVgJUVp2 .messageText{fill:#333;stroke:none;}#mermaid-svg-YsEfNSJEgVgJUVp2 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YsEfNSJEgVgJUVp2 .labelText,#mermaid-svg-YsEfNSJEgVgJUVp2 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-YsEfNSJEgVgJUVp2 .loopText,#mermaid-svg-YsEfNSJEgVgJUVp2 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-YsEfNSJEgVgJUVp2 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-YsEfNSJEgVgJUVp2 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-YsEfNSJEgVgJUVp2 .noteText,#mermaid-svg-YsEfNSJEgVgJUVp2 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-YsEfNSJEgVgJUVp2 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YsEfNSJEgVgJUVp2 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YsEfNSJEgVgJUVp2 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YsEfNSJEgVgJUVp2 .actorPopupMenu{position:absolute;}#mermaid-svg-YsEfNSJEgVgJUVp2 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-YsEfNSJEgVgJUVp2 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YsEfNSJEgVgJUVp2 .actor-man circle,#mermaid-svg-YsEfNSJEgVgJUVp2 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-YsEfNSJEgVgJUVp2 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}正常消费阶段触发 Rebalancepar[JoinGroup 阶段]Leader 执行分区分配par[新分配生效]恢复正常消费心跳超时/离开检测到消费者变更心跳响应: REBALANCE_IN_PROGRESS心跳响应: REBALANCE_IN_PROGRESSJoinGroup (选举 Leader)JoinGroup (作为 Follower)成为 Leader (包含成员列表)SyncGroup (上传分配方案)SyncGroup (下发分配方案)连接超时,踢出组提交新 offset提交新 offset

    2. Rebalance 阶段详解

    阶段一:发现阶段 (Detection)

    // Coordinator 检测到消费者变化
    // 1. 心跳超时检测
    // 2. 主动离开请求
    // 3. 订阅变更请求

    阶段二:JoinGroup 阶段

    // 消费者发送 JoinGroup 请求
    JoinGroupRequest request = new JoinGroupRequest()
    .setGroupId("my-group")
    .setMemberId(currentMemberId)
    .setProtocolType("consumer")
    .setProtocols( subscriptions );

    // Coordinator 响应
    // – 指定 Leader 消费者
    // – 返回当前组成员列表

    阶段三:SyncGroup 阶段

    // Leader 消费者执行分区分配
    PartitionAssignor assignor = new RangeAssignor();
    Map<String, Assignment> assignments =
    assignor.assign(metadata, groupSubscription);

    // Leader 发送分配结果给 Coordinator
    SyncGroupRequest request = new SyncGroupRequest()
    .setGroupId("my-group")
    .setMemberId(leaderId)
    .setAssignments(assignments);

    // Coordinator 广播给所有消费者

    阶段四:稳定阶段 (Stable)

    // 消费者收到分配结果
    // 1. 撤销原有分区
    // 2. 分配新分区
    // 3. 开始消费
    // 4. 恢复正常心跳


    五、Rebalance 的优缺点

    1. 优点

    优点说明示例场景
    自动负载均衡 消费者负载自动调整,无需人工干预 新增消费者自动分担压力
    高可用性 消费者故障时自动转移分区 某消费者宕机,其他接管
    弹性伸缩 支持动态扩缩容 业务高峰期增加消费者
    分区变化适应 Topic 扩容自动分配 从3分区扩到6分区
    容错性 网络闪断后自动恢复 消费者重启后重新加入

    2. 缺点

    缺点说明影响程度
    Stop-The-World Rebalance 期间所有消费者暂停消费
    重复消费 分区重新分配导致消息被多次处理
    消费延迟 Rebalance 期间消息积压
    频繁 Rebalance 配置不当导致频繁触发
    数据倾斜 分配不均导致部分消费者过载
    状态丢失 本地状态需要重建

    3. Rebalance 代价分析

    // Rebalance 代价计算
    class RebalanceCost {

    // 计算 Rebalance 总代价
    long calculateTotalCost(RebalanceEvent event) {
    // 1. 暂停消费时间
    long stopTime = event.getDuration();

    // 2. 重复消费代价
    long duplicateMessages = event.getReassignedPartitions()
    .stream()
    .mapToLong(p -> p.getLastProcessedOffset() p.getLastCommittedOffset())
    .sum();

    // 3. 状态重建代价
    long stateRebuildTime = event.getStatefulConsumers()
    .stream()
    .mapToLong(c -> c.rebuildState())
    .sum();

    // 4. 网络开销
    long networkCost = event.getMembers() *
    (JOIN_REQUEST_SIZE + SYNC_REQUEST_SIZE);

    return stopTime + duplicateMessages * 10 + stateRebuildTime + networkCost;
    }
    }


    六、Rebalance 优化策略

    1. 参数优化

    # 消费者配置优化
    # 心跳相关(减少误判)
    session.timeout.ms=45000 # 会话超时(适当增大)
    heartbeat.interval.ms=3000 # 心跳间隔(session的1/3)
    max.poll.interval.ms=300000 # 最大 poll 间隔(5分钟)

    # Rebalance 相关
    partition.assignment.strategy=org.apache.kafka.clients.consumer.StickyAssignor # 粘性分配
    max.poll.records=500 # 每次 poll 最大记录数(防止处理过慢)

    # 超时设置
    default.api.timeout.ms=60000 # API 超时
    request.timeout.ms=30000 # 请求超时

    # 连接优化
    reconnect.backoff.ms=50 # 重连退避
    reconnect.backoff.max.ms=1000 # 最大重连退避

    2. 静态成员配置

    // 静态成员(Static Membership)- 减少 Rebalance
    props.put(ConsumerConfig.GROUP_INSTANCE_ID_CONFIG, "consumer-1-static");

    // 优点:消费者重启时保留分区分配
    // 适用场景:重要消费者,频繁重启的场景

    3. 渐进式 Rebalance

    // 协同式粘性分配(Cooperative Sticky)
    props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,
    CooperativeStickyAssignor.class.getName());

    // 特点:
    // – 分批撤销分区
    // – 减少 STW 时间
    // – 部分消费者可继续消费

    4. 业务层优化

    @Component
    public class OptimizedConsumer {

    @KafkaListener(topics = "my-topic")
    public void consume(ConsumerRecord<String, String> record) {
    try {
    // 1. 幂等处理(防止重复消费)
    if (isProcessed(record)) {
    return;
    }

    // 2. 快速处理,避免 poll 超时
    processWithTimeout(record, 1000);

    // 3. 异步提交 offset
    commitOffsetAsync(record);

    } catch (TimeoutException e) {
    // 4. 超时处理,避免触发 Rebalance
    log.warn("处理超时,稍后重试");
    throw new RetryableException(e);
    }
    }

    // 5. 监听 Rebalance 事件
    @KafkaListener(topics = "my-topic")
    public void consumeWithRebalanceListener(ConsumerRecord<String, String> record,
    Consumer consumer) {
    consumer.subscribe(Arrays.asList("my-topic"), new ConsumerRebalanceListener() {
    @Override
    public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
    // 分区被撤销前:提交 offset,清理状态
    consumer.commitSync();
    clearLocalState(partitions);
    }

    @Override
    public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
    // 新分区分配后:初始化状态
    initializeLocalState(partitions);
    }
    });
    }
    }


    七、Rebalance 监控与排查

    1. 监控指标

    # 1. 查看消费者组状态
    bin/kafka-consumer-groups.sh –describe –group my-group –bootstrap-server localhost:9092

    # 输出关键指标
    # – LAG: 积压消息数(Rebalance 期间会增大)
    # – CURRENT-OFFSET: 当前偏移量
    # – LOG-END-OFFSET: 最新偏移量

    # 2. JMX 监控指标
    # MBean: kafka.consumer:type=consumer-coordinator-metrics
    # – rebalance-total: Rebalance 总次数
    # – rebalance-rate-per-hour: 每小时 Rebalance 次数
    # – rebalance-latency-avg: 平均 Rebalance 延迟

    2. 日志排查

    # 查看 Rebalance 相关日志
    grep "Rebalance" /var/log/kafka/consumer.log

    # 常见日志模式
    # 1. 触发 Rebalance
    INFO: [Consumer clientId=consumer-1, groupId=my-group]
    Preparing to rebalance group

    # 2. JoinGroup
    INFO: [Consumer clientId=consumer-1, groupId=my-group]
    Successfully joined group with generation 5

    # 3. 分配结果
    INFO: [Consumer clientId=consumer-1, groupId=my-group]
    Assigned partitions: [topic-0, topic-1, topic-2]

    # 4. 完成 Rebalance
    INFO: [Consumer clientId=consumer-1, groupId=my-group]
    Completed rebalance in 3456 ms

    3. 问题排查清单

    问题现象可能原因排查命令解决方案
    频繁 Rebalance session.timeout.ms 太小 查看心跳日志 增大超时时间
    Rebalance 时间过长 分区数太多 查看分配时间 使用 Sticky 策略
    重复消费严重 提交 offset 不及时 查看 offset 提交日志 改为同步提交
    消费者无法加入 max.poll.interval.ms 太小 查看处理时间 增大间隔或优化代码
    分配不均 Range 策略导致 查看分配结果 改用 RoundRobin/Sticky

    八、最佳实践总结

    1. 配置推荐

    # 生产环境推荐配置
    # 通用配置
    session.timeout.ms=45000
    heartbeat.interval.ms=15000
    max.poll.interval.ms=300000
    max.poll.records=500

    # 分配策略(推荐)
    partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor

    # 关键消费者(可选)
    group.instance.id=consumer-1-static # 静态成员

    # 提交配置
    enable.auto.commit=false # 手动提交
    auto.commit.interval.ms=5000 # 如果自动提交

    2. 代码最佳实践

    @Component
    public class BestPracticeConsumer {

    @KafkaListener(topics = "my-topic")
    public void consume(ConsumerRecord<String, String> record,
    Acknowledgment ack) {

    // 1. 幂等处理
    String messageId = record.key();
    if (redisUtils.exists(messageId)) {
    ack.acknowledge(); // 已处理过,直接提交
    return;
    }

    try {
    // 2. 业务处理(设置超时)
    CompletableFuture.runAsync(() -> process(record))
    .orTimeout(30, TimeUnit.SECONDS)
    .join();

    // 3. 记录处理状态
    redisUtils.set(messageId, "processed", 1, TimeUnit.HOURS);

    // 4. 手动提交
    ack.acknowledge();

    } catch (Exception e) {
    // 5. 异常处理
    log.error("处理失败", e);
    // 根据异常类型决定是否重试
    if (isRetryable(e)) {
    throw new RetryableException(e); // 触发重试
    } else {
    sendToDlq(record); // 发送死信队列
    ack.acknowledge(); // 避免阻塞
    }
    }
    }

    // 6. Rebalance 监听器
    @Bean
    public ConsumerRebalanceListener rebalanceListener() {
    return new ConsumerRebalanceListener() {
    @Override
    public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
    log.info("分区被撤销: {}", partitions);
    // 提交最后的 offset
    // 清理本地状态
    }

    @Override
    public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
    log.info("新分配分区: {}", partitions);
    // 初始化状态
    // 可指定从哪个 offset 开始
    }
    };
    }
    }

    3. 监控告警配置

    # Prometheus 告警规则
    groups:
    name: kafka_rebalance_alerts
    rules:
    # 频繁 Rebalance 告警
    alert: KafkaHighRebalanceRate
    expr: rate(kafka_consumer_coordinator_rebalance_total[5m]) > 0.1
    for: 10m
    annotations:
    summary: "高频 Rebalance 检测"
    description: "消费者组 {{ $labels.group }} 每5分钟 Rebalance 次数 > 0.1"

    # Rebalance 耗时过长
    alert: KafkaSlowRebalance
    expr: kafka_consumer_coordinator_rebalance_latency_avg > 10000
    for: 5m
    annotations:
    summary: "Rebalance 耗时过长"
    description: "平均 Rebalance 耗时 {{ $value }}ms"

    # 消费者 Lag 突增
    alert: KafkaLagSpike
    expr: delta(kafka_consumer_lag[5m]) > 10000
    for: 2m
    annotations:
    summary: "消息积压突增"
    description: "可能正在 Rebalance,积压增加 {{ $value }}"

    4. 性能优化 checklist

    • 选择 CooperativeStickyAssignor 策略
    • 配置合理的 session.timeout.ms (30-45秒)
    • 确保 max.poll.interval.ms > 最大处理时间
    • 关键消费者使用 静态成员
    • 实现 幂等处理 防止重复消费
    • 添加 Rebalance 监听器 处理状态
    • 监控 Rebalance 频率和耗时
    • 分区数合理(≤ 消费者数 × 消费能力)
    • 消费者实例数稳定,避免频繁扩缩容

    九、总结

    Rebalance 核心要点

    维度关键点
    三种策略 Range(范围)、RoundRobin(轮询)、Sticky(粘性)
    触发原因 消费者变化、分区变化、订阅变化、Coordinator变更
    主要缺点 Stop-The-World、重复消费、延迟增加
    优化方向 Sticky策略、参数调优、静态成员、幂等处理
    监控重点 Rebalance频率、耗时、Lag变化

    Rebalance 是 Kafka 实现自动负载均衡的核心机制,但也是一把双刃剑——它保证了高可用和弹性,但也带来了短暂的服务暂停和重复消费。通过选择合适的分配策略、合理配置参数、实现幂等处理和状态管理,可以将 Rebalance 的影响降到最低。

    赞(0)
    未经允许不得转载:171主机测评 » Kafka的Rebalance基础介绍
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址