「kafka golang客户端sarama源码分析」--- 2. 从ConsumerGroupSession到ConsumerGroupClaim

写在前面

上一章中初步介绍了sarama consumergroup的结构。从本章开始,我们循序渐进,开始深入其底层的各个组件。本章先介绍从ConsumerGroupSession到ConsumerGroupClaim的工作原理。

调用时序

以上是具体的调用时序,这里不做过多介绍,下面根据时序图开始逐步查阅代码。

ConsumerGroup的创建

ConsumerGroup 实例的创建流程非常简单。这里以上篇中提到的官方 Consumergroup 模版代码中的执行顺序进行分析。

ConsumerGroup 实例通过 NewConsumerGroup 方法创建,在创建 ConsumerGroup 实例前会首先传入 Kafka集群Broker的地址,创建 Client 实例。Client 部分会在后续篇章中介绍。

创建完成 Client 实例后,即调用 newConsumerGroup 创建 ConsumerGroup 实例。

在 newConsumerGroup 方法中,首先会创建 Consumer 实例,Consumer 部分会在后续篇章中介绍。

接下去将前面创建好的 Client 和 Consumer 实例作为 ConsumerGroup 实例的成员变量,进行赋值。至此 ConsumerGroup 实例就完成创建了。

ConsumerGroupSession的初始化

ConsumerGroupSession 作为管理消费者组消费和rebalance的组件,它在 ConsumerGroup 实例的 Consumer 方法中进行初始化。其初始化流程大致有以下步骤

调用newSession

在调用 newSession 之前,会先调用 RefreshMetadata 来获取消费者订阅的 Topics 列表中各个主题的元数据,元数据中包含每个主题的分区信息,和每个分区的 leader 副本和 follower 副本信息。这里主要是为了 Client 实例对 Broker 实例进行创建和管理。这部分会在后续篇章中介绍。

在调用元数据刷新后,就开始进入 newSeesion 方法执行 ConsumerGroupSession 的初始化工作。

我们能看到,在 newSession 执行完成后,Consumer 方法会阻塞监听 ConsumerGroupSession 实例的 ctx 取消信号。也就是说,用户在调用 Consumer 方法成功后,整个消费者端进程就会开始阻塞。后续就由 Consumer 方法入参中的 handler(ConsumerGroupHandler interface 的实现类型的对象)执行回调方法,完成消费者端的业务逻辑。

消费者加入Kafka消费者组

在 newSession 中,首先会执行的步骤,是为消费者组中的消费者做 Partitions 的分配,这个过程遵循 Kafka 的消费者加入协议,分为三步:

  • 调用Coordinator:通过消费者组ID,获取消费者组所属的 Coordinator 信息,Coordinator 是 Kafka集群的某个Broker。对于 sarama 来说,这里返回 Coordinator 其实就是一个 Broler 实例。前面提到过,sarama 中的 Broker 实例就是用来维护管理与Kafka集群的某个Broker的TCP链接的。
  • 执行join和sync,将当前消费者加入消费者组。这里不过多赘述, sarama 完全按照 Kafka 的消费者加入协议进行消费者入组。

创建 seesion 实例

在 newSession 完成消费者加入后,下面调用 newConsumerGroupSession 方法。真正开始创建 ConsumerGroupSession 实例。

在完成 ConsumerGroupSession 实例的创建后,会开启一个 goroutine,维护对 Kafka集群的保活心跳和感知集群下发的 rebalance 信息。这两个任务由 heartbeatLoop 方法实现。

完成心跳配置后,开始初始化当前消费者订阅的各个 Topic/Partition 对的 offset 管理模块的初始化。这里的订阅信息存储在 claims 变量中。这个变量是在消费者完成加组后根据 Kafka 服务端返回的当前消费者的 Topic/Partition 订阅信息生成而来的。

Setup回调和ConsumerGroupClaim创建

接下来,会执行用户测的 Setup 回调,用以告知业务逻辑,消费者已加入成功,可以开始准备拉取消息。

