在leader consumer执行partition分配的时候,存在一种特殊的机制,即consumer和partition所在节点机器所在的机架(rack)号相同的时候,会优先分配此partition给这个consumer。下面描述分区分配策略时,也会分启不启用Rack Aware两种情况分别进行分析。
RangeAssignor
partition和consumer排序
partition排序规则
partition按照编号从小到大排序即可。
consumer排序
consumer的排序稍微复杂一些,需要参考group.instance.id
和member.id
等。
- 如果双方的
group.instance.id
都存在,则直接按字典序进行比较 - 如果只有一方的
group.instance.id
存在,那么有group.instance.id
的排在前面 - 如果双方都不存在
group.instance.id
,那么按照各自的member.id
基于字典序进行比较
consumer的member.id
格式:${client.id}-UUID
,默认的client.id
的格式为:consumer-${group.id}-${编号}
。
未启用Rack Aware的分配流程
此流程针对于单个topic的所有partition。
现有如下partition和consumer:
partition: partition 0, partition 1, partition 2
consumer: consumer 0(consumer-test-group-1-UUID), consumer 1(consumer-test-group-2-UUID)
排序结果:
partition 0(编号:0), partition 1(编号:1), partition 2(编号:2)
consumer 0(consumer-test-group-1-UUID), consumer 1(consumer-test-group-2-UUID)
分配结果
总共有3个分区,两个消费者,那么每个消费者平均有 3(分区数) / 2(consumer数) = 1个分区,剩余1个分区(3%2),剩余的分区按照消费者的先后顺序分配,排在前面的优先分配 因此有如下分配结果:

缺点
明显,排在前面的消费者实例会分配到更多的分区,某些情况下并不是很好的方案(比如前面的消费者当前的消费压力已经很大了,某个消息的消费时长过长,导致两次调用poll方法的间隔很长)。
启用Rack Aware的分配流程
启用条件
- 所有consumer所在的rack和所有topic的partition副本所在的rack没有重叠,这种情况下没有启用
Rack Aware
的必要 - 在consumer和partition的rack存在重叠的情况下,
preferRackAwareLogic
为true时,强制启用Rack Aware
,不过此field仅在单测中使用,在正常使用场景下可以直接忽略 - 在consumer和partition的rack存在重叠的情况下,每个partition的副本所在的rack完全一致,此种情况下无需启用
Rack Aware
。假设consumer 0 所在的rack为rack-0,既然和partition副本所在的rack有重叠,那么至少存在一个partition 0的某个副本也在rack-0,又因为每个partition的副本所在的rack完全一致,那么其他partition的副本中至少有一个也在rack-0上,即对于consumer 0来说,不管分配哪个分区,这个分区中总有一个副本的rack和consumer 0在一个rack上,也就没有所谓rack一致优先分配的必要了。所以,要启用Rack Aware
,至少要有一个分区的副本所在rack和其他分区不一样。
为说明方便,对partition的副本做了简化处理,假设所有partition只有一个leader副本。
现有两个Topic,topic0-1,每个topic都各自有三个partition,partition0-2,以及两个consumer0-1,具体关系以及所在rack如下图所示。

具体流程
由于consumer 0和consumer 1 所在的rack同样存在partition,且两个topic的partition所在的rack不完全相同,因此满足启用Rack Aware
机制的前提条件。
RangeAssignor
的Rack Aware
机制会将订阅了完全相同的topic的consumer放在一起处理,因此这里,consumer 0 和consumer 1会放在一起进行处理。进一步地,如果这些consumer订阅的topic中有相同数量的partition的,也会放到一起进行处理,因此这里会将topic 0 和topic 1 放到一起进行处理。
接下来,逐个分区开始分配。首先要从consumer中找到第一个其所在的rack和topic 0 和 topic 1 的对应编号的分区所在 rack 都相同 的consumer(实际上只要 consumer 和每个分区对应编号的分区的所有副本所在的rack有一个相同的即可,由于这里做了简化处理,每个分区的副本只有一个,因此需要和这一个副本所在的 rack 一致)。
topic0-1的第一个分区的分配流程:
由于consumer 1 所在的rack: rack 0 和 topic 0 以及 topic 1 的 partition 0 所在的rack是一样的,因此我们会将topic 0 - parititon 0 以及 topic 1 - parititon 0 都分配给 consumer 1。之后判断consumer 1 对于 topic 0 和 topic 1 的分区的分配数量是否已经到了上限。具体上限的计算公式为:
topic 0 总共有3个分区,两个消费者,那么每个消费者平均有 3(分区数) / 2(consumer数) = 1个分区,剩余1个分区(3%2),剩余的分区按照消费者的先后顺序分配,排在前面的优先分配,因此 conusmer 0 最多可以分配到 topic 0 的 2个分区。类似的,consumer 1 最多可以分配到 topic 0 的 1个分区。 topic 1 因为分区数量和topic 0 一样,所以每个消费者能分到的最大分区数量也是一样的。
如果达到了上限,后面再分配分区就直接跳过此consumer。对于,consumer 1,它已经分配不到分区了。
topic0-1的第二个分区的分配流程:
由于consumer 0 所在的rack: rack 0 和 topic 0 以及 topic 1 的 partition 1 所在的rack都不一样,因此这里不将 partition 1 分配给 consumer 0。而consumer 1 也是类似的情况,所以两个topic的partition 1 不会分配给任何一个consumer。
topic0-1的第三个分区的分配流程:
与第二个分区情况类似,所以两个topic的partition 2 也不会分配给任何一个consumer。
综上所述,Rack Aware
机制还是比较严格的,如果一个consumer订阅了多个topic,而多个topic的分区数量又一样的话,每个topic同样编号的分区要想分配给该consumer,此conusmer所在的rack必须要和这些同样编号的分区的所有副本所在的rack中至少有一个是相同的。其中有哪一个topic的分区的副本的rack中没有和该conusmer的rack一样,就不会分配给它任何一个topic的对应编号的分区。
后续再分配
经过Rack Aware
分配历程后,consumer 1 分配到了 topic0-1的 partition 0,对于topic0-1,分别都能再分到1个分区。 conusmer 0 则是一无所获。 接下来则是按照此前说明的未启用Rack Aware
时的分配流程来进行分配剩下的分区了。相对来说简单的多,最终可以得到如下结果:
