通过partition.assignment.strategy参数进行配置分区策略, 默认使用的策略是org.apache.kafka.clients.consumer.RangeAssignor, (default)
还存在
org.apache.kafka.clients.consumer.RoundRobinAssignor和org.apache.kafka.clients.consumer.StickyAssignor (0.11版本之后)这两种
RangeAssignor 分区策略方法:
@Override
public Map<String, List<TopicPartition>> assign(Map<String, Integer> partitionsPerTopic,
Map<String, Subscription> subscriptions) {
// 1 获取每个topic 被多少个consumer订阅了, key 是topic,value 是List
Map<String, List<String>> consumersPerTopic = consumersPerTopic(subscriptions);
// 2 存储分配策略
Map<String, List<TopicPartition>> assignment = new HashMap<>();
for (String memberId : subscriptions.keySet())
assignment.put(memberId, new ArrayList<>());
for (Map.Entry<String, List<String>> topicEntry : consumersPerTopic.entrySet()) {
String topic = topicEntry.getKey(); // 获取topic名
List<String> consumersForTopic = topicEntry.getValue();// 获取consumer 对应订阅的topic关系
//3 每个topic的partition数量
Integer numPartitionsForTopic = partitionsPerTopic.get(topic);
if (numPartitionsForTopic == null)
continue;
Collections.sort(consumersForTopic);
//4 表示平均每个consumer会分配到多少个partition, 比如一个topic 10个分区 3个C 10/3 = 3
int numPartitionsPerConsumer = numPartitionsForTopic / consumersForTopic.size();
//5 平均分配后还剩下多少个partition未被分配, 10%3 = 1
int consumersWithExtraPartition = numPartitionsForTopic % consumersForTopic.size();
List<TopicPartition> partitions = AbstractPartitionAssignor.partitions(topic, numPartitionsForTopic);
//6 将未能被平均分配的partition分配到前consumersWithExtraPartition个consumer, 第一个C将被多分配一个Partition
for (int i = 0, n = consumersForTopic.size(); i < n; i++) {
int start = numPartitionsPerConsumer * i + Math.min(i, consumersWithExtraPartition);
int length = numPartitionsPerConsumer + (i + 1 > consumersWithExtraPartition ? 0 : 1);
assignment.get(consumersForTopic.get(i)).addAll(partitions.subList(start, start + length));
}
}
return assignment;
}
RangeAssignor策略的原理:
举例:
2个Topic;每个Topic有4个分区;2个消费者C0,C1;所订阅的分区标识为 t0p0、t0p1、t0p2、t0p3、t1p0、t1p1、t1p2、t1p3
分配结果:
C0: t0p0,t0p2,t1p0,t1p2
C1: t0p1,t0p3,t1p1,t1p3
看似分配的很和谐很均匀。
假如上面的每个Topic改为3个Partition,订阅的分区标识为:t0p0、t0p1、t0p2、t1p0、t1p1、t1p2
分配结果:
C0: t0p0,t0p1,t1p0,t1p1
C1: t0p2,t1p2
明显的看到这样的分配并不均匀,如果将类似的情形扩大,有可能会出现部分消费者过载的情况,而部分消费者又会闲置。
RoundRobinAssignor策略的原理:
将消费组内的所有消费者以及消费者所订阅的所有topic的partition按照字典顺序排序,然后通过轮询的方式逐个将分区以此分配给每个消费者,说白了也就是先每一个consumer都分配一轮,一轮分配完成之后接着下一轮继续分配,知道分配完为止。
举例:
假如一个topic 有5个partition,然后一个group有3个consumer,那么roundbin的分配方式如下:
分配方式:
C0: p0,p3
C1:p1,p4
C2: p2
貌似也是非常的和谐的分配方式
对于多topic情况分析:
3个topic:t0,t1,t2。 t0有2个partition,t1有3个partition,t2有4个partition
Consumer中有3个Consumer,C0,C1,C2; C0订阅了 t0, C1订阅了t0和t1, C2订阅了t0,t1,t2
分配方案:
C0:t0p0,
C1:t0p1,t1p0,t1p2
C2:t1p1,t2p0,t2p1,t2p2,t2p3
很明显 consumer2 要是把 t1p1 也分配给 C1 就会显得更加均匀一些了。
粘性分配策略(kafka0.11版本之后)
粘性分配器有两个作用:
举例分析:
3个Consumer:C0,C1,C2,均订阅了4个Topic:t0,t1,t2,t3, 每个Topic 有2个Partition
分区个数:t0p0、t0p1、t1p0、t1p1、t2p0、t2p1、t3p0、t3p1 这8个分区
分配结果:
C0:t0p0、t1p1、t3p0
C1:t0p1、t2p0、t3p1
C2:t1p0、t2p1
似乎跟前面的分配没有很大区别,分配的很和谐很完美。但是假如有一个Consumer 脱离了消费组,那么消费组就会执行在平衡操作,消费分区重新分配。如果采用RoundRobinAssignor策略,分配结果如下:
C0: t0p0,t1p0,t2p0,t3p0
C2:t0p1,t1p1,t2p1,t3p1
RoundRobinAssignor策略 会按照消费者C0,C1重新轮询分配,如果采用StickyAssignor策略
StickyAssignor策略分配结果:
C0:t0p0,t1p1,t3p0,t2p0
C2:t1p0,t2p1,t0p1,t3p1
可以看出StickyAssignor策略分配结果 保留了第一次的分配结果然后将脱离消费组的C1 在分配给剩余的两个消费者,最终都保持了平衡。
在来分析第二种方式 RoundRobinAssignor 中的例子
3个topic:t0,t1,t2。 t0有1个partition,t1有2个partition,t2有3个partition
t0p0、t1p0、t1p1、t2p0、t2p1、t2p2
Consumer中有3个Consumer,C0,C1,C2; C0订阅了 t0, C1订阅了t0和t1, C2订阅了t0,t1,t2
C0:t0p0
C1:t1p0
C2:t1p1,t2p0,t2p1,t2p2
这样很明显分配是不均匀的,
采用 StickyAssignor 分配策略之后
C0:t0p0
C1:t1p0,t1p1
C2:t2p0,t2p1,t2p2
这样很明显比上面的分配结果要均匀了。此时如果C0脱离了消费组在来对比两种消费结果。
RoundRobinAssignor方式:
C1: t0p0,t1p1
C2: t1p0,t2p0,t2p1,t2p2
StickyAssignor策略,那么分配结果
C1:t1p0,t1p1,t0p0
C2: t2p0,t2p1,t2p2
结果上看StickyAssignor策略比另外两者分配策略而言显得更加的优异,这个策略的代码实现复杂,所以生产上推荐使用 StickyAssignor策略