Kafka Rebalance 机制详解
一、Rebalance 基本概念
1. 什么是 Rebalance
Rebalance 是 Kafka 消费者组的一种分区重分配机制 ,当消费者组的状态发生变化时,触发所有分区在所有消费者之间重新分配,以达到负载均衡的目的。
graph TB
subgraph "Rebalance 前 - 负载不均"
direction TB
C1[Consumer 1] --> P0[Partition 0]
C1 --> P1[Partition 1]
C1 --> P2[Partition 2]
C1 --> P3[Partition 3]
C1 --> P4[Partition 4]
C2[Consumer 2] --> P5[Partition 5]
C3[Consumer 3]
C3 -.->|空闲| X[无分区分配]
style C1 fill:#f96,stroke:#333
style C2 fill:#9cf,stroke:#333
style C3 fill:#ccc,stroke:#333
style P0 fill:#f96
style P1 fill:#f96
style P2 fill:#f96
style P3 fill:#f96
style P4 fill:#f96
style P5 fill:#9cf
end
subgraph "Rebalance 中 - 暂停消费"
direction TB
C1'[Consumer 1] -.->|暂停| P0'[Partition 0]
C1' -.->|暂停| P1'[Partition 1]
C1' -.->|暂停| P2'[Partition 2]
C2'[Consumer 2] -.->|暂停| P3'[Partition 3]
C2' -.->|暂停| P4'[Partition 4]
C3'[Consumer 3] -.->|暂停| P5'[Partition 5]
GC[Group Coordinator] -->|重新分配| ALL
style C1' fill:#f96,stroke:#333,stroke-dasharray: 5 5
style C2' fill:#9cf,stroke:#333,stroke-dasharray: 5 5
style C3' fill:#9f9,stroke:#333,stroke-dasharray: 5 5
style GC fill:#f9f,stroke:#333,stroke-width:2px
end
subgraph "Rebalance 后 - 负载均衡"
direction TB
C1''[Consumer 1] --> P0''[Partition 0]
C1'' --> P1''[Partition 1]
C2''[Consumer 2] --> P2''[Partition 2]
C2'' --> P3''[Partition 3]
C3''[Consumer 3] --> P4''[Partition 4]
C3'' --> P5''[Partition 5]
style C1'' fill:#f96,stroke:#333
style C2'' fill:#9cf,stroke:#333
style C3'' fill:#9f9,stroke:#333
style P0'' fill:#f96
style P1'' fill:#f96
style P2'' fill:#9cf
style P3'' fill:#9cf
style P4'' fill:#9f9
style P5'' fill:#9f9
end
2. Rebalance 核心目标
目标
说明
负载均衡
确保每个消费者处理大致相等数量的分区
故障转移
消费者故障时,其分区被其他消费者接管
弹性伸缩
新增消费者时,自动分担负载
分区再分配
Topic 分区数变化时,重新分配
二、Rebalance 的三种核心策略
1. Range 策略(范围分配)
原理
Range 策略是基于单个 Topic 的分区分配策略,它将每个 Topic 的分区按照消费者顺序进行范围划分。
分配算法
java
复制代码
// 伪代码: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] (少分一个)
配置方式
java
复制代码
// 消费者配置
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 的所有分区 视为一个列表,轮询分配给所有消费者。
分配算法
java
复制代码
// 伪代码: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]
配置方式
java
复制代码
// 消费者配置
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 策略在保证负载均衡的前提下,尽可能保留上一次的分配结果 ,最小化分区移动。
核心原则
负载均衡 :最终分配结果尽可能均匀
粘性 :尽量保持现有分区分配不变
最小移动 :只移动必要的最小集合
分配算法
java
复制代码
// 伪代码:Sticky 分配算法
// 1. 保留现有分配中仍然有效的部分
// 2. 计算需要重新分配的剩余分区
// 3. 按负载均衡原则分配剩余分区
// 示例:
// 初始分配: C0: [A0, A1], C1: [A2, B0]
// 新增 C2 消费者
// Sticky 策略会尽量保留:
// C0: [A0, A1] (保持不动)
// C1: [A2] (只移动 B0)
// C2: [B0] (接收移动的分区)
配置方式
java
复制代码
// 消费者配置
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. 三种策略对比总结
特性
Range
RoundRobin
Sticky
分配粒度
按 Topic
全部分区
全部分区
均匀性
一般
优秀
优秀
移动成本
高
高
低
计算复杂度
低
中
高
适用场景
单 Topic
多 Topic 均匀
通用推荐
Rebalance 时间
中
中
短
消费者增减影响
大
中
小
三、触发 Rebalance 的原因
1. 消费者数量变化
graph TD
subgraph "触发场景"
A[消费者数量变化] --> A1[新消费者加入]
A --> A2[消费者主动退出]
A --> A3[消费者崩溃/超时]
A --> A4[消费者取消订阅]
end
subgraph "处理流程"
B[Group Coordinator 检测] --> C[触发 Rebalance]
C --> D[重新分配分区]
D --> E[消费者恢复消费]
end
(1)新消费者加入
java
复制代码
// 场景:新增消费者实例
// 触发条件
props.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group");
props.put(ConsumerConfig.CLIENT_ID_CONFIG, "consumer-3");
// 加入组流程
// 1. 消费者向 Coordinator 发送 JoinGroup 请求
// 2. Coordinator 检测到组变化
// 3. 触发 Rebalance,重新分配分区
(2)消费者离开/故障
2. Topic 分区数变化
bash
复制代码
# 场景:增加 Topic 分区
bin/kafka-topics.sh --alter \
--topic my-topic \
--bootstrap-server localhost:9092 \
--partitions 6
# 触发效果
# 1. 新增分区没有消费者
# 2. Group Coordinator 检测到分区变化
# 3. 触发 Rebalance 分配新增分区
3. 订阅关系变化
java
复制代码
// 场景:动态修改订阅
consumer.subscribe(Arrays.asList("topic1", "topic2")); // 初始订阅
// 修改订阅
consumer.subscribe(Arrays.asList("topic1", "topic3")); // 触发 Rebalance
// 取消订阅
consumer.unsubscribe(); // 触发 Rebalance
4. Group Coordinator 变更
graph LR
A[Group Coordinator 节点故障] --> B[新的 Broker 接管]
B --> C[加载消费者组元数据]
C --> D[触发 Rebalance]
D --> E[所有消费者重新连接]
四、Rebalance 详细流程
1. 完整 Rebalance 时序图
sequenceDiagram
participant C1 as Consumer 1
participant C2 as Consumer 2
participant C3 as Consumer 3
participant GC as Group Coordinator
participant ZK as Metadata Store
Note over C1,C3: 正常消费阶段
C3->>GC: 心跳超时/离开
GC->>GC: 检测到消费者变更
Note over GC: 触发 Rebalance
GC->>C1: 心跳响应: REBALANCE_IN_PROGRESS
GC->>C2: 心跳响应: REBALANCE_IN_PROGRESS
par JoinGroup 阶段
C1->>GC: JoinGroup (选举 Leader)
C2->>GC: JoinGroup (作为 Follower)
end
GC->>C1: 成为 Leader (包含成员列表)
Note over C1: Leader 执行分区分配
C1->>GC: SyncGroup (上传分配方案)
GC->>C2: SyncGroup (下发分配方案)
GC->>C3: 连接超时,踢出组
par 新分配生效
C1->>GC: 提交新 offset
C2->>GC: 提交新 offset
end
Note over C1,C2: 恢复正常消费
2. Rebalance 阶段详解
阶段一:发现阶段 (Detection)
java
复制代码
// Coordinator 检测到消费者变化
// 1. 心跳超时检测
// 2. 主动离开请求
// 3. 订阅变更请求
阶段二:JoinGroup 阶段
java
复制代码
// 消费者发送 JoinGroup 请求
JoinGroupRequest request = new JoinGroupRequest()
.setGroupId("my-group")
.setMemberId(currentMemberId)
.setProtocolType("consumer")
.setProtocols( subscriptions );
// Coordinator 响应
// - 指定 Leader 消费者
// - 返回当前组成员列表
阶段三:SyncGroup 阶段
java
复制代码
// 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)
java
复制代码
// 消费者收到分配结果
// 1. 撤销原有分区
// 2. 分配新分区
// 3. 开始消费
// 4. 恢复正常心跳
五、Rebalance 的优缺点
1. 优点
优点
说明
示例场景
自动负载均衡
消费者负载自动调整,无需人工干预
新增消费者自动分担压力
高可用性
消费者故障时自动转移分区
某消费者宕机,其他接管
弹性伸缩
支持动态扩缩容
业务高峰期增加消费者
分区变化适应
Topic 扩容自动分配
从3分区扩到6分区
容错性
网络闪断后自动恢复
消费者重启后重新加入
2. 缺点
缺点
说明
影响程度
Stop-The-World
Rebalance 期间所有消费者暂停消费
高
重复消费
分区重新分配导致消息被多次处理
中
消费延迟
Rebalance 期间消息积压
高
频繁 Rebalance
配置不当导致频繁触发
中
数据倾斜
分配不均导致部分消费者过载
低
状态丢失
本地状态需要重建
中
3. Rebalance 代价分析
java
复制代码
// 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. 参数优化
properties
复制代码
# 消费者配置优化
# 心跳相关(减少误判)
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. 静态成员配置
java
复制代码
// 静态成员(Static Membership)- 减少 Rebalance
props.put(ConsumerConfig.GROUP_INSTANCE_ID_CONFIG, "consumer-1-static");
// 优点:消费者重启时保留分区分配
// 适用场景:重要消费者,频繁重启的场景
3. 渐进式 Rebalance
java
复制代码
// 协同式粘性分配(Cooperative Sticky)
props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,
CooperativeStickyAssignor.class.getName());
// 特点:
// - 分批撤销分区
// - 减少 STW 时间
// - 部分消费者可继续消费
4. 业务层优化
java
复制代码
@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. 监控指标
bash
复制代码
# 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. 日志排查
bash
复制代码
# 查看 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. 配置推荐
properties
复制代码
# 生产环境推荐配置
# 通用配置
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. 代码最佳实践
java
复制代码
@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. 监控告警配置
yaml
复制代码
# 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
九、总结
Rebalance 核心要点
维度
关键点
三种策略
Range(范围)、RoundRobin(轮询)、Sticky(粘性)
触发原因
消费者变化、分区变化、订阅变化、Coordinator变更
主要缺点
Stop-The-World、重复消费、延迟增加
优化方向
Sticky策略、参数调优、静态成员、幂等处理
监控重点
Rebalance频率、耗时、Lag变化
Rebalance 是 Kafka 实现自动负载均衡的核心机制,但也是一把双刃剑 ------它保证了高可用和弹性,但也带来了短暂的服务暂停和重复消费。通过选择合适的分配策略、合理配置参数、实现幂等处理和状态管理,可以将 Rebalance 的影响降到最低。