协调者处理不同消费者的“加入组请求”,由于不能立即返回“加入组响应”给每个消费者,它会创建一个“延迟操作”,表示协调者会延迟发送“加入组响应”给消费者 。 
但协调者不会为每个消费者的 “加入组请求”都创建一个 “延迟操作”,而是仅当消费组状态从“稳定”转变为“准备再平衡”,才创建一个“延迟操作”对象 。

(1)初始时消费组状态为“稳定”,第一个消费者加入消费组 。 因为消费组状态为“稳定”,所以协调者允许消费者执行再平衡操作 。协调者更改消费
       组的状态为“准备再平衡”,并创建一个延迟的操作对象 。

(2)消费组状态为“准备再平衡”,第二个消费者加入消费组 。 因为消费组状态为“准备再平衡”,所以协调者不允许消费者执行再平衡操作 。消费组状态仍然不变,
       协调者也不会创建延迟的操作对象。

“准备再平衡” 

  协调者处理消费者的“加入组请求”,如果消费者设置的成员编号未知,协调者会为这个消费者指定一个新的成员编号,然后创建消费者成员元数据,并加入到消
 费组元数据中 。如果消费者成员编号是已知的,说明消费组元数据中已经存在对应的消费者成员元数据,只需要更新已有的成员元数据。

  协调者为消费者分配的成员编号,会作为“加入组响应结果”返回给消费者。 消费者发送“同步组请求”时必须指定这个新分配的成员编号 ,这样协调者才能
知道成员编号对应的消费者元数据。 实际上 , 后面如果消费者要重新加入消费组,再次发送“加入组请求”时也需要指定成员编号 。 通常来说,协调者为消费者
分配了一个成员编号,协调者的消费组元数据就会一直记录这个消费者的信息 。成员编号的主要作用就是用来标识一个消费者,消费者后续的任何请求动作都
应该带有分配给它的成员编号。
   消费组的状态从“稳定”进入“准备再平衡”,表示准备开始再平衡操作 。 一次再平衡操作只会由一个消费者发起,并只会创建一个延迟的操作对象。 延迟操
作还和延迟缓存有关,因为延迟操作如果不能及时完成,就应该放入延迟缓存。 当然,延迟缓存不是普通的数据缓存,它还要提供检查延迟操作能否完成的方法,
并且保证在指定时间内未完成时,必须强制完成超时的延迟操作。

延迟操作和延迟缓存
  Kafka服务端在处理客户端的一些请求时,如果不能及时返回响应结果给客户端,会在服务端创建一个延迟操作对象( DelayedOperatlon ),并放在
延迟缓存中( DelayedOperationPurgatory )。 Kafka的延迟操作有多种:延迟的生产 、延迟的响应 、延迟的加入 、延迟的心跳。
 这里先给出一些延迟操作相关的结论 。
- 延迟操作需要指定一个超时时间,表示在指定时间内没有完成时会被强制完成 。
- 延迟操作加入到延迟缓存中,会指定一个键 。 比如,和消费组相关的延迟加入,键是消费组编号 。
- 服务端创建延迟操作后,通常会有“尝试完成延迟操作”的动作(延迟操作如果能够尽早完成是最好的) 。 尝试完成延迟操作的外部事件会有多种情况,
  而且因为延迟操作有依赖条件,所以任何可能改变依赖条件的事件,都应该执行“尝试完成延迟操作” 。比如,协调者因为依赖了“等待消费者发送加入组请求”
  这个条件才会创建“延迟的加入组”对象 。 如果有消费者发送了加入组请求,就应该尝试完成“延迟的加入组”对象。
- 当外部事件尝试完成延迟操作时,怎么判断延迟操作能不能完成?不同的延迟操作类型因为依赖条件不同,应该自定义可以完成延迟操作的条件判断。
 
创建延迟操作的最终目的是让操作不再被延迟,延迟操作对象有下面几个跟完成操作相关的方法。
- tryComplete () 尝试完成,如果不能完成,返回 false ,表示延迟操作还不能完成 。
- onComplete ()延迟操作完成时的回调方法,完成有两种:正常主动完成和超时被动完成 。
- onExpiration ()延迟操作超时的回调方法,如果之前一直调用尝试完成都不能完成,在指定的超时时间过去后就会强制完成。 调用这个回调方法,
   一定会再调用tryComplete ()方法。

