消费者客户端使用“消费者的协调者对象”( ConsumerCoordinator )来代表所有和服务端协调者节点有关的请求处理,比如心跳请求、
获取和提交分区的偏移量(自动提交任务)、发送“加入组请求”和“同步组请求”从协调者获取到分区 。 服务端处理客户端请求的人口都是KafkaApis类,
它会针对不同的请求类型分发给不同的方法处理 。

服务端定义发送响应结果的回调方法
  不同消费者在不同时刻发送请求给服务端,服务端并不会立即发送响应结果给消费者。 为了保证服务端的高性能,虽然服务端不能立即返
回响应结果给消费者但并不意味着服务端对每个请求的处理都是阻塞的 。 那么既要做到不能阻塞请求的处理,又要做到必须返回响应结果给消费
者,服务端的做法是:在处理每个请求时,首先定义一个“发送响应结果的回调方法”( sendResponseCallback() ),回调方法会传给负责消费组
相关业务逻辑的消费组协调者( GroupCoordinator )。 当协调者认为请求完成时,会调用回调方法发送响应结果给消费者 。

  为什么要在处理请求刚开始就定义回调方法,而不是在请求真正完成后直接发送响应结果,这样就不需要回调方法了 。 这是因为服务端发送响
应结果给客户端,会创建一个和请求互相关联的响应结果对象,确保“客户端发送给服务端的请求” 和 “服务端返回给客户端的响应结果 ” 是在同一个
网络通道中完成的 。 “创建响应结果时需要持有请求的引 用” , 如果没有在处理请求的地方定义回调方法 ,而是在请求完成时直接创建响应结果并
发送给客户端 , 就需要把请求对象一路传到请求完成的地方才可以 。

  服务端处理“加入组请求”中回调方法的参数是“加入组的结果”,然后封装成“加入组的响应”返回给客户端。处理“同步组请求”中回调方法的参数是
“成员状态”, 即分配的分区,然后封装成“同步组的响应”返回给客户端 。客户端从服务端接收的响应结果和服务端返回给客户端的响应结果必须一样 。 

  协调者是同一个消费组下所有消费者的协调节点 。 一个消费组有多个消费者 ,而消费组只是一个逻辑概念 。 具体涉及消费组相关的业务逻辑操作时
必须有具体的实现类才能完成 , 协调者就充当了这样的管理员角色。 协调者会通过元数据的方式管理消费’组下的所有消费者 。 协调者可以同时管理多个消费组
所以元数据有两种:消费组的元数据 、消费者的元数据。 消费组的元数据包括了所有消费者的元数据。

消费者和消费组元数据
  消费者加入组过程发送的“加入组请求”和“同步组请求”,都会指定消费组编号( groupid )和消费者成员编号(memberId ),同一个消费组编号只
对应一个“消费组元数据”( GroupMetadata ,下文简称“组元数据”)。 服务端使用“消费者成员元数据”( MemberMetadata ,下文简称“成员元数据”)
表示每个消费者发送的元数据信息,并添加到对应的“组元数据”中 。

1. 消费者成员元数据
  “成员元数据”类的构造函数参数,有成员编号 、 消费组编号、协议元数据集 。 会话超时时间是由客户端发送“加入组请求 ”时指定的,latestHeartbeat变量
记录了该消费者最近一次发送心跳的时间 。 另外,“成员元数据”最重要的一个信息是 : 当前这个消费者到底分配到了哪些分区( assignment变量),
因为消费者加入组的最终目的就是从协调者获取到分区 。

  “成员元数据”还定义了两个值对象,它们分别对应服务端在处理请求时定义的两个发送响应回调方法 : 
- awaitingJoinCallback:加入组回调方法

- awai.ti.ngSyncCallback :同步组回调方法

2. 消费组元数据
  一个“组元数据” 管理了所有消费者的 “成员元数据” 。 如果添加“成员元数据” 时都还没有“组元数据” (更新时一定存在“组元数据”),就会先创建 “组元数据” 。
创建“组元数据”是必须的,如果没有“组元数据”, 即使有“成员元数据”,也是没有意义的 。

  “组元数据”在消费者需要加入或更新时,除了更新对应消费者的“成员元数据” ,还会记录一些其他数据。 比如,协调者会为消费组选择一个主消费者 ,
 来代替它执行分区分配工作 。 另外,每个消费者发送“加入组请求时,都会指定一个会话超时时间。协调者会从消费组的所有消费者中,选择
一个最大的会话超时时间,作为“再平衡操作的超时时间 ” 。

  消费组元数据中还有一个很重要的数据 : “消费组的当前状态” 。 因为每个消费者加入消费组都分成“加入组”和“同步组”两个步骤,所以协调者在处理不
