Kafka 三种分区策略分析

背景

年初的时候用搭建过一个数据处理系统,mq用的kafka,当时对kafka的分区策略不明确,用的默认策略,即RangeAssigor,但我并不知道具体的分区消费逻辑。这几天趁着架构组向下推广kafka, 我研究了具体的分区原理。

我有个疑问

最新的kafka有三种分区策略,分别是RangeAssigor、RoundRobinAssignor、StickyAssignor,默认的策略是RangeAssigor,客户端可以指定使用某种策略,那这三种策略的原理、应用场景是什么?

 

主题及分区例子

A组: 两个主题T1 、 T2 ,每个主题配备四个分区,分别为P1 、P2 、P3 、 P4

B组: 两个主题T1、T2, 每个主题配备三个分区,分别为P1 、P2 、P3

C组: 一个主题T1,每个主题配备四个分区,分别为P1、P2、P3、P4

D组: 三个主题T1、T2、T3, 每个主题分别有 1 、 2、 3个分区

E组:四个主题T1、T2、T3、T4,每个主题分别有俩分区

 

由于在kafka中,一个消费组里的消费者可以订阅多个topic,每个topic会包含多个分区,但默认情况下一个分区只能被一个消费组下面的一个消费者消费,并且如果消费者没有订阅某个主题,那么该消费者不会分配到这个主题下的分区。

那么为什么我们要分区? 分区对于 Kafka 集群的好处是:实现负载均衡。分区对于消费者来说,可以提高并发度,提高效率。

 

所以我们把主题与分区进行排列组合一下,即:

A组: T1P1、T1P2 、 T1P3、 T1P4、  T2P1、 T2P2、 T2P3、  T2P4八种

B组: T1P1、T1P2 、 T1P3、  T2P1、 T2P2、 T2P3 六种

C组:   T1P1、  T1P2、  T1P3 、T1P4 四种

D组:T1P1 、T2P1、 T2P2、T3P1 、 T3P2、 T3P3  六种

E组:T1P1、T1P2、T2P1、T2P2、T3P1、T3P2、T4P1、T4P2

 

分析及举例

RangeAssigor分区策略:RangeAssigor策略是对每个topic单独进行分配的,原理是按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进行平均分配,以保证分区尽可能均匀地分配给所有的消费者。

同一消费组订阅同一主题

  • 比如当前消费组里有一个消费者C1,那么拿C组4个分区(T1P1、  T1P2、  T1P3 、T1P4 )来举例,毋庸置疑,所有分区的数据都需要它来消费,也就是说消费者C1,对应的分区为  
    • C1 => T1P1、  T1P2、  T1P3 、T1P4
       
  • 如果我们再往消费组里加一个消费者C2,那么分区会如何分配呢?此时有两个消费者,四个分区, RangeAssigor策略会将分区先按照id顺序排序,然后将消费者按字典顺序排序,尽量均匀的分配给消费组里的消费者,即 
    • C1 =>  T1P1、  T1P2
    • C2=> T1P3 、T1P4
       
  • 上述的分区是偶数的,遇到可以被整除的消费者,每个消费者获得的可消费分区数量都是均匀的,加入我们的C组中只有三个分区呢?此时有两个消费者,三个分区,那该如何分配?  此时C组排列组合为 T1P1、  T1P2、  T1P3
    • C1=> T1P1、  T1P2
    • C2=> T1P4
    那么是怎么计算的,我们可以这么计算,分区排列组合数M, 消费组中消费者数量N , 均匀分配个数x = M/N    不均匀个数y = M%N,  那么,我们可以得知,前M个消费者会被分配x+1个分区,而剩下的消费者只会获得x个分区,消费组内消费者之间分配的分区差值不会>1
     
  • 那如果我们的消费者个数大于分区数,又会怎么分配呢,比如现在消费组里有5个消费者C1、C2、C3、C4、C5,分区排列是C组,分区个数X < 消费者个数Y 的情况,此时就会有  Y-X个消费者被闲置,及字典顺序靠后的消费者不会分配到分区
    • C1  => T1
    • C2 => T2
    • C3 => T3
    • C4 => T4
    • C5 闲置

同一消费组消费订阅不同主题

  • 拿A组2个主题8个分区(T1P1、T1P2 、 T1P3、 T1P4、  T2P1、 T2P2、 T2P3、  T2P4)举例,有两个消费者C1、C2分别订阅了这俩主题,我们对分区、消费者排好序之后开始分配,由于rangeassigor策略是针对每个topic单独分配的,所以原理是
    • 先从把主题T1的三个分区分配给消费者,即
      • C1 => T1P1、T1P2
      • C2 => T1P3、 T1P4
    • 然后再把主题T2的三个分区分配给消费者, 即
      • C1 =>  T1P1、T1P2、 T2P1、 T2P2
      • C2=>  T1P3 、T1P4、 T2P3、  T2P4

         此时看,由于每个主题的分区个数都可以被同一消费组的消费者个数整除,所以此时看着分配的还听均匀哈,那如果不能被整除的话,由会如何分配呢?

  • 拿B组2个主题6个分区(T1P1、T1P2 、 T1P3、  T2P1、 T2P2、 T2P3)举例,有2个消费之C1 、C2 分别订阅了这俩主题,按照分配策略,每个主题分区不被消费者个数整除的情况,字典顺序靠前的消费者就会多承担分区的策略,分配结果如下
    • 先从把主题T1的三个分区分配给消费者,即
      • C1 => T1P1、T1P2
      • C2 => T1P3
    • 然后再把主题T2的三个分区分配给消费者, 即
      • C1 => T1P1、T1P2、T2P1、 T2P2
      • C2 => T1P3、 T2P3

         此时再看,消费者承担的分区就不均匀了,kafka里有很多个这样的主题会发生什么呢?答案是消费者C1承担的分区过多, 负载不均衡,会造成挤压,消费不及时,可见这种消费有问题。

 