尝试完成延迟的加入操作

  协调者在创建完延迟操作对象之后,为了检查能否完成刚刚创建的延迟操作,会调用延迟缓存的tryCompleteElseWatch () 方法立即尝试完成 。
 延迟缓存会调用延迟操作的tryComplete () 方法, 对于加入组的延迟缓存,就是调用延迟加入对象的tryCompleteJoin ()方法 。 这个方法的第
二个参数表示如果可以完成,就会强制完成延迟加入对象, RP最终会调用到延迟加入对象的 onCompleteJoin ()方法 。 延迟加入操作对象的
tryComplete () 方法和 onComplete() 方法,它们的具体实现是调用协调者的tryCompleteJoin ()方法和 onCompleteJoin () 方法 。

  延迟操作能否完成的判断条件是:消费组元数据的notYetRejoinedMembers ()方法返回值是否为空 。这个方法收集的是消费组中 awaitingJoinCallback
值对象为空的消费者成员元数据。 因为协调者一旦开始处理消费者发送的“加入组请求”,就会设置awaitingJoinCallback值对象为“发送响应的回调方法”,
所以如果消费者发送了“加入组请求”,并且也被协调者开始处理,就不会被notYetRejoinedMembers()方法选出来 。

  以协调者处理第一个消费者发迭的加入组请求为例,因为第一个消费者的 awaitingJoinCallback值对象为空,所以 notYetRejoinedMembers  ()方法
不会选择第一个消费者。 那么,这个方法因为没有收集到任何一个满足条件的消费者,返回值为空,就会执行 forceComplete ()方法,并调用延迟操作
的onCompleteJoin () 方法,开始返回“加入组响应”给消费者 。

这和我们前面认为的“协调者会等待所有的消费者都发送了加入组请求后,才会认为请求处理完成”看起来有点矛盾。 协调者在处理第一个消费者的加
入组请求,没等到其他消费者发送加入组请求,就已经开始返回加入组响应结果给第一个消费者了 。 实际上 ,协调者实现消费组的再平衡操作,
是通过让消费者重新发送“加入组请求”的方式来完成的 。

消费组稳定后,原有消费者重新加入消费组

  协调者在处理消费者发送的“加入组请求”和“同步组请求”时,都会依赖于消费组当前的状态进入不同的分支流程 。 假设第一个消费者完成一次再平衡操
作后,又有新的消费者发送了“加入组请求” 。 如下图 所示,新消费者会发起新的再平衡操作,原有的消费者也需要重新发送“加入组请求”,
具体步骤如下 。

(1) (图 (左))第一个消费者发送“加入组请求”,也完成了延迟操作,会将它的回调方法重置为空。

(2) (图(中))协调者处理第二个消费者的“加入组请求”,消费组状态已经是“稳定” 。 和处理第一个消费者的“加入组请求”类似,
  它也会将消费组状态改为“准备再平衡”,并创建一个延迟的操作对象。

(3) (图 (中))协调者创建完延迟操作后,通过延迟缓存尝试完成刚刚创建的延迟操作 。 但和协调者处理第一个消费者不一样,这时尝试完成的条件不能满足,
  处理第二个消费者的“加入组请求”就结束了 。

(4)  虽然协调者处理第二个消费者的“加入组请求”结束了,但延迟操作对象还不能完成,延迟操作对象会被加入到延迟缓存的监控列表中 。
    后续要完成延迟操作,有两种办法:外部事件触发、超时触发 。

(5)(图 (右))协调者等待第一个消费者重新发送“加入组请求” 。 如果第一个消费者在超时时间内重新发送“加入组”请求,再次调用“尝试完成延
  迟操作”(外部事件触发),满足完成延迟操作的条件。 延迟操作会从延迟缓存中移除,并调用延迟操作完成的回调方法,返回“加入组响应”给所有的
  两个消费者。

(6)但如果第一个消费者在会话超时时间内没有重新发送“加入组请求,消费组成员元数据中第一个消费者的回调方法一直是空的 。 协调者会在延迟
       操作超时后,强制完成超时的延迟操作 。 这时也会调用延迟操作完成的回调方法,但只返回“加入组响应” 给第二个消费者(因为第一个消费者的回
  调方法为空,所以并不会返回响应结果给第一个消费者)。

  协调者在处理第一个消费者的“加入组请求”时,创建的延迟操作对象会立即完成 。 但处理第二个消费者的“加入组请求”时,消费组中已经存在第一个消费者
