详解kafka中的消息分区分配算法

作者:温故知新之java 时间:2021-06-02 08:16:15 

背景

kafka有分区机制,一个主题topic在创建的时候,会设置分区。如果只有一个分区,那所有的消费者都订阅的是这一个分区消息;如果有多个分区的话,那消费者之间又是如何分配的呢?

分配算法

RangeAssignor

定义

Kafka默认采⽤RangeAssignor的分配算法。

RangeAssignor策略的原理是按照消费者总数和分区总数进⾏整除运算来获得⼀个跨度,然 后将分区按照跨度进⾏平均分配,以保证分区尽可能均匀地分配给所有的消费者。对于每⼀个 Topic,RangeAssignor策略会将消费组内所有订阅这个Topic的消费者按照名称的字典序排序,然 后为每个消费者划分固定的分区范围,如果不够平均分配,那么字典序靠前的消费者会被多分配 ⼀个分区。

这种分配⽅式明显的⼀个问题是随着消费者订阅的Topic的数量的增加,不均衡的问题会越来 越严重,⽐如上图中4个分区3个消费者的场景,C0会多分配⼀个分区。如果此时再订阅⼀个分区 数为4的Topic,那么C0⼜会⽐C1、C2多分配⼀个分区,这样C0总共就⽐C1、C2多分配两个分区 了,⽽且随着Topic的增加,这个情况会越来越严重。

源码分析

public class RangeAssignor extends AbstractPartitionAssignor {
   ....
   @Override
   public Map> assign(Map partitionsPerTopic, Map subscriptions) {
       // 1. 获取每个topic被多少个consumer订阅了
       Map<String,List<String>> consumersPerTopic = consumersPerTopic(subscriptions);
       // 2. 存储最终的分配⽅案
       Map<String,List<String>> assignment = new HashMap<>();
       for (String memberId : subscriptions.keySet())
           assignment.put(memberId, new ArrayList());
       for (Map.Entry> topicEntry : consumersPerTopic.entrySet()) {
           String topic = topicEntry.getKey();
           List consumersForTopic = topicEntry.getValue();
           // 3. 每个topic的partition数量
           Integer numPartitionsForTopic = partitionsPerTopic.get(topic);
           if (numPartitionsForTopic == null)
           continue;
           Collections.sort(consumersForTopic);
           // 4. 表示平均每个consumer会分配到多少个partition
           int numPartitionsPerConsumer = numPartitionsForTopic / consumersForTopic.size();
           // 5. 平均分配后还剩下多少个partition未被分配
           int consumersWithExtraPartition = numPartitionsForTopic % consumersForTopic.size();
           List partitions = AbstractPartitionAssignor.partitions(topic, numPartitionsForTopic);
           // 6. 这⾥是关键点,分配原则是将未能被平均分配的partition分配到前 consumersWithExtraPartition个consumer
           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;
   }
}

场景

可以完全平均分配

详解kafka中的消息分区分配算法

无法完全平均分配,排序靠前分的更多

详解kafka中的消息分区分配算法

消费者数量大于分区数量,排名靠前先分得,排名靠后未分得分区

详解kafka中的消息分区分配算法

RoundRobinAssignor

定义

RoundRobinAssignor的分配策略是将消费组内订阅的所有Topic的分区及所有消费者进⾏排序后尽 量均衡的分配(RangeAssignor是针对单个Topic的分区进⾏排序分配的)。如果消费组内,消费者订阅 的Topic列表是相同的(每个消费者都订阅了相同的Topic),那么分配结果是尽量均衡的(消费者之间 分配到的分区数的差值不会超过1)。

源码分析

package org.apache.kafka.clients.consumer;
public class RoundRobinAssignor extends AbstractPartitionAssignor {
@Override
public Map> assign(Map partitionsPerTopic, Map subscriptions) {
       <Map> assignment = new HashMap<>();
       for (String memberId : subscriptions.keySet()) assignment.put(memberId, new ArrayList()); // 1. 环状链表,存储所有的consumer,⼀次迭代完之后⼜会回到原点
       CircularIterator assigner = new CircularIterator<> (Utils.sorted(subscriptions.keySet())); // 2. 获取所有订阅的topic的partition总数 for (TopicPartition partition : allPartitionsSorted(partitionsPerTopic, subscriptions)) {
       final String topic = partition.topic();
       while (!subscriptions.get(assigner.peek()).topics().contains(topic))
           assigner.next();
           assignment.get(assigner.next()).add(partition);
       }
       return assignment;
   }
.... }

场景

无法完全平均分配,排序靠前分的更多

详解kafka中的消息分区分配算法

StickyAssignor

定义

尽管RoundRobinAssignor已经在RangeAssignor上做了⼀些优化来更均衡的分配分区,但是在⼀些情况下依旧会产⽣严重的分配偏差,从字⾯意义上看,Sticky是&ldquo;粘性的&rdquo;,可以理解为分配结果是带&ldquo;粘性的&rdquo;&mdash;&mdash;每⼀次分配变更相对 上⼀次分配做最少的变动(上⼀次的结果是有粘性的) 其⽬标有两点:

  • 分区的分配尽量的均衡

  • 每⼀次重分配的结果尽量与上⼀次分配结果保持⼀致

场景

详解kafka中的消息分区分配算法

来源:https://juejin.cn/post/7086656375685660708

标签:kafka,消息,分区,分配
0
投稿

猜你喜欢

  • SpringBoot读取自定义配置文件方式(properties,yaml)

    2021-06-30 23:46:07
  • Spring Boot详解创建和运行基础流程

    2022-01-28 19:57:05
  • kafka生产者发送消息流程深入分析讲解

    2022-10-12 08:39:30
  • Android 自定义gradle property详解及实例代码

    2022-05-08 15:17:49
  • Android仿百度地图小度语音助手的贝塞尔曲线动画

    2022-03-21 00:09:26
  • JAVA实现301永久重定向方法

    2023-12-18 10:59:08
  • springboot整合mybatis plus与druid详情

    2022-07-31 14:22:14
  • Java获取文件的类型和扩展名的实现方法

    2021-09-24 08:56:56
  • 基于Java实现多线程下载并允许断点续传

    2021-07-19 03:05:52
  • c#使用DotNetZip封装类操作zip文件(创建/读取/更新)实例

    2023-04-08 02:00:19
  • android studio3.0以上如何通过navicat访问SQLite数据库文件

    2022-03-26 14:28:55
  • 浅谈用java实现事件驱动机制

    2022-07-12 18:06:03
  • 基于SpringBoot+Redis实现分布式锁

    2023-10-16 11:44:31
  • C# 语言入门基础介绍

    2022-03-02 13:32:33
  • java使用common-fileupload实现文件上传

    2022-03-06 03:21:08
  • java 对象输入输出流读写文件的操作实例

    2021-06-13 15:24:30
  • Java Base64 加密与解密示例代码

    2022-11-13 15:11:56
  • java实现sftp客户端上传文件以及文件夹的功能代码

    2023-02-14 22:07:28
  • java面试题——详解HashMap和Hashtable 的区别

    2023-08-06 16:38:25
  • SpringBoot四种读取properties文件的方式(小结)

    2023-10-10 03:25:14
  • asp之家 软件编程 m.aspxhome.com