Kafka核心技术与实战 <三>

536 阅读11分钟

其他更多java基础文章:
java基础学习(目录)


这系列是根据极客时间《Kafka核心技术与实战》这个课程做的笔记

本篇目录

  • 消费者组
  • 避免消费者Rebalance
  • 消费者组重平衡全流程解析

消费者组

Consumer Group :Kafka提供的可扩展且具有容错性的消息者机制。

重要特征

  • 组内可以有多个消费者实例(Consumer Instance)。
  • 消费者组的唯一标识被称为Group ID,组内的消费者共享这个公共的ID。
  • 消费者组订阅主题,主题的每个分区只能被组内的一个消费者消费
  • 消费者组机制,同时实现了消息队列模型和发布/订阅模型。
  • 消费者组中的实例个数,最好与订阅主题的分区数相同,否则多出的实例只会被闲置。一个分区只能被一个消费者实例订阅。

消费者组的位移管理方式

  • 对于Consumer Group而言,位移是一组KV对,Key是分区,V对应Consumer消费该分区的最新位移。
  • Kafka的老版本消费者组的位移保存在Zookeeper中,好处是Kafka减少了Kafka Broker端状态保存开销。但ZK是一个分布式的协调框架,不适合进行频繁的写更新,这种大吞吐量的写操作极大的拖慢了Zookeeper集群的性能。
  • Kafka的新版本采用了将位移保存在Kafka内部主题__consumer_offsets的方法。

消费者组的重平衡

重平衡:本质上是一种协议,规定了消费者组下的每个消费者如何达成一致,来分配订阅topic下的每个分区。

触发条件:

  • 组成员数发生变更
  • 订阅主题数发生变更
  • 定阅主题分区数发生变更

Rebalance 的设计是要求所有consumer实例共同参与,全部重新分配所有用分区。并且Rebalance的过程比较缓慢,这个过程所有 Consumer 实例都会停止消费,等待 Rebalance 完成。

避免消费者Rebalance重平衡

什么是重平衡

  • 让一个Consumer Group下所有的consumer实例就如何消费订阅主题的所有分区达成共识的过程。
  • 在重平衡过程中,所有Consumer实例共同参与,在协调者组件的帮助下,完成订阅分区的分配。
  • 整个过程中,所有实例都不能消费任何消息,因此对Consumer的TPS影响很大

为什要避免重平衡

  • Rebalance影响Consumer端的TPS,因为重平衡过程中消费者不能消费消息
  • Rebalance很慢,如果有数百个消费者实例,整个过程耗时可能达到几个小时
  • Rebalance效率低,这个过程是全员参与,通常不考虑局部性原理,但局部性原理对系统性能提升特别重要。
  • 真实的业务场景中,很多Rebalance都是计划外或是不必要的。

何时会触发重平衡

  • 组成员数量发生变化
  • 订阅主题数量发生变化
  • 订阅主题分区数发生变化。

要避免哪些重平衡

最常见的是消费者数发生变化触发的重平衡,其他的重平衡是不可避免的,但消费者数量变化是可避免的

  • Consumer实例增加
    当启动一个配置相同的group.id值的consumer程序时,就是向这个组中增加一个消费者实例,这中秋情况一般是我们为了提升消费者端的TPS,是计划内的,所以也不用避免。
  • Consumer实例减少
    • 按计划的减少消费者实例,同样不用避免
    • 计划外的减少触发的重平衡才是我们要关注的。

如何避免重平衡

在某些情况下,Consumer实例会被Coordinateor错误地认为“已停止”,进而被踢出Group。这种情况导致的重平衡是需要避免的。

一、Consumer实例不能及时的发送心跳请求

当消费者组完成重平衡后,每个Consumer实例都会定期地向Coordinator发送心跳请求,如这个心跳请求没有被及时发送,Coordinator就 会认为该Consumer已经掉线,将其从组中移除,并开启新一轮重平衡。

