深入理解Kafka(四):消费者分区分配策略源码剖析

前言

分析rebalance过程知道,每个消费者本地都会保存一份与Topic下分区之间的分配关系,本文则着重分析分区的分配策略

分区分配策略(Partition Assignment Strategy):

Kafka默认有三个分区策略:RangeAssignorStickyAssignorRoundRobinAssignor,如果要自定义分配策略的话可以继承AbstractPartitionAssignor这个类,默认分区分配策略为RangeAssignor
如果要指定分区分配策略的话可以通过server.properities配置文件中指定参数partition.assignment.strategy设置 ,默认值为class org.apache.kafka.clients.consumer.RangeAssignor

区别

  1. Range分区策略针对的是消费者组下的某个topic
  2. RoundRobin分区策略针对的是消费者组下的所有topic
  3. RangeRoundRobin分区策略都会对订阅的所有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

这个有时间再分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值