Kafka Consumer分区策略

文章目录

      • RangeAssignor 分区策略
      • RoundRobinAssignor 分区策略
      • StickyAssignor 分区策略

RangeAssignor
RoundRobinAssignor
StickyAssignor

通过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 分区策略

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策略的原理:

  • 按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进行平均分配,(一个Topic中partition总数 / 订阅这个Topic的Consumer数),
  • 对于每个Topic以数字顺序排列可用分区,并以字典顺序排列每个Consumer。然后,我们将分区数除以消费者总数,以确定分配给每个消费者的分区数。如果它没有平均划分,那么字典序靠前的消费者会被多分配一个分区

举例:
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 分区策略

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 就会显得更加均匀一些了。

StickyAssignor 分区策略

粘性分配策略(kafka0.11版本之后)
粘性分配器有两个作用:

  • 它保证分配尽可能平衡
    分配给Consumer的topic partitions数量最多相差1个;或 每个拥有比其他Consumer少2倍以上的topic partitions的Consumer无法将任何这些topic partitions转移给它
  • 当发生重新分配时,它会保留尽可能多的现有分配。当topic partitions从一个使用者移动到另一个Consumer时,这有助于节省一些开销处理

举例分析:
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策略

你可能感兴趣的:(Kafka)