kafka学习笔记(十一) --- 消费者组重平衡能避免吗

在第八篇里,我们已经大概讲过重平衡的原理和用途,这里就直接进入主题。

  • 协调者( Coordinator)

在Rebalance过程中,所有消费者实例共同参与,在协调者帮助下完成订阅分区的分配。这个协调者是什么?在Akafka中协调者对应的术语是Coordinator,专门Consumer Group服务,负责为Group执行Rebalance以及提供提供位移管理和组成员管理等。

具体来讲,Consumer在提交位移时,其实是向Coordinator所在 Broker提交位移同样地,当Consumer启动时,也是想Coordinator所在Broker发送请求,然后由Coordinator负责执行消费者组的注册、成员管理记录等元数据的管理操作。所在Broker启动时,都会创建和开启相应的Coordinator组件。也就是说,所有Broker都有各自的Coordinator组件。那么,Consumer如何确认为她服务的Coordinator在那台Broker上呢?答案就在__consumer_offsets身上。

目前,Consumer Group确定Coordinator所在Broker的算法有两步:

第一步:确定由位移主体的哪个分区来保存Group数据:

               partitionId=Math.abs(groupId.hashCode() % offsetsTopicPartitionCount)。

第二步:找出该分区Leader副本所在Broker,该Broker即为Coordinator。

在实际使用过程中,Consumer应用程序,特别是Java Consumer API,能够自动发现并连接正确的Coordinator,我们不用操心这个问题。知晓这个算法的最大意义在于,能帮我们解决定位问题。当Consumer Group出现问题,需要快速排查Broker日志时,可以根据这个算法准确定位Coordinator对应的Broker,不必一台一台Broker地盲查。

  • 如何避免Rebalance?

虽然前面已经说过Rebalance,这里也先来简述一下,帮助回忆。

Rebalance的弊端:

  1. 影响Consumer端TPS,因为一旦Rebalance开始,所有Consumer实例就会停止消费。
  2. Rebalance很慢,如果你的组成员很多,就一定会有这个痛点。
  3. Rebalance效率不高,即所有Group都要参与重新分配分区,通常不会考虑局部性原理,但局部性原理会大大提升系统性能,因为它能最大限度地减少Rebalance对剩余Consumer的冲击。

关于第3点,这里多说一些。目前Kafka,在默认情况下,每次Rebalance时,之前的分配方案都不会被保留。基于这个原因,社区与0.11.0.0版本推出StickyAssignor,即有粘性的分区分配策略。所谓粘性,是每次Rebalance时,该策略会尽可能地保留之前的分配方案,尽量达到分区分配的最小变动。比较遗憾的是,这个策略还有一些bug,而且需要升级到0.11.0.0才能使用,在试驾生产环境中用的不多。

另外,比较遗憾的是,对于弊端的第1点和第2点,社区目前还没有好的解决方案,也就是无解的。虽然没办法解决这些问题,但是我们还是可以避免那些不必要的Rebalance。其实在真实的业务场景中,很多Rebalance都是计划外的,而客户端的TPS都是被这类原因拖慢的, 所以要尽量避免这类原因。

要避免Rebalance,首先要知道Rebalance发生的时机:

  1. 组成员数量变化;
  2. 订阅主题数量的变化;
  3. 订阅主题的分区数变化。

后面两个通常都是运维操作,一般难以避免,这里主要说说第1个时机该如何避免。一般碰到的Rebalance绝大部分都是第一个时机下发生的。Consumer实力增加的情况很好理解,当我们启动一个配置有相同group.id值的Consumer程序时,实际上就向这个Group添加一个实例。此时,Coordinator会接纳这个新实例,将其加入组中,并重新分配分区。通常来说,增加Consumer实例的操作都是计划内的,可能是出于增加TPS或提高伸缩性的需要。总之,它不属于我们要规避的那类“不必要Rebalance”。

我们更在意的是Group下实例数减少的情况,如果比就是要停掉某些Consumer实例,那自不必说,关键是在某些情况,Consumer实例会被Coordinator错误地认为“已停止”,从而被“踢出”Group。那么,Coordinator会在什么情况下认为实例已挂从而退其出组呢?

接下来了解一下Consumer提供了3个参数,它们都会影响Rebalance的发生:

  1. session.timeout.ms,默认值10秒,即如果Coordinator没在10秒内收到Group下某Consumer实例的心跳,就会认为这个实例已挂,从而将其移出Group,开启新一轮Rebalance。可以说,这个参数决定了Consumer存活性的时间间隔。
  2. heartbeat.interval.ms,这个值越小,Consumer实例发送心跳请求的频率越高。但是频繁地发送心跳,会额外消耗带宽资源,好处是能更快知晓当前是否开启Rebalance,因为Coordinator通知各个Consumer实例开启Rebalance的方法,就是将REBALANCE_NEEDED标志封装进心跳请求的响应体中。
  3. max.poll.interval.ms,限定了两次poll调用的最大时间间隔,默认值5分钟。表示你的Consumer程序如果无法在5min内无法消费完poll方法返回的消息,那么Consumer会主动发起“离开组”的请求,Coordinator就会开启新一轮Rebalance。

上面说的参数配置,对应使用过程中可能出现的三种“不必要的”Rebalance情况:

第一类,因为未及时发送心跳,导致Consumer被“踢出”Group而引发的。所以你需要仔细设置前面两个参数,这里有最佳实践,即session.timeout.ms=6s,heartbeat.interval.ms=2s,要保证实例被判定“dead”之前,能够发送至少3轮心跳请求,即session.timeout.ms >= 3 * heartbeat.interval.ms。

第二类,Consumer消费时间过长曹植的Rebalance。如某个用户,在他们的场景中,要将消息处理后的结果写入MongoDB,这里MongoDB的一丁点不稳定,都会导致COnsumer程序消费时长的增加。最好将这个参数设置成比你下游最大处理时长大一点。总之,你要为你的业务处理逻辑留下充足的时间。

如果按照上面的推荐数值恰当地设置了这几个参数,再去拍查一下Consumer端的GC表现,比如是否出现频繁的Full GC,从而引发Rebalance,这也是一种常见现象。

标注:这个系列文章是本人在极客时间专栏---kafka核心技术与实战中的学习笔记

    https://time.geekbang.org/column/article/101171

发布了37 篇原创文章 · 获赞 20 · 访问量 4954

猜你喜欢

转载自blog.csdn.net/qq_24436765/article/details/102562001