kafka消费者客户端分区分配,协调器,自定义分区器分配策略

在kafka的消费者客户端中,是以组来区分消费者的,不同消费者组之间没有关联,对于某个主题来说包含N个分区,一个消费者组内的M个消费者会按照一定的分区分配策略来消费这个N个分区,消费者组内的每个消费者会消费不同的分区,不会有一个分区被同一个消费者组内的多个消费者消费。
消费者客户端参数partition.assignment.strategy来设定消费者组内消费者消费主题分区的分配策略,可以配置多个分配策略,用逗号隔开,常见的消费者客户端分区分配策略有:
RangeAssignor:按照消费者总数和分区总数进行整除运算来获得一个range区间,然后将分区按照range区间进行平均分配。RangeAssignor会将消费者组内订阅这个主题的消费者按照字典顺序排序,然后为每个消费者划分固定的range区间,如果分配不够平均,字典顺序靠前的会被多分配一个分区。
例:主题 topic001 有4个分区 p0,p1,p2,p3
消费者组 test-001有2个消费者 c0,c1,分配后,消费情况如下:
c0: p0,p1
c2:p2,p3
如果只有三个分区,分配后消费情况如下:
c0: p0,p1
c1:p2

RoundRobinAssignor:将消费者组内的消费者按照名称字典顺序排序,然后通过轮询的方式将分区依次分配给消费者
StickyAssignor:主要为了保证两点:1 分配尽可能比较均衡 2 尽可能与上次分配保持相同

自定义分区分配:自定义分区分配策略需要实现org.apache.kafka.clients.consumer.internals.PartitionAssignor有两个内部类,Subscription,Assignment

Subscription表示消费者的订阅信息,两个属性 topics和userData,表示消费者订阅的主体和用户自定义信息 , PartitionAssignor 的 public Subscription subscription(Set<String> topics)返回一个Subscription,一般Subscription的topics与参数topics对应,这里我们可以自己定义一些信息,这样在PartitionAssignor.assign进行分区分配的时候,通过自定义的信息能够实现一些分配的逻辑

Assignment表示分配结果的信息,两个属性partitions和userData,表示所分配到的分区集合和用户自定义数据

public interface PartitionAssignor {
	//
    Subscription subscription(Set<String> topics);
	//实现分区分配逻辑
    Map<String, Assignment> assign(Cluster metadata, Map<String, Subscription> subscriptions);
//消费者组leader根据分区器分配完成后回调该方法
    void onAssignment(Assignment assignment);
	//提供分配策略的名称
    String name();

    class Subscription {
        private final List<String> topics;
        private final ByteBuffer userData;

        public Subscription(List<String> topics, ByteBuffer userData) {
            this.topics = topics;
            this.userData = userData;
        }

        public Subscription(List<String> topics) {
            this(topics, ByteBuffer.wrap(new byte[0]));
        }
        public List<String> topics() {
            return topics;
        }
        public ByteBuffer userData() {
            return userData;
        }
        @Override
        public String toString() {
            return "Subscription(" +
                    "topics=" + topics +
                    ')';
        }
    }
    class Assignment {
        private final List<TopicPartition> partitions;
        private final ByteBuffer userData;

        public Assignment(List<TopicPartition> partitions, ByteBuffer userData) {
            this.partitions = partitions;
            this.userData = userData;
        }
        public Assignment(List<TopicPartition> partitions) {
            this(partitions, ByteBuffer.wrap(new byte[0]));
        }
        public List<TopicPartition> partitions() {
            return partitions;
        }
        public ByteBuffer userData() {
            return userData;
        }
        @Override
        public String toString() {
            return "Assignment(" +
                    "partitions=" + partitions +
                    ')';
        }
    }
}

Kafka中还提供了一个抽象类,org.apache.kafka.clients.consumer.internals.AbstractPartitionAssignor,简化了PartitionAssignor的实现,
实现一个随机分配的自定义分区器:


