KafkaConsumer分区分配策略

前面我们讲到Consumer加入组的流程,主要包括了JoinGroup 和 SyncGroup 两个步骤。其中在JoinGroup 之后会选出 Consumer Group 的Leader 用于组内订阅关系的制定。今天我们详细的讲解一下,Kafka Java客户端提供的几种分区分配策略。Kafka 客户端提供了 3 种分区分配策略:RangeAssignor、RoundRobinAssignor 和 StickyAssignor,前两中分配方案相对简单一些,StickyAssignor 分配方案相对复杂一些,可以通过 partition.assignment.strategy 参数去设置。

RangeAssignor

RangeAssignor 是按照 Topic 的维度进行分配的,也就是说按照Topic 对应的每个分区平均的按照范围区段分配给Consumer 实例。这种分配方案是按照Topic 的维度去分发分区的,此时可能会造成先分配分区的Consumer 实例的任务过重。

从上图的最终分配结果看来,因为是按照Topic A 和 Topic B 的维度进行分配的。对于Topic A 而言,有 2 个消费者,此时算出来C0 得到 2 个分区,C1 得到 1 个分区;对于Topic B的维度也是一样,也就是先分配的Consumer 会得到的更多,从而造成倾斜。需要注意一点的是,RangeAssignor 是按照范围截断分配的,不是按顺序分发的。

RoundRobinAssignor

RoundRobinAssignor 中文可以翻译为轮询,也就是顺序一个一个的分发。其中代码里面的大概逻辑如下:拿到组内所有Consumer 订阅的 TopicPartition,按照顺序挨个分发给 Consumer,此时如果和当前Consumer 没有订阅关系,则寻找下一个Consumer。从上面逻辑可以看出,如果组内每个消费者的订阅关系是同样的,这样TopicPartition 的分配是均匀的。

当组内每个消费者订阅的Topic 是不同的,这样就可能会造成分区订阅的倾斜。

StickyAssignor

StickyAssignor 是Kafka Java客户端提供的 3 中分配策略中最复杂的一种,从字面上可以看出是具有“粘性”的分区策略。Kafka从0.11 版本开始引入,其主要实现了两个功能:

1、主题分区的分配要尽可能的均匀;

2、当Rebalance 发生时,尽可能保持上一次的分配方案。

当然,当上面两个条件发生冲突是,第一个提交件要优先于第二个提交,这样可以使分配更加均匀。下面我们看一下官方提供的 2 个例子,来看一下RoundRoubin 和 Sticky 两者的区别。

从上面我们可以看出,初始状态各个Consumer 订阅是相同的时候,并且主题的分区数也是平均的时候,两种分配方案的结果是相同的。但是当Rebalance 发生时,可能就会不太相同了,加入上面的C1 发生了离组操作,此时分别会有下面的 Rebalance 结果:

从上面Rebalance 后的结果可以看出,虽然两者最后分配都是均匀的,但是RoundRoubin 完全是重新分配了一遍,而Sticky 则是在原先的基础上达到了均匀的状态。

下面我们再看一个Consumer订阅主题不均匀的例子。


从上面的订阅关系可以看出,Consumer 的订阅主题个数不均匀,并且各个主题的分区数也是不相同的。此时两种分配方案的结果有了较大的差异,但是相对来说Sticky 方式的分配相对来说是最合理的。下面我们看一下 C1 发生离组时,Rebalance 之后的分配结果。

​​​​​​​

从上面结果可以看出,RoundRoubin 的方案在Rebalance 之后造成了严重的分配倾斜。因此在生产上如果想要减少Rebalance 的开销,可以选用 Sticky 的分区分配策略。

自定义实现

通常情况下,Kafka 客户端提供的分区分配规则就已经够用了。但是对于一些跨区域多机房的集群部署方案时,就可能需要优化其分区的分配策略。一种比较常见的方案是,将同一个机房里面的 Broker 与对应的 Consumer建立关系,从而可以减少网络延迟,以及减少出口带宽的占用。


参考:《Apache Kafka 源码剖析》、Kafka 源代码