3、分区分配策略:RangeAssignor、RoundRobinAssignor、StickyAssignor、CooperativeStickyAssignor 原理与对比
在 Kafka 消费者组中,分区分配策略决定了当消费者加入或离开组时,Topic 的各个分区如何被分配给组内的消费者。选择错误的策略可能导致分区倾斜、消费延迟飙升,甚至引发频繁的 Rebalance。本章深入剖析四种内置分配策略的核心原理、适用场景与性能差异。
3.1 策略全景概览
Kafka 2.4 之前主要提供三种策略:RangeAssignor、RoundRobinAssignor、StickyAssignor。2.4 之后引入了 CooperativeStickyAssignor,用于支持增量式协作 Rebalance。下表从核心机制、分配目标、Rebalance 影响三个维度进行对比:
| 策略名称 | 核心分配机制 | 分配目标 | Rebalance 影响 | 引入版本 |
|---|---|---|---|---|
| RangeAssignor | 按 Topic 顺序,将连续分区范围分配给消费者 | 每个 Topic 内分区连续 | 全量 Stop-The-World | 0.9.0 |
| RoundRobinAssignor | 将所有分区排序后轮询分配给消费者 | 分区总数均衡 | 全量 Stop-The-World | 0.9.0 |
| StickyAssignor | 在保持分区粘性的前提下尽量均衡 | 均衡 + 最小化分区移动 | 全量 Stop-The-World | 2.0.0 |
| CooperativeStickyAssignor | 增量式粘性分配,分阶段移动分区 | 均衡 + 最小化分区移动 + 增量 Rebalance | 增量协作(不停顿) | 2.4.0 |
3.2 RangeAssignor:按 Topic 范围分配
原理:对每个 Topic 独立进行分配。将 Topic 的分区按序号排序,然后除以消费者数量得到每个消费者应得的分区数,最后将连续的一段分区分配给同一个消费者。
分配示例:假设有 Topic-A(4 个分区:0,1,2,3)和 Topic-B(3 个分区:0,1,2),消费者组有 2 个消费者 C1、C2。
- Topic-A:4 / 2 = 2,C1 得到 [0,1],C2 得到 [2,3]
- Topic-B:3 / 2 = 1 余 1,C1 得到 [0],C2 得到 [1,2]
结果:C1 共 3 个分区,C2 共 4 个分区,出现明显倾斜。
3.3 RoundRobinAssignor:轮询分配
原理:将所有订阅 Topic 的所有分区按照 Topic 名称和分区序号排序,形成一个环形列表。然后按消费者顺序依次轮询分配,直到全部分区分配完毕。
分配示例:同上例(Topic-A 4 分区,Topic-B 3 分区,C1、C2)。
- 排序后分区列表:[A-0, A-1, A-2, A-3, B-0, B-1, B-2]
- 轮询分配:C1 得到 A-0, A-2, B-1;C2 得到 A-1, A-3, B-0, B-2
结果:C1 共 3 个分区,C2 共 4 个分区,仍然存在倾斜。这是因为轮询无法保证每个消费者获得完全相等的分区数,当分区总数不能被消费者数整除时,前几个消费者会多分一个。
3.4 StickyAssignor:粘性分配
原理:在保证分区分配尽可能均衡的前提下,最小化分区移动。当发生 Rebalance 时,StickyAssignor 会尽量保留消费者当前已经分配到的分区,只移动那些为了达到均衡而必须移动的分区。
核心目标(按优先级):
- 均衡性:每个消费者分配到的分区数差值不超过 1。
- 粘性:在满足均衡性的前提下,尽可能保留消费者已有的分区分配。
分配示例:初始分配 C1 持有 [A-0, A-1, B-0],C2 持有 [A-2, A-3, B-1, B-2]。当 C2 离开后,StickyAssignor 会优先将 C2 的分区分配给 C1,同时尽量保持 C1 原有的分区不动。最终 C1 得到 [A-0, A-1, A-2, A-3, B-0, B-1, B-2],所有分区保持连续且粘性。
3.5 CooperativeStickyAssignor:协作式粘性分配
原理:在 StickyAssignor 的基础上,将 Rebalance 过程从全量停止改为增量协作。消费者组协调器分多轮进行分区移动,每轮只移动部分分区,消费者在移动期间可以继续消费未被移动的分区。
工作流程:
- 第一轮:协调器计算需要移动的分区列表,通知相关消费者撤销这些分区。
- 消费者撤销:消费者停止消费被撤销的分区,提交偏移量,然后发送“已撤销”响应。
- 第二轮:协调器将已撤销的分区分配给目标消费者,目标消费者开始消费。
- 迭代:如果仍有不均衡,重复上述步骤,直到所有分区分配均衡。
3.6 四种策略对比 SVG 图
下图从均衡性、粘性、Rebalance 影响三个维度对四种策略进行可视化对比:
3.7 策略选择决策树
在实际生产环境中,选择哪种策略取决于消费者组的稳定性和延迟敏感度。以下决策树可以帮助快速选择:
3.8 配置示例与验证
在消费者客户端配置中,通过 partition.assignment.strategy 参数指定策略。以下为 Java 客户端配置 CooperativeStickyAssignor 的示例:
Properties props = new Properties();
props.put("bootstrap.servers", "broker1:9092,broker2:9092");
props.put("group.id", "read-group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
// 设置协作式粘性分配策略
props.put("partition.assignment.strategy",
"org.apache.kafka.clients.consumer.CooperativeStickyAssignor");
// 可选:设置最大轮次,防止无限协作
props.put("max.cooperative.rebalance.retries", "5");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("topic-read"));
kafka-consumer-groups --bootstrap-server localhost:9092 --group read-group --describe 查看分区分配情况。观察每个消费者的分区数是否均衡,以及 Rebalance 日志中是否出现 CooperativeStickyAssignor 字样。
3.9 性能影响总结
| 策略 | 均衡性 | Rebalance 延迟 | 分区移动量 | 推荐场景 |
|---|---|---|---|---|
| RangeAssignor | 差 | 高(全量STW) | 高 | 不推荐生产使用 |
| RoundRobinAssignor | 中 | 高(全量STW) | 高 | 单Topic、消费者固定 |
| StickyAssignor | 优 | 中(全量STW但移动少) | 低 | 消费者组稳定 |
| CooperativeStickyAssignor | 优 | 低(增量协作) | 极低 | 读写分离、弹性伸缩、低延迟要求 |