4.1.2 消费者轮询的准备工作


4.1.2 消费者轮询的准备工作

客户端的消费者与服务端的协调者通信通过消费者的协调者类完成,在拉取消息前有两个ensure()方法:第一个是ensureCoordinatorKnown()方法,确保客户端已经连接上协调者;第二个是ensureParti.ti.onAssi.gnment(),确保消费者收到协调者分配给它的分区,如果消费者没有分配到分区,就无法拉取消息。客户端在第一步必须先连接上协调者,才能调用第二步从协调者获取分区,否则第二步就无法执行。

**注意:**这里有两个“协调者”对象,消费者客户端的协调者(ConsumerCoordinator)和服务端的协调者(GroupCoordinator)。消费者会通过ConsumerCoordi.nator类发送请求给服务端的协调者节点去处理。就像在RPC中存在服务端和客户端一样,客户端持有一个服务端的引用或代理对象,并通过该代理对象调用服务端的方法。

上面假设消费者分配到分区,不过对于订阅模式,消费者的分区是由协调者分配的,拉取消息之前一定要确保存在协调者,并且协调者也成功地为当前消费者分配了分区。消费者能够分配到分区的条件是要加入到消费组中。这些事件发生的顺序为:消费者向协调者申请加入消费组,服务端存在管理消费组的协调者,协调者将消费者加入消费组,协调者为所有消费者分配分区,消费者从协调者获得分配给它的分区,消费者拉取分区的消息。

消费者投取消息的准备工作有:连接协调者,向协调者发送请求加入消费组,从协调者获得分配的分区。其中连接的协调者节点跟上一章中消费者的偏移量管理器类似,“消费者提交偏移盘”给管理消费组的协调者,与这里“消费者连接”管理消费组的协调者本质上是一样的。属于同一个消费组的所有消费者连接的协调者都是同一个节点,不管是提交偏移量操作,还是发觉与消费组相关的请求。

  1. 发送请求井获取结果的几种方案

每个消费者都要向协调者发送“加入消费组的请求”(Joi.nGroupRequest),协调者才会知道消费组有哪些消费者,才可以执行全局的分区分配算法。每个消费者从“加入消费组的响应”(Joi.nGroupResponse)可以获取到分配给它的分区。消费者什么时候分配到分区由协调者决定,如果协调者的分配算法还没有执行完,消费者就不能得到分配分区。一种比较复杂的获取分区流程如下。

  • 消费者发送“加入消费组的请求”,向协调者申请加入消费组。
  • 协调者接受消费者加入消费组。
  • 协调者等待消费组中其他消费者也发送“加入消费组的请求”。
  • 协调者执行分区分配算法为所有消费者分配分区。
  • 消费者再次发送获取分区的请求。
  • 协调者返回分区给消费者。

上面的流程中步骤(5)执行的时机不好控制,因为消费者请求获取分区不一定就能得到结果。可能协调者的计算工作还没有完成,消费者就得等待,或隔段时间再请求。一种比较简单的做法是:消费者发送“加入消费组”的请求,协调者直接返回属于这个消费者的分区。也就是说,没有了步骤(5),消费者只需要发送一次请求,不需要再次发送新的请求来获取分区结果。下面的伪代码采用第二种方式获取分区:
在这里插入图片描述

上面的做法采用同步的模式:消费者发送完请求会一直等待,直到收到响应结果。但实际上协调者为了保证同一个消费组的所有消费者都能分配到分区,要等待所有消费者都加入消费组后,才开始执行分区分配算法。所以消费者发送请求后返回的是一个带有“加入消费组的响应”的异步对象(F川Ure),表示响应结果会在未来的某个时刻返回给消费者客户端。客户端得到异步对象,可以直接调用Future.get()方法阻塞式地等待,或者用一个循环/轮询判断异步对象是否完成,只有完成时才需要获取响应结果。下面的伪代码展示了获取响应结果的几种用法,Kafka采用了最后一种,它和第三种方法类似,它的第二行语句相当于循环轮询(详见4.2.3节):
在这里插入图片描述

  1. 消费者加入消费组
    消费者通过客户端的协调者调用ensureActi.veGroup()方法加入一个消费组需要3步:准备阶段调用。nJoi.nPrepare()方法,发送“加入消费组的请求”阶段调用sendJoi.nGroupRequest()方法,“成功加入消费组”阶段获取到分区调用onJoi.nCoP!plete()方法。消费者只有在再平衡时才需要重新加入消费组,可以用一个外部的布尔变量rejoi.nNeeded来控制是否需要发送“加入消费组的请求”,代替上面第三种方法判断异步对象是否完成。消费者在重新加入消费组成功后,会设置布尔变量为false,这样下次轮询时就不需要再次发送“加入消费组”的请求了。相关代码如下:
    在这里插入图片描述

