“万恶”的 Kafka 再平衡:2招解决不必要的再平衡

内容分享7天前发布
0 5 0

“万恶”的 Kafka 再平衡:2招解决不必要的再平衡

最近在维护一个Kafka消息转发的项目的时候,遇到一个很诡异的问题:如果消费者 poll() 方法的参数 timeout 设置太短,可能会导致再平衡失败,缘由是在 timeout 规定的时限内如果没有成功再平衡,就会导致消费者心跳线程 disable, 此时消费者将无法发送心跳给协调者所在 Broker, 消费者也就无法重新进入消费者组,这个消费者就会崩溃了。再平衡的失败,就像蝴蝶效应一样,引发了一系列后续错误,最终导致消费者不可用,程序崩溃。

尽管 Kafka 再平衡(Rebalance)赋予消费者组动态增加、删除消费者实例的能力,让其以高伸缩性和高容错性闻名遐迩,但是再平衡的失败将会导致一系列的连锁反应,直至消费者崩溃,严重影响了消费者组的消费能力。

那到底什么是再平衡?是什么引发了再平衡?再平衡的弊端是什么?如何避免不必要的再平衡呢?为什么再平衡这么招人恨呢?

不着急,且听我慢慢道来。

1. 再平衡到底是什么鬼?

再平衡(Rebalance)就是让一个消费者组(Consumer Group)的所有消费者(Consumer)实例就如何分配所订阅的主题的所有分区达成共识的过程。简单来说,再平衡实则就是中间人,给消费者组中的消费者实例与分区牵线搭桥,一一配对(或者一对多配对),让消费者实例有的消费。从这个角度看,再平衡真的是功德无量,为不少“单身狗”找到了归宿,不仅让消费者找到消费的对象,也让分区找到归宿,解决了大量的“剩男剩女”问题。

在再平衡过程中,所有的消费者实例共同参与,在协调者(Coordinator)的协助之下,完成订阅主题的分区的分配。这里的协调者专门为消费者组服务,主要负责执行再平衡、提供位移管理以及组成员管理等,它的角色很像月老,只要涉及到再平衡的方方面面,事无巨细,它都要插手,可以说是个很尽职的月老了。

每一个消费者组都有一个对应的协调者,这个协调者位于 Broker,所有的 Broker 在启动时,都会创建和开启相应的协调者组件。在消费者启动的时候,会向消费者组对应的协调者所在的 Broker 发送各种请求,然后由协调者负责执行消费者组的注册、成员管理记录等元数据管理操作。 当消费者在提交位移的时候,实则也是在向协调者所在的 Broker 提交位移。

2. 再平衡到底做了什么“恶”?

“万恶“的再平衡到底做了什么”恶“?为什么会这么招人恨呢?

如果再平衡能够好好做它的月老,每天「千里姻缘一线牵」,成全消费者和分区的好事,那它就真的是功德无量了。凡事就怕一个但是,但是呢,再平衡本身有许多臭毛病,让人哭笑不得。

第一,再平衡的过程会停止消费者组所有成员的消费过程。在再平衡的过程中,所有的消费者实例全部都会停止消费,stop the world(STW),直到再平衡完成,才会恢复消息进程,这个过程会对消费端的 TPS 造成很大影响。

其次,再平衡的过程效率很低。再平衡的时候所有的消费者实例均需要参与其中,全部重新分配所有分区。这个过程实则做了许多无用功,实际上,尽量减少各个消费者实例所分配的分区的变动才是最优的方案,而不是全部重新洗牌,这样的话,就会节省许多资源和时间。例如,消费者1原本负责消费分区1、2、3,那么再平衡之后也应该接着消费1、2、3,而不是重新分配其他分区,这样的话,消费者1连接这些分区所在的Broker的 TCP 连接就可以继续用了,不仅节省了资源,也减少了时间。局部性原理对提升系统性能特别重大。

最后,再平衡的速度实在是太慢了。上百个消费者实例的消费者组再平衡一次很可能需要几个小时,这是难以忍受的。

值得注意的是,社区提出了有粘性的分区分配策略,利用局部性原理解决再平衡过程中的效率问题。每次再平衡的过程中,该策略会尽可能保留之前的分配方案,尽量实现分区分配的最小变动。

那到底是谁打开了潘多拉魔盒,释放出再平衡的恶呢?

3. 是谁引爆了再平衡“恶魔”的导火索?

