Kafka分区分配策略

在leader consumer执行partition分配的时候,存在一种特殊的机制,即consumer和partition所在节点机器所在的机架(rack)号相同的时候,会优先分配此partition给这个consumer。下面描述分区分配策略时,也会分启不启用Rack Aware两种情况分别进行分析。

RangeAssignor

partition和consumer排序

partition排序规则

partition按照编号从小到大排序即可。

consumer排序

consumer的排序稍微复杂一些,需要参考group.instance.idmember.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机制的前提条件。

RangeAssignorRack 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时的分配流程来进行分配剩下的分区了。相对来说简单的多,最终可以得到如下结果:

相关推荐
free-9d2 小时前
NodeJs后端常用三方库汇总
后端·node.js
写不出来就跑路3 小时前
WebClient与HTTPInterface远程调用对比
java·开发语言·后端·spring·springboot
天上掉下来个程小白4 小时前
MybatisPlus-06.核心功能-自定义SQL
java·spring boot·后端·sql·微服务·mybatisplus
知了一笑4 小时前
独立开发第二周:构建、执行、规划
java·前端·后端
寻月隐君4 小时前
想用 Rust 开发游戏?这份超详细的入门教程请收好!
后端·rust·github
晴空月明5 小时前
分布式系统高可用性设计 - 缓存策略与数据同步机制
后端
Real_man6 小时前
新物种与新法则:AI重塑开发与产品未来
前端·后端·面试
小马爱打代码6 小时前
Spring Boot:将应用部署到Kubernetes的完整指南
spring boot·后端·kubernetes
卜锦元6 小时前
Go中使用wire进行统一依赖注入管理
开发语言·后端·golang
SoniaChen338 小时前
Rust基础-part3-函数
开发语言·后端·rust