解决
Consumer端设置:

  • Session.timeout.ms:默认为10秒,表示10秒内Coordinator没有收到Group下某个Consumer实例的心跳,就认为实例下线。这个可以适当的增大
  • heartbeat.interval.ms:控制发送心跳请求的频率,频繁的发送心跳请求会额外消耗带库资源。
  • max.poll.interval.ms:限定Consumer端应用程序两次调用poll方法的最大时间间隔。默认值是5分钟,表示如果Consumer程序在5分钟之内无法消费完poll方法返回的消息,那么consumer会主动的发起“离开组”的请求,从而导致重平衡。

建议:

session.timeout.ms=6s  
Heartbeat.interval.ms=2s  

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

二、Consumer消费时间过长

消费者端处理了一个很重的消费逻辑,耗时较长,导致Consumer端应用程序两次调用poll方法的时间超出设置的最大时间间隔。

解决:

  • max.poll.interval.ms参数设置较大一些
  • 优化消费者端业务逻辑,压缩消费耗时

三、GC影响

Consumer端的GC表现也会导致频繁的重平衡,频繁的Ful GC会导致长时间的断顿。

解决:
JVM调优。(查看参数优化那一节)

扩展知识-协调者Coordinator

所谓协调者,在 Kafka 中 对应的术语是Coordinator ,它专门为 Consumer Group 服务,负责为 Group 执行 Rebalance 以及提供位移管理和组成员管理等。
具体来讲,Consumer 端应用程序在提交位移时,其实是向 Coordinator 所在的 Broker 提交位移。同样地,当 Consumer 应用启动时,也是向 Coordinator 所在的 Broker 发送 各种请求,然后由 Coordinator 负责执行消费者组的注册、成员管理记录等元数据管理操作。
所有 Broker 在启动时,都会创建和开启相应的 Coordinator 组件。也就是说,所有 Broker 都有各自的 Coordinator 组件。那么,Consumer Group 如何确定为它服务的 Coordinator 在哪台 Broker 上呢?答案就在我们之前说过的 Kafka 内部位移主题 __consumer_offsets 身上。
目前,Kafka 为某个 Consumer Group 确定 Coordinator 所在的 Broker 的算法有 2 个 步骤。

  1. 确定由位移主题的哪个分区来保存该 Group 数据: partitionId=Math.abs(groupId.hashCode() % offsetsTopicPartitionCount)。
  2. 找出该分区 Leader 副本所在的 Broker,该 Broker 即为对应的 Coordinator。

简单解释一下上面的算法。

  1. 首先,Kafka 会计算该 Group 的 group.id 参数的哈希值。比 如你有个 Group 的 group.id 设置成了“test-group”,那么它的 hashCode 值就应该是 627841412。
  2. 其次,Kafka 会计算 __consumer_offsets 的分区数,通常是 50 个分区,
  3. 之后将刚才那个哈希值对分区数进行取模加求绝对值计算,即 abs(627841412 % 50) = 12。 此时,我们就知道了位移主题的分区 12 负责保存这个 Group 的数据。有了分区号, 算法的第 2 步就变得很简单了,我们只需要找出位移主题分区 12 的 Leader 副本在哪个 Broker 上就可以了。这个 Broker,就是我们要找的 Coordinator。

消费者组重平衡全流程解析

重平衡的通知机制

  • 重平衡过程通过消息者端的心跳线程(Heartbeat Thread)通知到其他消费者实例。
  • Kafka Java消费者需要定期地发送心跳请求到Broker端的协调者,以表明它还存活着。
    • 在kafka 0.10.1.0版本之前,发送心跳请求是在消费者主线程完成的,也就是代码中调用KafkaConsumer.poll方法的那个线程。这样做,消息处理逻辑也是在这个线程中完成的 ,因此,一旦消息处理消耗了过长的时间,心跳请求将无法及时发到协调者那里,导致协调者错判消费者已死。
    • 在此版本后,kafka社区引入了单独的心跳线程来专门执行心跳请求发送,避免这个问题。
  • 重平衡的通知机制是通过心跳线程来完成的,当协调者决定开启新一轮重平衡后,他会将“REBALANCE_IN_PROGRESS”封装进心跳请求的响应中,发还给消费者实例。当消费者实例发现心跳响应中包含了”REBALANCE_IN_PROGRESS”,就能立即知道重平衡开始了。
  • 消费者端的参数heartbeat.interval.ms的真实用途是控制重平衡通知的频率。

