Consumer 分区分配策略
定义与作用
分区分配策略决定了消费组内各 Consumer 如何瓜分 Partition。不同的分配策略影响负载均衡度和Rebalance 效率。Kafka 提供了四种内置策略,从经典的 Range/RoundRobin 到新一代的 Cooperative Sticky。
核心原理
四种策略对比
Range 策略(默认)详解
Range 策略按 Topic 独立分配,每个 Topic 的分区排序后均分给消费者。问题:如果一个消费组订阅了多个分区数不均匀的 Topic,数据倾斜会很严重。
Sticky 策略
Sticky 试图保持旧分配不变,仅移动必要的分区:
Rebalance 前: C1→[P0,P1,P3], C2→[P2]
C3 加入后(Range): C1→[P0,P1], C2→[P2,P3], C3→[] (大量迁移)
C3 加入后(Sticky): C1→[P0,P1,P3], C2→[P2], C3→[] (暂无迁移,等下次)
Cooperative Sticky(增量重平衡)
传统 Rebalance 是"Stop-The-World"的:全部撤销再重新分配。Cooperative Sticky 允许分批执行:
| 特性 | 传统(Eager) | Cooperative Sticky |
|---|---|---|
| 撤销范围 | 全部撤销 | 只撤销需迁移的分区 |
| 消费暂停 | 全组暂停 | 仅被撤销分区的 Consumer 短暂暂停 |
| 适用版本 | 所有 | Kafka 2.4+ |
| 配置 | partition.assignment.strategy=<Range> | partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor |
完整示例
示例一:Range 策略导致的数据倾斜
场景:消费组订阅 Topic A(3 分区)和 Topic B(1 分区),2 个 Consumer。
操作前环境:两个 Topic 已创建,Consumer 使用默认 Range 策略。
# 创建 Topic
bin/kafka-topics.sh --create --topic topic-a --partitions 3 --bootstrap-server localhost:9092
bin/kafka-topics.sh --create --topic topic-b --partitions 1 --bootstrap-server localhost:9092
分配结果(Range):
| Consumer | Topic A | Topic B | 总分区数 |
|---|---|---|---|
| Consumer 1 | P0, P1 | P0 | 3 |
| Consumer 2 | P2 | — | 1 |
问题:Consumer 1 负载是 Consumer 2 的 3 倍。
改用 RoundRobin:
props.put("partition.assignment.strategy",
"org.apache.kafka.clients.consumer.RoundRobinAssignor");
| Consumer | 总分区(RoundRobin) |
|---|---|
| Consumer 1 | topic-a-P0, topic-a-P2, topic-b-P0 → 3 分区 |
| Consumer 2 | topic-a-P1 → 1 分区 |
(注意:RoundRobin 在这个场景改善有限,因为总数 4 分区 / 2 消费者是均匀的,但 Range 的分配方式不均)
示例二:Cooperative Sticky 减少 Rebalance 影响
场景:秒杀活动中 Consumer 3 因 OOM 重启,需要最小化 Rebalance 对消费的影响。
Properties props = new Properties();
// ... 基础配置 ...
// 关键配置
props.put("partition.assignment.strategy",
"org.apache.kafka.clients.consumer.CooperativeStickyAssignor");
props.put("group.instance.id", "consumer-3"); // 静态成员,重启后不触发 Rebalance
props.put("session.timeout.ms", "30000"); // 合理设置心跳超时
操作前后对比:
| 事件 | Eager Rebalance 影响 | Cooperative Sticky 影响 |
|---|---|---|
| C3 OOM 重启 | 全组暂停消费 10-30s | 仅 C3 的分区暂停,C1/C2 继续消费 |
| C3 恢复 | 再次全组暂停 10-30s | C3 重新加入,接收回原分区 |
易错场景
易错 1:多 Topic 订阅时默认 Range 策略的倾斜陷阱
场景:一个消费组订阅了 10 个 Topic,其中 8 个是 1 分区的低流量 Topic,2 个是 100 分区的高流量 Topic。
后果:Range 策略在每个 Topic 内独立分配,低流量和高流量混合分配后,数据倾斜非常严重。部分 Consumer 闲置,部分 Consumer 过载。
最佳实践:订阅多 Topic 时使用 RoundRobin 或 Sticky。
易错 2:忘记 group.instance.id 导致静态成员失效
场景:设置了 group.instance.id 但没有设置足够大的 session.timeout.ms。
后果:Consumer 重启时间超过 session.timeout.ms 时,静态成员仍然会触发 Rebalance。
正确做法:
props.put("group.instance.id", "consumer-1");
props.put("session.timeout.ms", "60000"); // 给重启留足时间
面试高频考点
Q:什么时候用 Range?什么时候用 Cooperative Sticky?
A:
Range(默认)适合:
- 订阅单个 Topic 且分区数能被消费者数整除的场景
- 简单场景,不想引入额外复杂性
Cooperative Sticky(推荐)适合:
- 大规模消费组(10+ Consumer),Rebalance 代价大
- Consumer 频繁扩缩容的场景(K8s 弹性伸缩)
- 对消费延迟敏感的系统
RoundRobin 适合:
- 订阅多 Topic 且希望负载均匀
- 不介意 Rebalance 时全量重分配
Sticky(非 Cooperative) 是过渡方案,新项目直接用 Cooperative Sticky。