注意:消费组第一次发送“加入消费组”的请求时指定成员编号为UNKNOWN_MEMBER_ID,因为它还没有真正加入消费纽过。当消费者第一次成功加入消费组后,协调者除了返回分配给消费者的分区,还会返回给消费者一个实际的成员编号。后续如采消费者需要再次加入消费组,比如再平衡发生时,消费者发送“重新加入消费组”的请求,需要指定实际的成员编号。

消费者的协调者继承了抽象的协调者父类(AbstractCoordlnator),消费者连接协调者、发送“加入消费组”的请求、获取分区的逻辑都定义在抽象父类中。发送请求前的准备阶段也使用了-个布尔变filneedsJoi.nPrepare控制,初始时为true,调用onJoi.nPrepare()后设置为false,表示准备阶段已经完成。调用onJoi.nComplete()重置为true,为下次重新加入做准备。相关代码如下:
在这里插入图片描述

注意:就像旧版本的消费者和备份副本都会从主副本拉取消息一样,对应存在两种类型的拉取线程,抽象的拉取线程中定义了拉取消息的基本流程:构建拉取请求、发送拉取请求、处理分区结果。

下面以消费者调用两次轮询为例说明确保分配到分区的执行流程,第一次轮询步骤如下。

  • 初始needsJoinPrepare和rejoinNeeded都为true,消费者启动时默认要加入消费组。
  • 满足needRejoin(),先调用onJoinPrepare()做准备工作,比如提交偏移量等。
  • 准备工作完成后修改needsJoinPrepare=false,防止加入消费组完成前多次执行准备工作。
  • 满足needRejoin(),执行循环体:发送请求,调用一次客户端轮询尝试获取结果。
  • 消费者分配到分区,更新rejoinNeeded为false,并重置needsJoinPrepare为true。

如图4-8所示,在第一次轮询的最后一步中,onJoinComplete()方法在消费者成功加入消费组井分配到分区后,会更新订阅状态的needsPartitionsAssigned为false,这个变量也会被消费者用来判断是否需要重新加入消费组。如果它的值为false,表示消费者已经分配到分区了,不需要再次加入消费组(除非再平衡时)。因正在第二次轮询时,ensurePartitionAssignment()方法会立即返回,不会调用ensureActiveGroup()。

在这里插入图片描述

抽象协调者的needRejoin()方法是判断客户端是否需要重新加入消费组的依据,抽象类中这个方法取rejoinNeeded变量的值。消费者的协调者对该方法的实现,是取订阅状态needsPartitionAssignment的值。所以不管是在抽象类中设置rejoinNeeded=false,还是在消费者的协调者中设置needsPartitionAssignment=false,都表示消费者不再需要重新加入消费组。当抽象类的rejoinNeeded为true,或者消费者的协调者needsPartitionAssignme

在高级API中,消费者启动时向ZK注册不同事件的监昕器(比如分区变化、消费者成员变化、会话超时),当注册的事件发生时会触发ZKRebalanceli.stener的再平衡操作。新API的再平衡控制策略采用变量的方式,相比ZK监昕器没有额外的通信开销,直接在内存中修改变量,不需要和其他组件交五,虽然看起来很原始,却很高效。另外,由于协调者的消费组管理协议比高级API复杂,我们会在下一章分析客户端和服务端的协调者实现,这里只需要知道消费者向协调者发送Joi.nGroup请求后,可以接收到分配的分区。下面来看消费者轮询中的其他工作。

相关推荐
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页