的成员元数据,此时消费组中总共有两个消费者了 。 但是第一个消费者成员元数据的回调方法在协调者返回“加入组响应”给它时,就被重置为空了 。
 协调者在处理第二个消费者的“加入组请求”时,第二个消费者成员元数据的回调方法不为空 。消费组元数据的 notYetRejoinnedMembers ()方法,
表示还没重新发送“加入组请求”的消费者成员 。 如果该方法的返回值有数据,说明还有消费者成员没发送“加入组请求” 。 这里的消费者成员
必须是存在于消费组中的消费者成员,也是之前发送过“加入组请求”的成员,所以才表示“重新发送” 。

消费组未稳定,原有消费者重新加入消费组

  再来看另一种场景:其他消费者发送“加入组请求”先于第一个消费者发送“同步组请求” 。 协调者返回“加入组响应”给第一个消费者,并更改消费组
状态为“等待同步” 。 第一个消费者收到“加入组响应”后,但还没完成分区分配的工作,就有新的消费者发送了“加入组请求” 。 这时候其实第
一个消费者是不需要执行分区分配的,因为即使执行了,也只有它一个的,并不会包含新加入的第二个消费者 。

  如下图 (上)所示,第一个消费者完成分区分配工作后,“同步组请求”的消费组分配结果只有 第一个消费者的数据。 此时,第一个消费者将分配
结果发送给协调者,协调者是不会接受的 。 因为协调者已经处理了第二个消费者的“加入组请求”,消费组的状态被更改为“准备再平衡” 。 第二个消费
者在这个状态下, 会收到 REBALANCE_IN_PROGRESS的错误码,并重新发送“加入组请求” 。

  如下图 (下)所示,第一个消费者重新发送了“加入组请求”,协调者对第一个消费者重新发送的“加入组请求”,也会尝试完成第二个消费者创建的
延迟操作。因为满足可以完成延迟操作的条件,所以协调者会再次将消费组状态改为“等待同步”,并返回“加入组响应”给两个消费者 。 后续协调
者处理两个消费者的“同步组请求”,个消费者不是主消费者,只会设置消费者成员元数据中的回调方法。 当第一个消费者执行完分区分配后,
协调者处理第个消费者的“同步组请求”, 会同时返回“同步组响应”给两个消费者。

  消费组状态为“稳定”后,当有新消费者加入消费组,将消费组状态更改为“准备再平衡”,原有的消费者是通过心跳的方式
感知到需要重新发送“加入组请求” 。 如果消费组状态还是“等待同步”,就有新消费者加入消费组,也更改消费组状态为“再平衡” 。
于消费组状态未稳定,原有的消费者不会有心跳任务,所以协调者采用返回错误码方式通知原有的消费者 。 即消费
者在发送“同步组请求”时,如果消费组状态为“准备再平衡”,协调者会要求消费者重新发送“加入组请求” 。

  消费者的“加入组请求”时,使用一个“延迟操作”对象表示延迟返回“加入组响应”给消费者。 延迟操作创建时,
伴随着消费组状态从“稳定”转变为“准备再平衡”;延迟操作完成时,消费组状态会从“准备再平衡”转变为“等待同步” 。

