消费者票选分区分配策略实现

消费者的分区分配策略是由 PartitionAssignor 接口定义,主要有以下三个实现类:

RangeAssignor (默认):range

RoundRobinAssignor:roundrobin 

StickyAssignor:sticky 

不清楚的同学可以参考:三种分区分配策略

若上述三种分配方式不满足需求,我们也可以自定义实现PartitionAssignor接口。

若自定义分配时只与partitions相关,与用户数据userData无关(参考Assignment类),也可直接继承AbstractPartitionAssignor实现

 

怎么配置PartitionAssignor ?

Properties props = new Properties();
assignors.add(RangeAssignor.class.getCanonicalName());
assignors.add(RoundRobinAssignor.class.getCanonicalName());
assignors.add(StickyAssignor.class.getCanonicalName());  
props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, assignors);

在消费者客户端创建KafkaConsumer的属性配置的时候指定,并且可以指定多个PartitionAssignor 

 

分区分配策略由谁确定?多个PartitionAssignor 到底哪个生效?

分区分配策略由服务端确定

 

一个消费组可以有多个消费者,每个消费者都可以指定一个或者多个PartitionAssignor(分区分配策略),不指定默认是RangeAssignor 

可以简单回顾一下,消费者加入消费组流程可以大致分为三步:

(1)查找GroupCoordinator阶段:找一个负载最低的broker发起GroupCoordinatorRequest请求,然后阻塞直到收到GroupCoordinatorResponse响应,来确定这个消费者应该连接哪个broker的GroupCoordinator

(2)join Group阶段:消费者向GroupCoordinator发送加入组的JoinGroupRequest请求,服务端收集消费者信息产生JoinGroupResponse响应。这个过程会确定一个leader消费者,选择一个PartitionAssignor 

(3)sync Group阶段:消费者进行join响应回调,leader消费者会在本地进行分区(分区策略在join阶段由服务端指定),然后发起sync请求到服务端将分区分配信息带过去。其他follow消费者也会发起sync请求,sync响应返回分区分配信息。

 

服务端选择PartitionAssignor 流程发生在上述join阶段关键调用链如下:

- GroupCoordinator.onCompleteJoin

-- GroupMetadata.initNextGeneration

---- GroupMetadata.selectProtocol

def selectProtocol: String = {
    if (members.isEmpty)
      throw new IllegalStateException("Cannot select protocol for empty group")

    // select the protocol for this group which is supported by all members
    val candidates = candidateProtocols

    // let each member vote for one of the protocols and choose the one with the most votes
    val votes: List[(String, Int)] = allMemberMetadata
      .map(_.vote(candidates))
      .groupBy(identity)
      .mapValues(_.size)
      .toList

    votes.maxBy(_._2)._1
  }

selectProtocol方法主要有两步:

第一步是通过candidateProtocols方法找到分区分配策略候选项, 前面说过消费组中的每个消费者都可以设置自己的分区分配策略,这里的候选项是所有消费者共同支持的分区分配策略。

第二步是每个消费者从候选项中投票,找到票选最大值,选为当前消费者组的分区分配策略。

 

找候选集:GroupMetadata.candidateProtocols

  private def candidateProtocols = {
    // get the set of protocols that are commonly supported by all members
    allMemberMetadata
      .map(_.protocols)
      .reduceLeft((commonProtocols, protocols) => commonProtocols & protocols)
  }

所有消费者成员依次左规约找到共同的分区分配策略得到候选集candidates 

所有消费者成员从候选集票选:MemberMetadata.vote

def vote(candidates: Set[String]): String = {
    supportedProtocols.find({ case (protocol, _) => candidates.contains(protocol)}) match {
      case Some((protocol, _)) => protocol
      case None =>
        throw new IllegalArgumentException("Member does not support any of the candidate protocols")
    }
  }

MemberMetadata管理连接过来的消费者元数据,包括心跳数据和支持的protocols(对应前面所说的分区分配策略)

find会依次迭代消费者支持的protocols集合里的数据,找到的第一个在候选集中的protocol就返回,相当于投票成功

 

再回到selectProtocol的votes得到的是一个List[(String, Int)],String表示protocol,Int表示票数。

votes.maxBy(_._2)._1 找到票数最大的protocol返回。也就是前面说的分区分配策略。

 

举个例子

有一个消费者组 group-test,有三个消费者c1、c2、c3,支持的分区分配策略列表分别如下

c1:{ range, roundrobin, custom }

c2:{ range, roundrobin, sticky }

c3:{ roundrobin, range, sticky }

 

第一步选择候选集:候选集是c1、c2、c3的交集 { range,roundrobin }

第二步投票:c1投range、c2投range、c3投roundrobin,选票结果为{(range, 2), (roundrobin, 1)}

最后消费组group-test选择的分区分配策略为range

 

文中源代码来自kafka 1.0.0版本

 

posted @ 2022-07-11 18:47  OUYM  阅读(85)  评论(0编辑  收藏  举报