消费者组满足某些特定的条件的时候,就会触发再平衡。一般来说,触发再平衡的条件有以下三个:

  • 消费者组成员数发生变更。当消费者组中有新的消费者实例加入或者是有消费者实例由于崩溃而离开消费者组,都会触发再平衡。这是由于,此时消费者组中的消费者数量发生变化,与分区的对应关系发生了改变,因此需要进行再平衡重新分配。
  • 消费者订阅的主题发生变更。消费者订阅的主题的名称或者主题的数量发生变化都会触发再平衡,由于主题的变更打破了消费者与分区的对应关系,需要进行再平衡重新分配。
  • 订阅主题的分区数发生变化。和订阅主题的变更一样,订阅主题的分区数的变化打破了消费者与分区的分配关系,需要进行再平衡重新分配。

其中后面两个再平衡的触发条件是不可避免的,而且这两个条件的发生频率并没有那么高,也不太会造成一些比较难以追踪的 bug,所以实则这两个条件可以不做处理。

而消费者组成员数的变化引发再平衡则是超级常见的。当消费者实例增加的时候,一般都是我们启动一个新的具有一样 GroudID 的消费者实例入组的时候。此时,协调者将会执行再平衡,为消费者组重新分配分区。一般来说,增加消费者主要是为了提高消费者组的消费能力,因此这种情况实则也是不需要处理的。

但是,系统中某个消费者实例可能由于某些意外的情形而被协调者认为该消费者已经挂掉了,从而离开消费者组,这种情况实则我们是需要去避免的,由于消费者实例的减少会降低消费端的消费能力,而且这种情况往往还伴随着必定的异常和错误,因此这种情况我们是需要极力避免的。

4. 如何避免不必要的再平衡呢 ?

不必要的再平衡都有那些情况呢?

第一类不必要的再平衡是由于没有消费者的心跳线程没有及时发送心跳给协调者,导致协调者认为该消费者已经挂掉了,所以将其踢出消费者组。这里所谓的及时是指发送心跳的时间周期需要限制在消费者参数 session.timeout.ms (默认值 10 s)规定的时间范围内,即每隔session.timeout.ms 规定的时间需要发送一次心跳。除此之外,消费者端参数 heartbeat.interval.ms 允许用户控制发送心跳请求的频率,这个值越小,消费者发送心跳请求的频率就越高。尽管频繁的发送心跳请求会消耗必定的网络带宽,但是却能更加快速地知道当前是否需要再平衡剔除不可用的消费者。

合理设置这两个参数可以避免此类再平衡,以下是一些推荐参数:

  • 设置 session.timeout.ms = 6s
  • 设置 heartbeat.interval.ms = 2s
  • 要保证 Consumer 实例在被判定为“dead”之前,能够发送至少 3 轮的心跳请求,即 session.timeout.ms >= 3 * heartbeat.interval.ms。

第二类非必要 Rebalance 是 Consumer 消费时间过长导致的。这里的消息时间过长实则指的主要是消息处理的时间过长。消息处理时间过长会导致两次 poll() 方法之间调用的时间过长,当这个时间超过 max.poll.interval.ms(默认值 5 分钟) 规定的时间之后,消费者就会主动发起离组的请求,协调者就会发起新的一轮再平衡。因此,如果消息处理的过程时间超级长,那就需要将这个参数设置的大一点,比消息处理的时间稍微长一点,这样就不会由于消息处理时间过长而导致再平衡了。

除了以上这两种情况,实则消费者如果出现频繁的 Full GC 也会导致非预期的再平衡,因此排查问题的时候也需要思考一下这种情况。

5. 总结

作为一个“月老”,再平衡为分区和消费者组的消费付出了太多,不过,再平衡的坏脾气却让它屡屡犯错,频繁的可以避免的再平衡严重地影响了消费者的消费能力。而深入理解再平衡触发的条件和原理能够让我们更好地理解再平衡,所谓「打蛇打七寸」,抓住再平衡发生的命脉,我们就可以很好的解决那些可以避免的再平衡,从而提高消费者组的消费能力。

© 版权声明

相关文章

5 条评论

您必须登录才能参与评论!
立即登录
  • 头像
    闻卿82_26 投稿者

    几百个消费者场景再平衡为什么要几个小时?都干什么了?

    无记录
  • 头像
    愚人节生的真的 读者

    就是一个实际案例

    无记录
  • 头像
    沙培俊 读者

    pause+resume+拉取/消费线程分离,是彻底解决这种问题的终极手段,spring就是这么干的,你的那套靠调参的方法,不通用的

    无记录
  • 头像
    能吃的时候就多吃点糖分 读者

    哇,感谢提出,我去学习一下。不过本文的目的主要在于确保避免基本的一些再平衡的情况,确保使用是正确的。水平所限,有不对的地方或者有更好的方案,非常感谢拍砖提出,我会进一步学习,您的建议是促进我成长非常好的方式。我去研究一下Spring是如何做的,再次感谢~

    无记录
  • 头像
    桃川富子 读者

    收藏了,感谢分享

    无记录