消费者组状态机

Kafka设计了一套消费者组状态机(State Machine),帮助协调者完成整个重平衡流程。

kafka消费者组状态

  • Empty: 组内没有任何成员,但消费者组可能存在已提交的位移数据,而且这些位移尚未过期。
  • Dead: 组内没有任何成员,但组的元数据信息已经在协调者端被移除。协调者保存着当前向它注册过的所有组信息,所谓元数据就是类似于这些注册信息。
  • PreparingRebalance: 消费者组准备开启重平衡,此时所有成员都要重新请求加消费者组
  • CompletingRebalance: 消费者组下所有成员已经加入,各个成员正在等待分配方案。
  • stable: 消费者组的稳定状态。该状态表明重平衡已经完成,组内成员能够正常消费数据了。

消费者组状态的转换

Kafka定期自动删除过期位移的条件就是,组要处于Empty状态。如果消费者组停了很长时间(超过7天),那么Kafka很可能就把该组的位移数据删除了。

消费者端重平衡流程

  1. 重平衡的完整流程需要消费者端和协调者组件共同参与才能完成。
  2. 在消费者端,重平衡分为两个步骤:
    1. 加入组,对应请求:JoinGroup请求
    2. 等待领导者消费者分配方案:SyncGroup请求
  3. 当组内成员加入组时,他会向协调者发送JoinGroup请求。在该请求中,每个成员都要将自己订阅的主题上报,这样协调者就能收集到所有成员的订阅信息。一旦收集了全部成员的JoinGroup请求后,协调者会从这些成员中选择一个担任这个消费者组的领导者。
  4. 通常情况下,第一个发送JoinGroup 请求的成员自动成为领导者。这里的领导者是具体的消费者实例,它既不是副本,也不是协调者。领导者消费者的任务是收集所有成员的订阅信息,然后根据这些信息,制定具体的分区消费分配方案。
  5. 选出领导者之后,协调者会把消费者组订阅信息封装进JoinGroup请求的响应中,然后发给领导者,由领导统一做出分配方案后,进入下一步:发送SyncGroup请求。
  6. 领导者向协调者发送SyncGroup请求,将刚刚做出的分配方案发给协调者。值得注意的是,其他成员也会向协调者发送SyncGroup请求,只是请求体中并没有实际内容。这一步的目的是让协调者接收分配方案,然后统一以SyncGroup 响应的方式发给所有成员,这样组内成员就都知道自己该消费哪些分区了。

重平衡场景剖析

A.新成员入组

当协调者收到新的JoinGroup请求后,它会通过心跳请求响应的方式通知组内现有的所有成员,强制他们开启新一轮的重平衡。(下图中的leader发送SyncGroup请求内容应该有误,应该是分配方案,下四图同理)

B.组成员主动离组

消费者实例所在线程或进程调用close()方法主动通知协调者他要退出。这个场景涉及第三类请求:LeaveGroup请求。协调者收到LeaveGroup请求后,依然会以心跳响应的方式通知其他成员。

C.组成员崩溃离组

崩溃离组是指消费者实例出现严重故障,突然宕机导致的离组。崩溃离组是被动的,协调者通常需要等待一段时间才能感知,这段时间一般是由消费者端参数session.timeout.ms控制的。

D.重平衡时协调者对组内成员提交位移的处理

正常情况下,每个组内成员都会定期汇报位移给协调者。当重平衡开启时,协调者会给予成员一段缓冲时间,要求每个成员必须在这段时间内快速地上报自己的位移信息,然后在开启正常JoinGroup/SyncGroup请求发送。