partition.assignment.strategy 决定分区怎么分给消费组成员。这次用同一个场景把四种内置策略各跑一遍:三个 topic(分别有 3、2、4 个分区,共 9 个分区)被同一个消费组订阅,消费者按 C1、C2、C3 的顺序依次加入,然后 C2 离开。每个阶段读取每个成员持有的分区,直接对比"分得是否均匀"以及"成员变化时有多少分区换了主人"。
命令 · java // topics: strat-a(3 分区)、strat-b(2 分区)、strat-c(4 分区),副本数 3 // 每个成员是一个独立线程里的 KafkaConsumer,同一 group.id,group.protocol=classic,heartbeat.interval.ms=500 // 依次启动 C1、C2、C3(每次间隔 7 秒),再让 C2 离开,用 ConsumerRebalanceListener 记录每个成员当前持有的分区
=== Range === C1、C2 在线: C1=[a0, a1, b0, c0, c1] C2=[a2, b1, c2, c3] C1、C2、C3: C1=[a0, b0, c0, c1] C2=[a1, b1, c2] C3=[a2, c3] === RoundRobin === C1、C2、C3: C1=[a0, b0, c1] C2=[a1, b1, c2] C3=[a2, c0, c3] === Sticky === C1、C2、C3: C1=[a0, b1, c1] C2=[a1, b0, c0] C3=[a2, c2, c3] === CooperativeSticky === C1、C2、C3: C1=[a0, b1, c1] C2=[a1, b0, c0] C3=[a2, c2, c3]
(a0 表示 topic strat-a 的分区 0,其余同理。)
9 个分区分给 3 个成员,理想是每人 3 个。Range 分出了 4、3、2:它是逐个 topic 独立分配的,每个 topic 里都是"前面的成员多拿一个"——a 有 3 个分区,每人 1 个;b 有 2 个分区,只有 C1、C2 各拿 1 个;c 有 4 个分区,C1 拿 2 个,C2、C3 各拿 1 个——所有 topic 里 C1 都排在最前面,多出来的分区都落在它身上,累加之后 C1 最多、C3 最少。RoundRobin、Sticky、CooperativeSticky 都是把所有 topic 的分区放在一起分配,得到 3/3/3。这里的分区标签(谁拿了 a1)取决于成员 ID 的顺序,重跑一次可能不同,但数量差别是策略决定的。
策略 C3 加入时换了主人的分区数 C2 离开时换了主人的分区数(C2 原本持有 3 个) Range 3 [a1, a2, c3] 3 RoundRobin 5 [b0, b1, a2, c0, c3] 6 (多出来的:a2, c3, b0 也换了主人) Sticky 3 [a2, c2, c3] 3 CooperativeSticky 3 [a2, c2, c3] 3
这里的"换了主人"是把加入前后两个快照里每个分区的所属成员逐个比较得出的。C3 加入需要从别人那里拿 3 个分区,最少需要移动 3 个,Range、Sticky、CooperativeSticky 都做到了最少的 3 个,RoundRobin 移动了 5 个。C2 离开时,它持有的 3 个分区必须换主,最少也是移动 3 个:Range、Sticky、CooperativeSticky 刚好只动这 3 个,RoundRobin 却动了 6 个,其中 a2、c3、b0 这三个分区原本好好地在 C1、C3 手里,却被换了主人。这是 RoundRobin"每次都把所有分区重新排一遍"的直接后果。Sticky 系列的"sticky"就是尽量保留原来的归属,实测确实做到了。Range 在这个例子里的移动数也是最少,但它的均匀度更差(4/3/2)。
上面两张表里,Sticky 和 CooperativeSticky 的结果完全一致。它们的区别不在最终分配,而在重新分配的过程:上一篇《Consumer Rebalance》已经用持续写入的数据对比过,CooperativeSticky 只收回要移动的分区,没被移动的分区不停顿,但被移动的分区要经过第二轮 rebalance,停顿大约等于 heartbeat.interval.ms。本篇没有重复测量这一点。
结果:Range 因为逐 topic 分配,在多个 topic 且分区数不能整除时容易不均匀(这次 4/3/2);RoundRobin 分得最均匀,但成员变化时会多动分区(这次两次变化分别移动了 5 个和 6 个,而最少需要的都是 3 个);Sticky 和 CooperativeSticky 既均匀(3/3/3)又只移动必要的分区,二者的最终分配一致,区别在 rebalance 过程。
每一步都是本文上面两节用ConsumerRebalanceListener记录的原始分配快照对比得到的结果。