同消费者的这两种请求时,都需要改变消费组的状态 。 消费组元数据的状态机有 4 种状态:“稳定状态”( Stable )、“准备再平衡状态”( preparingRebalance )、
“等待同步状态”( AwaitingSync )、“离开状态”( Dead )。 协调者新创建一个消费组元数据,这个消费组元数据的初始状态为“稳定状态” 。

协调者处理请求前的条件检查
  协调者在处理“加入组请求”和“同步组请求”之前都需要优先做下面的一些条件检查 。
- 协调者不可用,通常是协调者被关闭了 。
- 消费者客户端传递的消费组编号无效,比如没有设置消费组编号 。
- 消费者连接错了协调者,这个协调者不是消费组的协调者。
- 协调者正在加载,通常是协调者自身在进行迁移 。
- 消费者客户端设置的会话超时时间无效。
- 协调者还没有消费组,但消费者的成员编号却不是“未知编号” 。
- 协调者有消费组,消费者的成员编号不是“未知编号”,但是不在消费组中 。

  协调者针对上面几种异常情况都有特定的错误码,并且会立即调用定义好的回调方法,把错误信息及时地返回给消费者客户端。
消费者客户端在响应处理器的回调方法中,针对每种错误码都有不同的处理。 比如,如果是“未知编号”错误码,就会重置客户端的成员编号为“未知编号”,
然后重新发送“加入组请求”;如果是GROUP_COORDINATOR_NOT_AVAILABLE或NOT_COORDINATOR_FOR_GROUP , 消费者就会连接新的协调者节点,
并重新发送“加入组请求” 。

允许消费者加入消费组,有下面两种情况 。
 
- 消费组为空井且成员编号为“未知编号”,允许加入 。 第一个消费者第一次加入组会执行一次。
- 消费组不为空 ,如果成员编号是“未知编号”,允许加入;如果成员变量不是“未知编号”,必须保证 已经在消费组中才允许加入。

  协调者处理消费者发送的“同步组请求”同样需要执行条件检查 。 客户端发送“加入组请求”后才会发送“同步组请求”,服务端处理“同步组请求”也一定在处理
“加入组请求”之后 。协调者在正常处理“加入组请求”时一定会创建消费组,在处理“同步组”请求时必须保证消费组不为空。

  消费者发送“加入组请求”和“同步组请求”一定是发送到同一个协调者节点 。 协调者在判断有没有消费组之前,会首先判断它是不是消费组的协调者,
如果不是,直接返回 NOT_COORDINATOR_FOR_GROUP错误码 。协调者处理两种情况的最终目的是 : 返回“加入组响应”和“同步组响应” 给消费者客户端。
其中,“加入组响应”要能够返回所有的消费者成员信息,“同步组响应”要能返回消费者的分配信息 。

协调者调用回调方法发送晌应给客户端

1 . 发送“加入组响应”给消费者
  协调者要返回“加入组响应”给消费组下的所有消费者之前,会增加纪元编号 、 选择出一个统一的消费组协议 、 将消费组状态更改为“等待同步” 。
因为是要返回“加入组响应”,而消费者成员元数对象保存了 “发送响应的回调方法”,所以只要用调用方法的方式调用值对象,就可以调用到“发送加入 
组响应的回调方法” 。

  消费组管理了所有的消费者成员, 协调者发送“加入组响应”时,是一次性一起发送响应结果给每个消费者 。但是协调者处理消费者发送的“加入
组请求”并不是同时进行的,这说明协调者在处理某些消费者的“加入组请求”时,并不会立即返回“加入组响应” 。 实际上,这是通过“延迟操作”
来实现的,“延迟操作”类似于延迟的任务,它和消费组的状态机也有关系 。

2 发送“同步组响应”给消费者
  协调者没有同时处理每个消费者的“同步组请求”,但最后同时发送了“同步组响应” 。 说明协调者在处理某些消费者的“同步组请求”时,
并不会立即返回“同步组响应” 。

  当协调者收到主消费者的“同步组请求 ” 后 ,它会立即返回“同步组响应”给所有的消费者(包括主消费者和普通消费者)。
协调者发送“ 同步组响应”给消费组每个消费者的方式和发送 “加入组响应” 类似。

3. 协调者保存消费组任务
  协调者在返回“同步组响应”给消费者之前,会先把“消费组分配结果”( groupAssignment )以普通消息的形式持久化到内部主题(_consumer_offsets )中