public class MyConsumerPartitionAssignor2 extends AbstractPartitionAssignor {
    @Override
    public Map<String, List<TopicPartition>> assign(Map<String, Integer> partitionsPerTopic, Map<String, Subscription> subscriptions) {
        Map<String, Set<String>> topicConsumers = this.getConsumersPerTopic(subscriptions);
        Map<String, List<TopicPartition>> assignment = new HashMap<>();

        topicConsumers.forEach(
                (topic,consumers)->{
                    int consumerSize = consumers.size();
                    Integer topicPartitionNums = partitionsPerTopic.get(topic);
                    List<String> consumerList = new ArrayList<>();
                    consumerList.addAll(consumers);

                    if(topicPartitionNums != null ){
                        List<TopicPartition> partitions = partitions(topic,topicPartitionNums);
                        for(TopicPartition partition : partitions){
                            int rand = new Random(consumerSize).nextInt();
                            String consumer = consumerList.get(rand);
                            if(!assignment.containsKey(consumer)){
                                assignment.put(consumer,new ArrayList<>());
                            }
                            assignment.get(consumer).add(partition);
                        }
                    }
                }
        );
        return assignment;
    }

    @Override
    public String name() {
        return null;
    }
    // 将consumer -> topics ,转换为 topic -> consumers,获取每个topic对应消费者列表
    private Map<String,Set<String>> getConsumersPerTopic(Map<String,Subscription> metaData){
        Map<String, Set<String>> topicConsumers = new HashMap<>();
        metaData.forEach(
                (consumer,subscription) ->{
                    List<String> topics = subscription.topics();
                    topics.forEach(
                            (topic) ->{
                                if(!topicConsumers.containsKey(topic)){
                                    topicConsumers.put(topic,new HashSet<>());
                                }
                                topicConsumers.get(topic).add(consumer);
                            });
                });

        return topicConsumers;
    }
}

还需要在消费者客户端配置中,增加如下配置:

properties.setProperty(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,MyConsumerPartitionAssignor.class.getName());

为了能够让组内消费者协调处理,kafka中设置了消费者协调器(ConsumerCoordinator)和组协调器(GroupCoordinator),用来处理消费者和消费者之间的逻辑处理。

  1. 当有新的消费者加入消费者组的时候,如果消费者没有消费者组对应的GroupCoordinator的broker节点信息,那么新加入的消费者需要查找到到对应消费者组的组协调器,会向集群中负载最低的broker节点发送FindCoordinatorRequest请求,将对应组协调器的相关信息包含node_id,host,port等信息返回给消费者
  2. 获取到组协调器的信息后,消费者客户端就会请求加入消费者组,会向组协调器发送JoinGroupRequest请求,消费者客户端会阻塞等待,直到组协调器返回相应
  3. 加入消费者组后,会同步消费者组相关信息。消费者leader会根据已经选举出来的分区分配策略进行具体的分区分配,并将分配的方案同步给各个消费者,这里leader并不是直接和组内其他消费者直接同步,而是通过组协调器,各消费者会向组协调器发送SyncGroupRequest请求同步分配方案。
  4. 分区重新分配完成之后,消费者组中的消费者就可以正常服务了。在开始服务之前还需要知道之前消息消费的位置。消费者将消费位移提交给组协调器,组协调器会将其保存在kafka内部主题__consumer_offsets中,此时消费者可以通过发送OffsetFetchRequest请求获取上次提交的消息位移,并从此处开始消费。消费者通过向组协调器发送心跳信息,只要消费者以正常的间隔来发送心跳,就被认为是活跃的

消费者组内的leader选举:每个消费者组内部也存在一个leader角色,用来进行消费者组内一些信息的同步和管理。消费者组内leader选举很简单,如果之前没有,就把第一个加入组内的消费者选举为leader,如果leaer消费者退出组,则随机选举一个消费者作为leader。

选举分区分配策略:每个消费者都可以设置分区分配策略,消费者组会从收集所有消费者的分区分配策略然后从中选择一个,这个并不是由leader决定,一般大致过程如下:

  1. 收集各个消费者支持的分区分配策略,组成一个分区分配策略的候选集
  2. 每个消费者从候选集中找出第一个自身支持的策略,为这个策略头上一票
  3. 计算候选集中每个策略的选票数,选票数最多的策略为当前消费者组的分区分配策略
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值