1、什么是 rebalance 机制
重平衡(rebalance)机制规定了如何让消费者组下的所有消费者来分配 topic 中的每一个分区。
2、rebalance 机制的触发条件是什么
(1)消费者组内成员变更
成员增加 :当有新的消费者加入到消费者组时,会触发重平衡以重新分配分区。
成员减少 :
主动离组:某个消费者调用 close() 方法显式退出消费者组。
被动离组:由于网络故障、应用程序崩溃或其他原因导致消费者无法发送心跳请求给协调者(Coordinator),超过一定时间(session.timeout.ms 或 max.poll.interval.ms),协调者认为该消费者已死亡并将其移除。
(2)订阅 topic 的数量发生变化
如果消费者组使用正则表达式订阅了多个 topic,并且新增了一个符合该模式的新 topic,这将触发 rebalance 以便新主题的分区可以被分配给现有消费者。
(3)订阅 topic 的分区数发生变化
当某个 topic 的分区数增加时(Kafka 支持动态增加分区),为了使新增加的分区能够被合理分配,也会触发 rebalance。
3、rebalance 机制的影响是什么
(1)消费中断
消息消费暂停 :rebalance 过程中,所有消费者都会暂时停止,直到新的分配方案确定并生效。
(2)资源消耗增加
网络流量增加 :rebalance 期间,消费者需要重新建立与协调者(Coordinator)的连接,并接收新的分区分配信息。这会生成额外的网络流量。
CPU 和内存使用上升 :频繁的 rebalance 会导致更多的 CPU 和内存用于处理心跳请求、位移提交以及重新分配逻辑,增加了 Broker 和消费者负载。
(3)延迟增大
由于消息消费暂停,已经到达但尚未处理的消息会被延迟处理,可能导致端到端的延迟增加。
4、如何减少 rebalance 的发生
rebalance 的触发条件中,2 和 3 可以人为约定规范的方式来减少 rebalance,但往往第一种情况才是引起 rebalance 的最常见原因。
除了消费者成员正常的添加和停止之外,还有些情况下 Coordinator 会错误的认为消费者组成员已停止而将其踢出组以致发生 rebalance。
我们知道维持分布式系统的方式通常是通过发送心跳的,kafka也不例外。
由于一些问题,你可能不清楚没接收到心跳的原因,比如是因为对方真正挂了还是只因为当时负载过高或网络堵塞没来得及发心跳等。所以一般会约定一个时间,超时即判定对方挂了。而在 kafka 消费场景中,session.timout.ms 参数就是规定这个超时时间的。
另外一个参数是 heartbeat.interval.ms,它控制发送心跳的频率,频率越高越不容易被误判,当然代价是会消耗更多资源。
此外,还有一个参数,max.poll.interval.ms,我们知道消费者 poll 数据后,需要进行处理然后再拉取。但如果两次拉取时间间隔超过这个参数的值,那消费者就会被踢出消费者组。也就是说处理时间不能超过 max.poll.interval.ms。该参数的默认值是 5 分钟,如果消费者接收到数据后会执行耗时操作,则应将其设置得大些。
总结一下几个参数 :
通过上述可知,一般建议如下 :
(1)heartbeat.interval.ms < session.timeout.ms:确保心跳间隔足够短,以便及时发现消费者故障,但又不会过于频繁发送心跳请求,增加网络开销。
(2)max.poll.interval.ms > 处理时间:确保这个值大于消费者处理消息所需最大时间,以避免由于处理时间过长而被误认为死亡。
5、rebalance 的三种策略
(1)Range
基于每个主题的分区分配,先对同一个 topic 里面的分区按照序号排序,并对消费者按照字母序进行排序。假如一个 topic下有 10 个分区,3 个消费者,排序后的分区是 0,1,2,3,4,5,6,7,8,9,消费者排序完之后将会是C1, C2, C3。通过 partition数/consumer数 来决定每个消费者应该消费几个分区。如果除不尽,那么前面消费者将会多消费 1 个分区。
例如,10/3 = 3 余 1 ,除不尽,那么 消费者 C1 便会多消费 1 个分区,最终分配结果如下:
C1 0,1,2,3 分区
C2 4,5,6 分区
C3 7,8,9 分区(如果有11 个分区的话,C1 将消费 0,1,2,3 分区,C2 将消费4,5,6,7 分区 C3 将消费 8,9,10 分区)
Range 的弊端:
如上,只是针对 1 个 topic 而言,C1 消费者多消费 1 个分区影响不是很大。如果有 N 个 topic,那么针对每个 topic,消费者 C1 都将多消费 1 个分区,topic 越多,C1 消费的分区会比其他消费者明显多 N 个分区。
(2)RoundRobin(默认策略)
轮询分区,kafka 默认的 rebalance 策略,把所有的 partition 和 consumer 都列出来,通过轮询来分配 partition 到消费者。
轮询分区分为如下两种情况:
①同一消费组内所有消费者订阅的 topic 都是相同的
②同一消费者组内的消费者订阅的 topic 不同
对于第一种情况,RoundRobin 策略的分区分配是均匀的。
例如:同一消费者组中,有 3 个消费者 C0、C1 和 C2,都订阅了 2 个主题 t0 和 t1,并且每个主题都有 3 个分区(p0、p1、p2),那么所订阅的所以分区可以标识为t0p0、t0p1、t0p2、t1p0、t1p1、t1p2。最终分区分配结果如下:
消费者C0 消费 t0p0 、t1p0 分区
消费者C1 消费 t0p1 、t1p1 分区
消费者C2 消费 t0p2 、t1p2 分区
对于第二种情况,可能会导致分区分配不均匀。如果某个消费者没有订阅消费组内的某个 topic,那么在分配分区的时候,此消费者将不会分配到这个 topic 的任何分区。
例如:同一消费者组中,有 3 个消费者 C0、C1 和 C2,他们共订阅了 3 个主题:t0、t1 和 t2,这 3 个主题分别有 1、2、3 个分区(即:t0有1个分区(p0),t1有 2 个分区(p0、p1),t2 有 3 个分区(p0、p1、p2)),即整个消费者所订阅的所有分区可以标识为 t0p0、t1p0、t1p1、t2p0、t2p1、t2p2。具体而言,消费者 C0 订阅的是主题 t0,消费者 C1 订阅的是主题 t0 和 t1,消费者 C2 订阅的是主题 t0、t1 和t2,最终分区分配结果如下:
消费者C0 消费 t0p0
消费者C1 消费 t1p0 分区
消费者C2 消费 t1p1、t2p0、t2p1、t2p2 分区
(3)Sticky
粘性分配策略,主要是为了让目前的分配尽可能保持不变,只挪动尽可能少的分区来实现重平衡。
举例,有三个消费者 C0,C1,C2 。三个主题 t0,t1,t2,t3。每个主题各有两个分区, t0p0,t0p1,t1p0,t1p1,t2p0,t2p1,t3p0,t3p1。
现在订阅情况如下:
C0:t0p0,t1p1,t3p0
C1:t0p1,t2p0,t3p1
C2:t1p0,t2p1
假设现在 C1 挂掉了,如果是 RoundRobin 分配策略,那么会变成下面这样:
C0:t0p0,t1p0,t2p0,t3p0
C2:t0p1,t1p1,t2p1,t3p1
就是说它会全部重新打乱,再分配,而如果使用 Sticky 分配策略,会变成这样:
C0:t0p0,t1p1,t3p0,t2p0
C2:t1p0,t2p1,t0p1,t3p1
也就是说,尽可能保留了原来的分区情况,不去改变它,在这个基础上进行均衡分配。
大佬,点个赞在走呗!