Kafka技术内幕 读书笔记之(五) 协调者——延迟的加入组操作的更多相关文章

  1. Kafka技术内幕 读书笔记之(五) 协调者——消费组状态机

    协调者保存的消费组元数据中记录了消费组的状态机 , 消费组状态机的转换主要发生在“加入组请求”和“同步组请求”的处理过程中 .协调者处理“离开消费组请求”“迁移消费组请求”“心跳请求” “提交偏移量请 ...

  2. Kafka技术内幕 读书笔记之(五) 协调者——消费者加入消费组

    消费者客户端轮询的3个步骤:发送拉取请求,客户端轮询,获取拉取结果 . 消费者在发送拉取请求之前,必须首先满足下面的两个条件.- 确保消费者已经连接协调者, 即找到服务端中管理这个消费者的协调者节点 ...

  3. Kafka技术内幕 读书笔记之(五) 协调者——协调者处理请求

    消费者客户端使用“消费者的协调者对象”( ConsumerCoordinator )来代表所有和服务端协调者节点有关的请求处理,比如心跳请求.获取和提交分区的偏移量(自动提交任务).发送“加入组请求” ...

  4. Kafka技术内幕 读书笔记之(四) 新消费者——新消费者客户端(二)

    消费者拉取消息 消费者创建拉取请求的准备工作,和生产者创建生产请求的准备工作类似,它们都必须和分区的主副本交互.一个生产者写入的分区和消费者分配的分区都可能有多个,同时多个分区的主副本有可能在同一个节 ...

  5. Kafka技术内幕 读书笔记之(三) 消费者:高级API和低级API——消费者消费消息和提交分区偏移量

    消费者拉取钱程拉取每个分区的数据,会将分区的消息集包装成一个数据块( FetchedDataChunk )放入分区信息的队列中 . 而每个队列都对应一个消息流( KafkaStream ),消费者客户 ...

  6. Kafka技术内幕 读书笔记之(四) 新消费者——心跳任务

    消费者拉取数据是在拉取器中完成的,发送心跳是在消费者的协调者上完成的,但并不是说拉取器和消费者的协调者就没有关联关系 . “消费者的协调者”的作用是确保客户端的消费者和服务端的协调者之间的正常通信,如 ...

  7. Kafka技术内幕 读书笔记之(二) 生产者——新生产者客户端

    消息系统通常由生产者(producer ). 消费者( consumer )和消息代理( broker ) 三大部分组成,生产者会将消息写入消息代理,消费者会从消息代理中读取消息 . 对于消息代理而言 ...

  8. Kafka技术内幕 读书笔记之(四) 新消费者——消费者提交偏移量

    消费组发生再平衡时分区会被分配给新的消费者,为了保证新消费者能够从分区的上一次消费位置继续拉取并处理消息,每个消费者需要将分区的消费进度,定时地同步给消费组对应的协调者节点 .新AP I为客户端提供了 ...

  9. Kafka技术内幕 读书笔记之(六) 存储层——服务端处理读写请求、分区与副本

    如下图中分区到 日 志的虚线表示 : 业务逻辑层的一个分区对应物理存储层的一个日志 . 消息集到数据文件的虚线表示 : 客户端发送的消息集最终会写入日志分段对应的数据文件,存储到Kafka的消息代理节 ...

随机推荐

  1. BZOJ4755 [JSOI2016]扭动的回文串 【后缀数组】【manacher】

    题目分析: 我写了史上最丑的后缀数组,怎么办? 首先manacher一遍两个串,这样只用考虑第三问.用$作为间隔符拼接两个串,把第一个串翻转.枚举回文中心,取最长的回文串,对于剩下的部分利用LCP匹配 ...

  2. 百度分享不支持https的解决方案(单独部署静态文件)

    首先是参考了博客,下载百度分享的静态代码 static 链接为:https://www.cnblogs.com/mmzuo-798/p/6434576.html 后来在nginx的 nginx.con ...

  3. django 配置邮件发送 send_email

    导入 send_email 所用方法导入 from django.core.mail import send_mail 因为使用的需要指明 发送人 所以要把 setting.py 中的 EMAIL_F ...

  4. urllib的实现---cookie处理

    Cookie的使用 用 Python 来登录网站, 用Cookies记录登录信息, 然后就可以抓取登录之后才能看到的信息. 什么是cookies? Cookie,指某些网站为了辨别用户身份.进行ses ...

  5. #1014 : Trie树 HihoCoder(字典树)

    描述 小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进. 这一天,他们遇到了一本词典,于是小Hi就向小Ho提出了那个经典的问题: ...

  6. 让Mac 可以使用mysql -u用户直接连接数据库

    在执行完安装版本的mysql数据库后,会发现执行mysql还是会出现 command not found的错误:解决方案 方案1.设置软连接到/usr/local/bin下在命令行下输入如下 ln - ...

  7. 使用 MongoDB 存储日志数据

    使用 MongoDB 存储日志数据     线上运行的服务会产生大量的运行及访问日志,日志里会包含一些错误.警告.及用户行为等信息.通常服务会以文本的形式记录日志信息,这样可读性强,方便于日常定位问题 ...

  8. 前端JS Excel解析导入

    本文转载自:https://www.cnblogs.com/yinqingvip/p/6743213.html 需要用到js-xlsx:下载地址:js-xlsx <!DOCTYPE html&g ...

  9. 【CF1119E】Pavel and Triangles

    题目大意:有 N 种长度的边,第 i 种长度为 \(2^i\),给定一些数量的这些边,问最多可以组合出多少种三角形. 题解:应该是用贪心求解,不过选择什么样的贪心策略很关键. 首先分析可知,两个较大边 ...

  10. 第十五篇-EditText做简单的登录框

    TextView和EditText的简单应用. MainActivity.java package com.example.aimee.edittexttest; import android.sup ...