书接上回,实际上,消费者提交偏移量如果存储在ZK 中,也是用消费组级别来表示。存储在ZK 中天生就具有共享存储的优势,所有的消费者只需要连接ZK 即可。而以主题方式存储偏移量时,就得考虑是否需要连接多个服务端节点。每个消费组只连接一个节点是最好的,这个节点负责管理一个消费组所有消费者所有分区的偏移量, 叫作偏移量管理器( OffsetManager)。和采用ZK方式将偏移量数据写到ZK不同,消费者将偏移量数据封装成偏移量提交请求( OffsetCommitRequest )发送给偏移量管理器。就像生产者的生产请求、消费者的拉取请求一样,偏移量提交请求和偏移量获取请求都是发送给Kafka服务端节点的。

 

如图3-25所示,总结一下目前为止客户端需要确定服务端节点的几个场景。

1.生产者发送消息时,直接在客户端决定消息要发送给哪个分区,这一步不向服务端发送请求。
2.消费者拉取管理器的LeaderFinderThread线程向服务端发送主题元数据请求,获取包含了主副本等信息的所有分区元数据,消费者拉取线程才能确定要连接哪些服务端节点。
3.提交偏移量虽然有点像生产者的发送消息,都是写数据,但也需要和l 消费者的LeaderFinderTherad一样,获取分区的主副本作为偏移量管理器,才能确定提交到哪个节点。

kafka偏移量优化 kafka偏移量管理_协调者

连接偏移量管理器:

拉取偏移量方法和提交偏移量方法,都需要和偏移量管理器通信。在这之前,消费者需要通过channelToOffsetManager() 方法向服务端任意一个节点发送“消费组的协调者请求”( GroupCoordinatorRequest),来获取消费组对应的协调节点,即偏移量管理器( OffsetManager)节点。服务端处理消费组的协调者请求,实际上也是通过查询主题的元数据来完成的。不过和LeaderFinderThread中返回主题元数据,然后还要在客户端继续处理( 比如获取存在主副本的分区)不同,这里在服务端完成“选择消费组对应内部主题的分区的主副本节点”,然后直接返回这个协调节点给客户端。也就是说客户端发送消费组的协调者请求,服务端返回的就是消费组的协调节点。

如图3-26所示,消费组1中所有消费者提交的偏移量都应该连接到代理节点1,但是消费组中不同消费者连接的任意代理节点可能一开始并不是代理节点1 。不过没关系,这一步只是准备工作,目的是确定目标节点,不管连接哪个节点,当前连接的节点都会告诉你应该连接的正确节点;如果你连得不对,根据返回值再去连接正确的节点。比如,消费者0 刚好连接的是代理节点1,可以直接把queryChannel作为offsetChannel ;而消费者1 和消费者2第一次连接的不是代理节点1 , 所以在得到结果时应该首先关闭queryChannel ,然后重新连接代理节点1作为queryChannel。

kafka偏移量优化 kafka偏移量管理_kafka_02

服务端处理提交偏移量的请求:

协调节点会将消费者的偏移量提交请求交给GroupCoordinator类的handleCommitOffsets () 方法处理,其中参数off setMetadata表示分配给消费者的所有分区消费进度。

kafka偏移量优化 kafka偏移量管理_kafka_03

写入偏移量消息会调用RepllcaManager.appendMessages () 方法,将消息集追加到本地日志文件,并且会把分区和对应的偏移量保存在协调节点的缓存中。目的是:再平衡后如果其他消费者需要读取分区的偏移量, 在连接上协调节点后,可以直接读取缓存, 而不需要从日志文件中读取。

如图3-27 所示, 消费者发送提交偏移量和获取偏移量都会被服务端的KafkaApis处理,服务端处理这两个请求的具体步骤如下。

(1)  KafkaApis将提交偏移量请求的处理交给消费组的协调者( GroupCoordinator)。
(2)消费组的协调者再交给消费组的元数据管理类( GroupMetadataManager)去处理。
(3)延迟的存储对象( DelayedStore )会调用副本管理器的appendMessages ()存储消息。
(4)副本管理器将消息追加到底层文件系统的日志文件中,这样分区的偏移量就存储到服务端了。
(5)分区和对应的偏移量会在消息存储成功后,被缓存至服务端的消费组元数据管理类。
(6)服务端处理客户端的获取分区偏移量请求, 会首先从缓存中获取。
(7)如果缓存中没有分区的偏移量, 就从日志文件中读取。

kafka偏移量优化 kafka偏移量管理_协调者_04

缓存分区的偏移量:

消费者提交自己负责分区的偏移量,除了写入服务端(协调节点)内部主题某个分区的日志文件中,还要把这部分数据保存一份到当前服务端的内存中,这样分区的偏移量保存在了磁盘和内存两个地方。偏移量消息的键由消费组、主题、分区组成( G roupTopicPartition),消息的值是分区的偏移量。查询分区的偏移量时给定GroupTopicPartition , 会返回分区对应的偏移量, 即分区当前的消费进度。

