Lazy loaded image
Kafka 源码阅读
🗒️Kafka 五、消费者
Words 1931Read Time 5 min
2025-7-22
2025-7-22
type
status
date
slug
summary
tags
category
icon
password
原文
消费者负责订阅 Kafka 中的主题,并且从订阅的主题上拉取消息。在 Kafka 中还有一层消费者组(Consumer Group)的概念,每个消费者都有对应的消费者组。当消息发布到主题后,只会投递给订阅它的每个消费组中的一个消费者。
消费者可以通过客户端参数 partition.assgnment.strategy 来设置消费者与订阅的主题之间的分区分配策略。

1.分区分配策略

 

1.1 RangeAssignor

这是 kafka 默认的分配策略的原理按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进行平均分配,以保证分区尽可能均匀地分配给所有消费者,然后将分区按照跨度进行平均分配,以保证分区尽可能均匀地分配给所有的消费者。
💡
hints: 这里说的消费者总数就是消费者是订阅同一主题的消费者数。

1.1.1 分配规则

RangeAssignor 会针对每个主题独立计算分区分配,不考虑其他主题的分配情况。
具体的分配规则:
  1. 每个消费者应分配的分区数 = 分区总数/消费者数(向下取整)
  1. 剩余分区按数组分配给前余数个消费者(每个多分配一个)
假设 n=分区总数/消费者数量(向下取整),m=分区数 % 消费者数量,那么前 m个消费者每个分配 n+1 个分区,后面的(消费者-m)个消费者每个分配 n个分区。

1.1.2 示例

同一主题情况:
例如 分区数=7,消费者数量=3 ,n=7/3=2, m=7%3 = 1 ,那么第一个分区则有3个分区,后面的分区则有 2 个分区。
不同主题情况:
假设现在有 4 个主题,每个主题 2个分区,3 个消费者,每个消费者都订阅了 4 个主题,那么分配如下
c0: t0-p0、t1-p0、t2-p0、t3-p0
c1: t1-p1、t1-p1、t2-p1、t3-p1
有一个消费者完全不参数于消费,浪费消费能力。
t0的主题分配: 分区数 2,每个消费者应分配 2/3 = 0(向下取整),前 2 个消费者各多分配一个分区。c0 → t0-0, c1→ t1-1,c3→无
以此类推……
💡
hints: 分区数大于消费者数,还是有一个消费者是多余的。所以主题的分区数要和消费者最好相等或成倍数关系,这样才能保证负载均衡。
 

1.2 RoundRobinAssignor

将消费组内所有消费者及消费者订阅的分区按照字典序排序,然后通过轮询的方式逐个将分区依次分配给每个消费者。
配置方式: key = partition.assignment.strategy , value = partition.assignment.strategy。
 
如果同一个消费组内所有的消费者的订阅信息是相同的,那么 RoundRobinAssignor 的分配策略的分区分配会是均匀的。
示例:

1.2.2 示例

消费组内所有消费者的订阅信息都是相同情况
假设消费者组里有2 个消费者,c0、c1,都订阅了主题 t0、t1,所有分区的标识为 t0p0 、 t0p1 、 t0p2 、 t1p0 、 t1p1 、 t1p2。
那么分配结果为
消费者 c0: t0-p0、t0-p2、t1-p1
消费者 c1: t0-p1、t1-p1、t1-p2
如果消费组内所有的订阅信息是不同情况
假设组内有一个消费组内有 3 个消费者c1、c2、c3,主题有 3 个 t0、t1、t2,分区数分别是 1、2、3,c1、c2、c3 分别订阅了 t0、t1、t2,那么最终的分配结果为
消费者 c0: t0-p0
消费者 c1: t1-p0
消费者 c2: t1-p1、t2-p0、t2-p1、t2-p2
可以看到 消费者 c2 负载最多,完全可以将 t1p1 分配给消费者c1。

1.3 StickyAssignor

StickyAssignor 的目的有两个,分区的分配尽可能均匀,分区的分配尽可能的与上次的分配保持相同。当两者发生冲突时,第一个目标优先于第二个目标
其主要作用是在消费者组发生重平衡(Rebalance)时,最小化分区分配的变动,同时保证负载均衡。
  1. 减少分区分配重分配的扰动
    1. 其主要作用是最小化分区分配的变动,同时保证负载均衡。当消费者中的成员增、删时,尽可能的保留原有消费者与分区之间的映射关系。
  1. 保证负载均衡,当消费者变动时,分区的分配尽可能的均衡(让各个消费者的负载差值最小化),避免因分区迁移导致的消费延迟或重复处理(如消费者本地缓存失效)
💡
触发重平衡不止消费者的变动,还有分区的变动。变动给消费者带来的影响在于分区分配的过程,以及从 Broker 拉取消息。分配过程中,消费者是会暂停消费的。如果按照上面两个RangeAssignor、RoundRobinAssignor 分配策略,此时消费者 Client 端是需要重新收集分区信息,然后开始逐个分配,粘性分区是尽量保持与之前一样,这样可以减少分配带来的时间和、性能的损耗(虽然它也需要分区的元信息,但是它缩短了分配过程)。
分区迁移会导致消费者需要重新初始化与新分区的连接、重置 offset、重新加载状态等,减少迁移降低这些额外的开销。作为一个有状态服务,尤其复杂状态的变更涉及的影响面会很多,通过设计减少变更会带来性能、稳定性的提升。

1.3.1 分配规则

所有消费者分配的分区数尽可能接近(差值 ≤ 1)。 若无法完全均衡(如分区总数无法整除消费者数),则优先保证 没有消费者比其他消费者少 2 个以上分区。

1.3.2 示例

消费组内订阅信息不同情况
假设有一个消费组包含 3 个消费者,有 3 个主题(t0、t1、t2)的分区数分别是 1、2、3,c0 订阅了 t0,c1订阅了 t0、t1 ,c2订阅了 t0、t1、t2 那么分配的结果如下
c0: t0-t0
c1: t1-p0、t1-p1
c3: t2-p0、 t2-p1、t2-p2
如果是RoundRobinAssignor则分配如下
c0: t0-t0
c1: t1-p0
c3: t1-p1、t2-p0、 t1-p1、t2-p2
消费组内订阅信息相同情况,这里同 RoundRobinAssgior 差不多,不再赘述。

1.4 CooperativeStickyAssignor

上面提到的重平衡策略都存在两个关键问题:
  • 全局停顿问题:所有消费者必须完全停止消费,等待新分配方案
  • 分区迁移风暴:即使单个消费者变动,也会触发全部分区洗牌(粘性分区方案除外)
在使用CooperativeStickyAssignor分配时,不强制所有消费者释放全部分区,只要求涉及冲突的消费者释放冲突分区。这意味无冲突的消费者可以完全不冲突,有冲突的消费者也只释放少部分分区,其他分区照常消费。
在分配规则上它与StickyAssignor保持一致。

2. 消费者重消费流程

 
上一篇
Kafka 四、日志
下一篇
MIT6.824: Distributed System

Comments
Loading...