RoundRobinAssignor分区策略:RoundRobinAssignor策略原理是将所有分区都按照字典顺序排序,然后通过轮询的方式均匀的分配给消费组内的每个消费者,我们可以拿此策略与nginx的roundrobin轮询策略类别,原理上是一致的。

由于是将所有主题与分区的排列组合按照字典顺序排序,所以我们无需考虑统一消费组内订阅几个主题的情况

  • 我们拿B组的的6个分区(T1P1、T1P2 、 T1P3、  T2P1、 T2P2、 T2P3)来举例,消费组内有2个消费者C1、C2,此时分区个数能被消费者个数整除,按照轮询的方式分配结果如下
    • C1 => T1P1 、 T1P3、  T2P2
    • C2 => T1P2、 T2P1、 T2P3

       我们可以看到,RoundRobinAssignor分区策略解决了RangeAssigor的问题,遇到分区个数不能被消费者个数争取的情况,轮询策略分配的会更加均匀,从而达到避免过载问题。、

 

  • 假如A组只有7个分区(T1P1、T1P2 、 T1P3、 T1P4、  T2P1、 T2P2、 T2P3),没有T2P4分区时,此时分区个数不能被消费者个数整除,轮询分配策略又会如何分配呢?答案是,给消费者一个个轮,少的就少吧,就是这么随性
    • C1 => T1P1 、 T1P3、  T2P1、 T2P3
    • C2 => T1P2、 T1P4、 T2P2

      这样分配看起来依旧不错,无伤大雅

  •    那么我们再举个伤大雅的例子,哈哈哈哈,上我们的D组,3个主题6个分区(T1P1 、T2P1、 T2P2、T3P1 、 T3P2、 T3P3),请仔细看下D组,每个主题对应的分区不同哈,消费组里配备3个消费者C1、C2、C3,C1订阅T1,C2订阅T1、T2,C3订阅T1、T2、T3,分区个数可以被消费者个数整除,那么分配结果如下
    • C1 => T1P1
    • C2 =>T2P1
    • C3 =>T2P2、T3P1、T3P2、T3P3
    出现上面的情况,我们首先需要知道的一点事,如果消费者没有订阅某个主题,那么消费者是不会获取到这个主题的分区的。接来下我们分析一下为什么会得到上面的分配结果
    • C1订阅T1,那么它得到T1P1,没有问题
    • C2订阅了T2 , 按照roundRobin策略T2P1被分配给C2, 也没问题
    • C3被分配了T2P2,原理同上,没有问题
    • 接下来就是按照roundRobin策略开始分配T3的分区,但是我们发现,C1、C2并没有订阅T3,那么T3的所有分区都由C3承担

           那么你看出问题了吗,此种情况下,分区又不均匀了,C3挂上的分区过多,负载变高,一般情况下咱们用不到同一个消费组如此订阅主题,这种比较高级。

 

StickyAssignor分区策略:Kafka从0.11.x版本开始引入这种分配策略,属于对roundRobin的一种优化,他有俩目的,一个是为了更均匀的分配分区,另一个就是分区的分配尽可能的与上次分配的保持相同,这点可能这么看不理解,下面我会具体举例给解释一下

  • 我们用E组的4个主题,每个主题2个分区,共8个分区(T1P1、T1P2、T2P1、T2P2、T3P1、T3P2、T4P1、T4P2),消费组里配置3个消费者C1、C2、C3,如果使用使用StickyAssignor分区策略,分配结果如下
    • C1=>T1P1、T2P2、T4P1
    • C2=>T1P2、T3P1、T4P2
    • C3=>T2P1、T3P2

       有没有很感动?这不就是roundRobin策略吗?你看结果都一样,你是不是耍我?别急,我说了这是roundRobin的一种优化嘛,如果此时C2突然肚子痛挂了呢?会引起组内rebalance,重新分配分区,分配结果如下

    • C1=>T1P1、T2P2、T4P1、T3P1
    • C3=>T2P1、T3P2、T1P2、T4P2

       此时还不直观,我们再把roundRobin策略下C2挂了的情况触发rebalance后分配结果列一下

    • C1=>T1P1、T2P1、T3P1、T4P1
    • C2=>T1P2、T2P2、T3P2、T4P2

       对比StickyAssignor、roundRobinAssignor两种策略rebalance后分配结果,你发现了什么?roundRobinAssignor实际上是在C2挂了之后,重新将8个分区按规则均匀分配到C1、C3上,但是StickyAssignor缺没有重新分配,而是在原有C1、C3的已有分区的基础上,继续分配C2的分区,并且还实现了均匀分配,保证负载均衡,现在觉得呢,是不是StickyAssignor策略更好一些?你可能要问,就为了个避免全部重新分配?这里牵扯出rebalance后,消费者重新消费新分区的时耗问题,尽可能少的分配,才会更快的拉起服务。

 

结论

目前咱们引入的kafka, 分配策略还处在默认策略即RangeAssigor, 这个分业务,目前看,咱们的业务几乎都是一个消费组订阅同一个主题,并且架构组提供的扩展也是,同一消费组仅支持消费一个主题,如果需要消费多个主题,那得改造一下。理论上默认策略够用,也不会引起负载问题。而如果我们有一个消费组订阅不同主题的情况,我建议直接使用StickyAssignor策略,因为他在消费者或者分区挂了时,rebalance更均匀,更友好。最后策略可以由客户端指定,不是说咱放着好的策略不用,这得根据场景选。

 

这里附上kafka官方文档,大家有兴许可以继续深入 https://kafka.apache.org/24/javadoc/org/apache/kafka/clients/consumer/

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值