Leader消费者在收到JoinGroupResponse后,会按照其中指定的分区分配策略进行分区分配,每个分区分配策略就是一个PartitionAssignor接口的实现。图是PartitionAssignor的继承结构及其中的组件。
PartitionAssignor接口中定义了Assignment和Subscription两个内部类。
进行分区分配需要的两方面的数据:Metadata中记录的集群元数据和每个Member的订阅信息。
为了用户增强对分配结果的控制,就将用户订阅信息和一些影响分配的用户自定义信息封装成Subscription,例如,"用户自定义数据"可以是每个消费者的权重。
其中,topics集合表示某Member订阅的Topic集合,userData表示用户自定义的数据。
PartitionAssignor接口提供了subscription方法,用于添加用户自定义数据,在创建JoinGroupRequest的时候会用到subscription()方法。
Assignment中保存了分区的分配结果,partitions表示的是分配给某消费者的TopicPartition集合,userData是用户自定义的数据。
再来看看PartitionAssignor的其他方法,assign是子类要实现的、完成Parition分配的抽象方法。
onAssignment()方法是在每个消费者收到Leader分配结果时的回调函数,此调用发生在解析SyncGroupResponse之后。
AbstractPartitionAssignor为了简化PartitionAssignor接口的实现,对assign()方法进行了实现,其中会将Subscription中的userData去除掉后,再进行分区分配。具体代码如下:
RangeAssignor和RoundRobinAssignor都是Kafka提供的PartitionAssignor接口的默认实现。
- RangeAssignor实现原理是:针对每个Topic,n=分区数/消费者数量,m=分区数%消费者数量,前m个消费者每个分配n+1个分区,后面的(消费者数量-m)个消费者每个分配n个Partition。
- RoundRobinAssignor原理是:将所有Topic的Partition按照字典序排列,然后对每个Consumer进行轮询分配。
举个例子,有C0、C1两个消费者和t0、t1两个Topic,每个Topic有三个分区编号都是0~2。使用RangeAssignor的分配结果是:C0:[t0p0,t0p1,t1p0,t1p1],C1:[t0p2,t1p2];使用RoundRobinAssignor的分配结果是:C0:[t0p0,t0p2,t1p1]、C1:[t0p1,t1p0,t1p2]。