如果协调节点出现问题需要进行故障迁移,新的协调者可以从“内部主题” 中读取持久化的消息,重建“消费组分配结果” 。

  一个协调者可以充当多个消费组的协调节点,并使用“消费组缓存”保存它管理的所有“消费组元数据” 。迁移协调者时会读取内部主题的“消费组分配结果”,
 重新加载到“消费组缓存”中 。 协调者处理“加入组请求”和“同步组请求”时,根据消费者客户端传递的消费组编号查询“消费组元数据” ,会先从
“消费组缓存”中查询,如果“消费组元数据”已经存在,直接使用现有的数据 。

    协调者将“消费组分配结果”保存到内部主题之后 , 才会发送“同步组响应”给每个消费者。

Kafka技术内幕 读书笔记之(五) 协调者——协调者处理请求的更多相关文章

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

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

  2. Kafka技术内幕 读书笔记之(五) 协调者——延迟的加入组操作

      协调者处理不同消费者的“加入组请求”,由于不能立即返回“加入组响应”给每个消费者,它会创建一个“延迟操作”,表示协调者会延迟发送“加入组响应”给消费者 . 但协调者不会为每个消费者的 “加入组请求 ...

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

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

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

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

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

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

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

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

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

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

  8. Kafka技术内幕 读书笔记之(一) Kafka入门

    在0.10版本之前, Kafka仅仅作为一个消息系统,主要用来解决应用解耦. 异步消息 . 流量削峰等问题. 在0.10版本之后, Kafka提供了连接器与流处理的能力,它也从分布式的消息系统逐渐成为 ...

  9. Kafka技术内幕 读书笔记之(六) 存储层——日志的读写

    -Kafka是一个分布式的( distributed ).分区的( partitioned ).复制的( replicated )提交日志( commitlog )服务 . “分布式”是所有分布式系统 ...

随机推荐

  1. Dirichlet's Theorem on Arithmetic Progressions POJ - 3006 线性欧拉筛

    题意 给出a d n    给出数列 a,a+d,a+2d,a+3d......a+kd 问第n个数是几 保证答案不溢出 直接线性筛模拟即可 #include<cstdio> #inclu ...

  2. Codeforces997C Sky Full of Stars 【FMT】【组合数】

    题目大意: 一个$n*n$的格子,每个格子由你填色,有三种允许填色的方法,问有一行或者一列相同的方案数. 题目分析: 标题的FMT是我吓人用的. 一行或一列的问题不好解决,转成它的反面,没有一行和一列 ...

  3. 【XSY2731】Div 数论 杜教筛 莫比乌斯反演

    题目大意 定义复数\(a+bi\)为整数\(k\)的约数,当且仅当\(a\)和\(b\)为整数且存在整数\(c\)和\(d\)满足\((a+bi)(c+di)=k\). 定义复数\(a+bi\)的实部 ...

  4. [2017-7-28]Android Learning Day7

    View动画效果 透明动画效果 旋转动画效果 移动动画效果 缩放动画效果 混合动画效果 1.透明动画效果(AlphaAnimation) 有两种方法 第一种在活动中设置,不需要xml文件 public ...

  5. 使用item pipeline处理保存数据

    一个Item Pipeline 不需要继承特定基类,只需要实现某些特定方法,面向接口. class MyPipeline(object): def __init__(self): "&quo ...

  6. NowCoder--牛可乐发红包脱单ACM赛C_区区区间间间

    题目链接:C_区区区间间间 思路:算贡献,求出每个数为当前最大值时所在的区间个数,和每个数为最小值的区间个数 和这个题有点类似 搭配食用效果更佳 点击这里 #include<bits/stdc+ ...

  7. 深入学习semaphore

    深入学习semaphore 控制同时访问资源线程数 访问特定资源前,先使用acquire(1)获得许可,如果许可数量为0,该线程则一直阻塞,直到有可用许可. 访问资源后,使用release()释放许可 ...

  8. P1966 火柴排队

    这道题需要小小的思考一波 (然而我思考了两节课) 好,我们先得出一个结论:a中第k大的与b中第k大的一定要排在一起,才能保证最小. 然后发现:挪a,b其实没有区别,故我们固定a,挪b. 然后我们就思考 ...

  9. CF528D Fuzzy Search

    题意:给定k,只含有ACGT的字符串S和T,求T在S中出现了多少次. 字符匹配:如果S的[i - k, i + k]中有字符x,那么第i位可以匹配x. 解: 首先预处理:f[i][j]表示S的第i位能 ...

  10. A1116. Come on! Let's C

    "Let's C" is a popular and fun programming contest hosted by the College of Computer Scien ...