前言
分析rebalance过程知道,每个消费者本地都会保存一份与Topic下分区之间的分配关系,本文则着重分析分区的分配策略
分区分配策略(Partition Assignment Strategy):
Kafka默认有三个分区策略:RangeAssignor,StickyAssignor和RoundRobinAssignor,如果要自定义分配策略的话可以继承AbstractPartitionAssignor这个类,默认分区分配策略为RangeAssignor。
如果要指定分区分配策略的话可以通过server.properities配置文件中指定参数partition.assignment.strategy设置 ,默认值为class org.apache.kafka.clients.consumer.RangeAssignor。
区别
- Range分区策略针对的是消费者组下的某个topic
- RoundRobin分区策略针对的是消费者组下的所有topic
- Range和RoundRobin分区策略都会对订阅的所有topic进行分配,只是Range会对每个topic进行一次分区分配,而RoundRobin将所有topic集中起来统一分区分配
RangeAssignor
假设 n = 分区数 / 消费者数,m = 分区数 % 消费者数, 订阅topic的消费者组下的前 m 个消费者分配 n+1 个分区,剩余的消费者分配 n 个分区,源码分析如下:
public Map<String, List<TopicPartition>> assign(Map<String, Integer> partitionsPerTopic, Map<String, Subscription> subscriptions) {
// 每个topic下的消费者数量
Map<String, List<String>> consumersPerTopic = this.consumersPerTopic(subscriptions);
// 保存每个消费者的分区结果
Map<String, List<TopicPartition>> assignment = new HashMap();
Iterator var5 = subscriptions.keySet().iterator();
while(var5.hasNext()) {
String memberId = (String)var5.next();
// 初始化一个空的列表
assignment.put(memberId, new ArrayList());
}
var5 = consumersPerTopic.entrySet().iterator();
// 遍历每一个topic
while(true) {
String topic;
List consumersForTopic;
Integer numPartitionsForTopic;
do {
if (!var5.hasNext()) {
return assignment;
}
Entry<String, List<String>> topicEntry = (Entry)var5.next();
// topic名称
topic = (String)topicEntry.getKey();
// topic下的所有消费者
consumersForTopic = (List)topicEntry.getValue();
// topic分区数
numPartitionsForTopic = (Integer)partitionsPerTopic.get(topic);
} while(numPartitionsForTopic == null);
Collections.sort(consumersForTopic);
// topic分区数 / topic下的消费者数,每个消费者最少要分配的分区数
int numPartitionsPerConsumer = numPartitionsForTopic / consumersForTopic.size();
// topic分区数 % topic下的消费者数,剩余的分区数
int consumersWithExtraPartition = numPartitionsForTopic % consumersForTopic.size();
// topic的分区列表
List<TopicPartition> partitions = AbstractPartitionAssignor.partitions(topic, numPartitionsForTopic);
int i = 0;
// 遍历topic下的每一个消费者
for(int n = consumersForTopic.size(); i < n; ++i) {
// 开始索引
int start = numPartitionsPerConsumer * i + Math.min(i, consumersWithExtraPartition);
// 分配的分区数 + 0 / 1,前consumersWithExtraPartition个消费者多分配一个分区
int length = numPartitionsPerConsumer + (i + 1 > consumersWithExtraPartition ? 0 : 1);
((List)assignment.get(consumersForTopic.get(i))).addAll(partitions.subList(start, start + length));
}
}
}
RoundRobinAssignor
public Map<String, List<TopicPartition>> assign(Map<String, Integer> partitionsPerTopic, Map<String, Subscription> subscriptions) {
Map<String, List<TopicPartition>> assignment = new HashMap();
Iterator var4 = subscriptions.keySet().iterator();
while(var4.hasNext()) {
String memberId = (String)var4.next();
// 存放每个消费者分配的分区
assignment.put(memberId, new ArrayList());
}
// 所有消费者的回环迭代器
CircularIterator<String> assigner = new CircularIterator(Utils.sorted(subscriptions.keySet()));
// 所有topic下的分区列表
Iterator var9 = this.allPartitionsSorted(partitionsPerTopic, subscriptions).iterator();
// 遍历分区列表
while(var9.hasNext()) {
TopicPartition partition = (TopicPartition)var9.next();
String topic = partition.topic();
// 遍历消费者,直到找到消费者订阅的topic存在该分区
while(!((Subscription)subscriptions.get(assigner.peek())).topics().contains(topic)) {
assigner.next();
}
// 分配当前分区给找到的消费者
((List)assignment.get(assigner.next())).add(partition);
}
return assignment;
}
StickyAssignor
这个有时间再分析