如图3-29 所示,偏移量请求和消费组有关,客户端只能连接指定的节点,所以是协调节点独享的缓存。而主题元数据( TopicMetadata )和消费组的协调者(GroupCoordinator)因为在每个服务端节点保存的数据都一样,可以请求任何一个节点,所以是所有节点共享的缓存。

kafka偏移量优化 kafka偏移量管理_协调者_05

 

 

再平衡和分区分配:

使用高级API ,每个消费者进程启动时都会创建一个消费者连接器,并在ZK中注册消费者成员变化、分区变化的监昕器。三旦监昕器注册的事件被触发,就会调用ZKRebalancerListener的再平衡方法,为消费组的所有消费者重新分配分区。为了保证整个消费组分区分配算法的一致性,当一个消费者触发再平衡时,该消费组内的所有消费者会同时触发再平衡。如图3-34 (左)所示,第一个消费者加入消费组触发再平衡,这时消费组只有一个消费者,所有的分区都分配给第一个消费者。如图3-34(右)所示,第二个消费者加入同一个消费组,会触发所有消费者的再平衡,即第一个消费者和第二个消费者都会再平衡。

 

 

kafka偏移量优化 kafka偏移量管理_消息队列_06

如图3-35所示,消费者执行再平衡和提交偏移量都直接和协调者交互,具体步骤如下。
(1)每个消费者触发再平衡时都和协调者联系,由协调者执行全局的分区分配。
(2)协调者分配完成后,将分区分配给每个消费者。
(3)每个消费者收到任务列表后,启动拉取钱程,拉取对应分区的消息,并更新拉取状态。
(4)消费者周期性提交分区的偏移量给协调者,协调者将分区偏移量写到内部主题。

kafka偏移量优化 kafka偏移量管理_kafka_07

消费者加入消费组:

下面把ensureActiveGroup ()方法中执行条件相关的部分(即判断是否需要重新加入消费组)都去掉,只保留“加入组请求”相关的业务逻辑, 主要步骤如下。
(1)消费者加入消费组之前,需要做一些准备工作,比如同步提交一次偏移量,执行监昕器的回调。
(2)消费者创建“加入组请求”,包括消费者的元数据作为请求的数据内容。
(3)消费者发送“加入组请求”,采用组合模式返回一个新的异步请求对象,并定义回调处理器。
(4)客户端通过轮询,确保组合模式返回的异步请求必须完成,这是一个阻塞的方法。
(5)异步请求完成后,执行回调方法,将分区设置到消费者的订阅状态,并重置心跳定时任务。

发送“加入组请求”:

如果是协调者负责分区的分配工作,消费者发送完“ 加入组请求”后,就可以从“加入组响应”中获取到分配给它们的分区。那么可以在发送“加入组请求”返回的异步请求对象上,使用组合模式加上一个适配器。在适配器的回调方法中,解析“加入组响应”中分配的分区,完成异步请求对象,最后协调者就可以获取异步请求的结果,这个结果就是分配给消费者的分区。

发送“同步组”请求:

普通消费者在收到“加入组响应结果”后,会立即发送“同步组请求”给协调者。而主消费者在收到“加入组响应结果”后,会从“加入组响应结果”中获取执行分区分配过程中需要用到的数据,然后调用performassignment()执行分区分配。只有这个执行过程完成后,主消费者才会开始发送“同步组请求”给协调者。

“加入组响应处理器” 的回调方法调用时, 表示消费者收到“加入组响应结果”,但“加入组”的异步请求还没有完成。“ 同步组响应处理器” 的回调方法调用时,表示消费者收到“同步组响应结果”由于“同步组响应结果,, 表示的数据就是分配给消费者的分区信息,所以可以完成“同步组”的异步请求, 并一起完成了“ 加入组”的异步请求。这时消费者读取“加入组”异步请求的结果就是分配给消费者的分区信息。如图5-3所示, 消费者获取协调者的分配结果,总体上可以分为下面4个步骤。

(1) 每个消费者都发送“加入组请求”给协调者节点。
(2)协调者收到所有消费者发送的“加入组请求”,返回“ 加入组响应”给每个消费者,还会将执行分区分配算法需要的数据(比如消费者成员列表)传给主消费者。
(3)主消费者执行完分区分配算法后,将“分配结果”通过“同步组请求” 的方式发送给协调者节点。其他消费者也会发送“ 同步组请求”给协调者,但是它们的请求中并没有“分配结果”数据。
(4) 每个消费者从“ 同步组响应”的结果数据中获取到分配给它们的分区。

kafka偏移量优化 kafka偏移量管理_kafka偏移量优化_08