执行完成 Setup 后,根据之前存储在 claims map变量中的 Topic/Partition 信息,来为每个 Topic/Partition 订阅去创建 ConsumerGroupClaim 实例,这个过程在 consume 方法中完成。上一章中曾提到,ConsumerGroupClaim 实例是一个类似信使的角色,在 ConsumerGroup和用户层中,传递从消费者拉取到的消息供用户处理的。

这里可以看到,为每个 Topic/Partition 订阅创建 ConsumerGroupClaim 实例是通过 goroutine 完成的。也就是说,后续 ConsumerGroupClaim 实例对 partitionConsumer 的管理以及更下层的消息 pull 动作都是通过 goroutine 异步完成的。

ConsumerClaims回调

在 consume 方法中,首先完成 ConsumerGroupClaim 实例的创建,然后启动一个 goroutine 来监听会话层和用户层各自的 context 取消信号,来保证在会话或者消费者进程退出时,ConsumerGroupClaim 实例与其下层的工作组件可以停止消费。

接下去,就是调用用户实现的 ConsumerClaims 回调方法,回调方法的入参中包含属于当前 Topic/Partition 订阅的 ConsumerGroupClaim 实例,ConsumerGroupClaim 实例中提供一个 Messages 方法,会返回一个带有消息的 channel。用户层可以持续监听这个 chennel,来获取消息进行业务处理。

ComsumerGroupSession的退出

上面我们分析了 ComsumerGroupSession 的创建流程,接下来,看看sarama如何通过 context 上下文来控制 ComsumerGroupSession 实现优雅退出。这里主要介绍两条控制路径。

用户层主动退出

第一步:当用户层代码完成 ComsumerGroup 实例创建和 Consumer 方法的调用后,会在一个循环内持续监听 context 取消信号和操作系统信号,当两者任一信号到来时,退出循环。直接调用 context 的 cancel 函数。

第二步:经过以上分析我们知道,Consumer 方法中在成功调用 newSession 方法后,会监听 ComsumerGroupSession 实例中通过用户层 context 派生而来的一个属于会话层的 context。由 context 原理可知,此时 <-sess.ctx.Done() 同样也会结束阻塞。当阻塞停止后,会调用 sess.release 方法。该方法中会通过 channel 机制关闭 heartbeatloop 协程。并且通过 WaitGroup 机制等待 ComsumerGroupSession 实例下多个的 ConsumerGroupClaim 实例全部退出。release 方法代码逻辑较为简单,不做过多赘述。

第三步:ConsumerGroupClaim 实例创建完成并开始 ConsumerClaims 回调前,会创建一个 goroutine,监听 第二步中 session 实例的 context 取消信号。因此,在用户层的 context 取消后,此处也同样感知到取消,从而跳出 select 监听。转而去调用 AsyncClose 方法,这个方法会控制所有 ConsumerGroupClaim 实例和它们的下层组件退出消费。

消费者rebalance

当出现消费者rebalance时,heartbeatLoop 协程会调用 ComsumerGroupSession 实例的 context 取消函数。前叙中第二步第三步的相应操作也会被触发,退出当前消费者组会话。

总结

本篇分析了从 ConsumerGroupSession 到 ConsumerGroupClaim 的工作流程,下一篇分析 partitionConsumer 和 brokerConsumer 组件的工作原理。

相关推荐
码拉松1 小时前
千万不要错过,优惠券设计与思考初探
后端·面试·架构
白总Server1 小时前
MongoDB解说
开发语言·数据库·后端·mongodb·golang·rust·php
计算机学姐2 小时前
基于python+django+vue的家居全屋定制系统
开发语言·vue.js·后端·python·django·numpy·web3.py
程序员-珍2 小时前
SpringBoot v2.6.13 整合 swagger
java·spring boot·后端
海里真的有鱼2 小时前
好文推荐-架构
后端
骆晨学长3 小时前
基于springboot的智慧社区微信小程序
java·数据库·spring boot·后端·微信小程序·小程序
AskHarries3 小时前
利用反射实现动态代理
java·后端·reflect
Flying_Fish_roe4 小时前
Spring Boot-Session管理问题
java·spring boot·后端
hai405874 小时前
Spring Boot中的响应与分层解耦架构
spring boot·后端·架构
Adolf_19936 小时前
Flask-JWT-Extended登录验证, 不用自定义
后端·python·flask