这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战
重平衡,也就是 Rebalance, 就是让一个 Consumer Group
下所有的 Consumer
实例就如何消费订阅主题的所有分区达成共识的过程。在 Rebalance
过程中,所有 Consumer
实例共同参与,在协调者组件的帮助下,完成订阅主题分区的分配。但是,在整个过程中,所有实例都不能消费任何消息,因此它对 Consumer
的 TPS 影响很大
重平衡的弊端
- Rebalance 影响 Consumer 端 TPS。在 Rebalance 期间,Consumer 会停下手头的事情,什么也不能做。
- Rebalance 很慢。如果你的 Group 下成员很多,需要停机很久
- Rebalance 效率不高。当前 Kafka 的设计机制决定了每次 Rebalance 时,Group 下的所有成员都要参与进来,而且通常不会考虑局部性原理,但局部性原理对提升系统性能是特别重要的。
遗憾的是,我没没有办法避免或者说解决重平衡
问题,但是我们可以避免不必要的重平衡发生,减少重平衡操作。
如何避免重平衡
重平衡发生的时机
- 组成员数量发生变化
- 订阅主题数量发生变化
- 订阅主题的分区数发生变化
因为后面两个通常都是运维的主动操作,所以它们引发的 Rebalance 大都是不可避免的,(其实生产环境也很少发生后面这两个重平衡)。我们主要解决因为组成员数量变化而引发的 Rebalance 该如何避免。
组员数量变化发生重平衡
这里先穿插一下协调者的概念
所谓
协调者
,在 Kafka 中对应的术语是Coordinator
,它专门为Consumer Group
服务,负责为 > Group 执行 Rebalance 以及提供位移管理和组成员管理等。
所有 Broker 在启动时,都会创建和开启相应的 Coordinator 组件。Consumer Group Kafka 内部位移主题__consumer_offsets
确定为它服务的 Coordinator 在哪台 Broker 上。Kafka 为某个 Consumer Group 确定 Coordinator 所在的 Broker 的算法有 2 个步骤。
- 确定由位移主题的哪个分区来保存该 Group 数据:
partitionId=Math.abs(groupId.hashCode() % offsetsTopicPartitionCount)
。- 找出该分区 Leader 副本所在的 Broker,该 Broker 即为对应的 Coordinator。
- 增加Consumer实例
当我们启动一个配置有相同group.id
值的 Consumer
程序时,实际上就向这个 Group 添加了一个新的 Consumer 实例。此时,Coordinator 会接纳这个新实例,将其加入到组中,并重新分配分区。通常来说,增加 Consumer 实例的操作都是计划内的,可能是出于增加 TPS 或提高伸缩性的需要。总之,它不属于我们要规避的那类“不必要 Rebalance”。
- 减少Consumer实例
停掉某些 Consumer 实例,那自不必说,关键是在某些情况下,Consumer 实例会被 Coordinator 错误地认为“已停止”从而被“踢出”Group。如果是这个原因导致的 Rebalance,这时,我们就需要采取措施避免重平衡
Consumer异常退组的情况
-
未能及时发送心跳,导致 Consumer 被“踢出”Group
每个 Consumer 实例都会定期地向 Coordinator 发送心跳请求,表明它还存活着。如果某个 Consumer 实例不能及时地发送这些心跳请求,Coordinator 就会认为该 Consumer 已经“死”了,从而将其从 Group 中移除,然后开启新一轮 Rebalance。
- Consumer 端有个参数,叫
session.timeout.ms
,该参数的默认值是 10 秒,如果 Coordinator 在 10 秒之内没有收到 Group 下某 Consumer 实例的心跳,它就会认为这个 Consumer 实例已经挂了。 - 除了这个参数,Consumer 还提供了一个允许你控制发送心跳请求频率的参数,就是
heartbeat.interval.ms
。这个值设置得越小,Consumer 实例发送心跳请求的频率就越高。频繁地发送心跳请求会额外消耗带宽资源,但好处是能够更加快速地知晓当前是否开启 Rebalance,因为,目前 Coordinator 通知各个 Consumer 实例开启 Rebalance 的方法,就是将REBALANCE_NEEDED
标志封装进心跳请求的响应体中。
- Consumer 端有个参数,叫
我们可以设置
- 设置 session.timeout.ms = 6s。
- 设置 heartbeat.interval.ms = 2s。
保证 Consumer 实例在被判定为“dead”之前,能够发送至少 3 轮的心跳请求。
-
Consumer 消费时间过长
Consumer 端有一个参数,用于控制 Consumer 实际消费能力对 Rebalance 的影响,即
max.poll.interval.ms
参数。它限定了 Consumer 端应用程序两次调用 poll 方法的最大时间间隔。它的默认值是 5 分钟,表示你的 Consumer 程序如果在 5 分钟之内无法消费完 poll 方法返回的消息,那么 Consumer 会主动发起“离开组”的请求,Coordinator 也会开启新一轮 Rebalance。
如果我们的业务消费消息的时间确实很长,我们可以适当将这个参数调大,如果我们的业务消费时间不长,但是还是经过很久才提交位移,那么我们需要检查一下是否我们的程序频繁的发生了Full GC
导致的,如果是,需要对JVM进行调优,有关JVM调优的方案可以参照 保姆级JVM调优实战
总结
- 重平衡会影响我们程序的吞吐量
- 排查重平衡发生的原因,避免Consumer端异常情况下的重平衡
- 通过设定Consumer端的参数,或者进行JVM调优避免不必要的重平衡
- session.timeout.ms
- heartbeat.interval.ms
- max.poll.interval.ms
- GC 参数