延迟的加入组操作:

协调者处理不同消费者的“加入组请求”,由于不能立即返回“加入组响应”给每个消费者,它会创建一个“延迟操作”,表示协调者会延迟发送“加入组响应”给消费者。但协调者不会为每个消费者的“ 加入组请求”都创建一个“ 延迟操作”,而是仅当消费组状态从“稳定”转变为“准备再平衡”,才创建一个“延迟操作”对象。
为了保证只创建一个“延迟操作” , 只有消费组的状态为“稳定”时才可以创建“延迟操作”,并且在创建“延迟操作”的同时,更新消费组状态为“准备再平衡” 。这样协调者在处理下一个消费者的“加入组请求”时,因为消费组状态已经更新为“准备再平衡”,就不会创建“延迟操作”对象了。
下面两个步骤示例了协调者如何处理两个消费者的“加入组请求” 。
(1)初始时消费组状态为“稳定”,第一个消费者加入消费组。因为消费组状态为“稳定”,所以协调者允许消费者执行再平衡操作。协调者更改消费组的状态为“准备再平衡”,并创建一个延迟的操作对象。
(2)消费组状态为“准备再平衡”,第二个消费者加入消费组。因为消费组状态为“准备再平衡”,所以协调者不允许消费者执行再平衡操作。消费组状态仍然不变,协调者也石会创建延迟的操作对象。

准备再平衡:

协调者处理消费者的“加入组请求”,如果消费者设置的成员编号未知,协调者会为这个消费者指定一个新的成员编号,然后创建消费者成员元数据,并加入到消费组元数据中。如果消费者成员编号是已知的,说明消费组元数据中已经存在对应的消费者成员元数据,只需要更新已有的成员元数据。协调者为消费者分配的成员编号,会作为“加入组响应结果”返回给消费者。消费者发送“同步组请求”时必须指定这个新分配的成员编号,这样协调者才能知道成员编号对应的消费者元数据。实际上, 后面如果消费者要重新加入消费组,再次发送“加入组请求”时也需要指定成员编号。通常来说,协调者为消费者分配了一个成员编号,协调者的消费组元数据就会一直记录这个消费者的信息。成员编号的主要作用就是用来标识一个消费者,消费者后续的任何请求动作都应该带有分配给它的成员编号。

延迟操作和延迟缓存:

Kafka服务端在处理客户端的一些请求时,如果不能及时返回响应结果给客户端,会在服务端创建一个延迟操作对象( DelayedOperation),并放在延迟缓存中( DelayedOperationPurgatory )。Kafka的延迟操作有多种:延迟的生产、延迟的响应、延迟的加入、延迟的心跳。关于延迟操作和延迟缓存相关的流程,会在下一章详细分析,这里先给出一些延迟操作相关的结论。

1.延迟操作需要指定一个超时时间,表示在指定时间内没有完成时会被强制完成。
2.延迟操作加入到延迟缓存中,会指定一个键。比如,和消费组相关的延迟加入,键是消费组编号。
3.服务端创建延迟操作后,通常会有“尝试完成延迟操作”的动作(延迟操作如果能够尽早完成是最好的) 。尝试完成延迟操作的外部事件会有多种情况,而且因为延迟操作有依赖条件,所以任何可能改变依赖条件的事件,都应该执行“尝试完成延迟操作” 。比如,协调者因为依赖了“等待消费者发送加入组请求”这个条件才会创建“延迟的加入组”对象。如果有消费者发送了加入组请求,就应该尝试完成“延迟的加入组”对象。
4.当外部事件尝试完成延迟操作时,怎么判断延迟操作能不能完成?不同的延迟操作类型因为依赖条件不同,应该自定义可以完成延迟操作的条件判断。

 

尝试完成延迟的加入操作:

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

如图5-7所示, 一旦协调者释放了“消费组元数据”的锁保护后,比如协调者返回“加入组响应”给第一个消费者之后,如果第一个消费者还没有发送“同步组请求”给协调者,协调者就可以接着处理其他消费者发送的“加入组请求” 。但如果在其他消费者发送“加入组请求”之前,第一个消费者很快地发送“同步组请求”给协调者,而协调者处理“同步组请求”和“加入组请求” 一样,都会对“消费组元数据”进行加锁,这时协调者就不会处理其他消费者发送的“加入组请求”了。

kafka java 延迟队列 kafka延迟队列使用_kafka

协调者在返回“加入组响应”给第一个消费者之后释放了同步的锁,消费组的状态也更新为“等待同步” 。并且第一个消费者同时也是主消费者,它在收到“加入响应”后,会立即执行分区分配工作。如图5-8(上)所示,协调者在处理第一个消费者的“同步组请求”时,不会执行其他消费者的“加入组请求” 。如佟15-8 ( 下)所示,在第一个消费者发送“同步组请求”给协调者之前,新的消费者发送了“加入组请求”,协调者就会处理新消费者的“加入组请求”,因为现在没有其他线程对消费组元数据进行加锁保护了。
 

kafka java 延迟队列 kafka延迟队列使用_kafka_02

 

 

消费组状态机:

协调者保存的消费组元数据中记录了消费组的状态机, 消费组状态机的转换主要发生在“加入组请求”和“同步组请求”的处理过程中。除此之外,协调者处理“离开消费组请求”“迁移消费组请求”“心跳请求” “提交偏移量请求”也会更新消费组的状态机,或者依赖消费组的状态进行不同的处理。
消费者要加入消费组, 需要依次发送“加入组请求”和“同步组请求”给协调者。消费者加入组的过程叫作再平衡,因为协调者处理这两种请求会更新消费组状态,所以再平衡操作跟消费组状态也息息相关。

消费组的状态转换:

如图5-11 所示,一次再平衡操作的正常消费组状态变化过程是:稳定→准备再平衡→等待同步→稳定,这个顺序是按顺时针转动的。以只有一个消费者加入消费组为例,要经过下面3 个步骤。
(1)消费者发送“加入组请求”,消费组的状态从初始的“稳定”更改为“准备再平衡”,这个过程会创建一个延迟的操作,并检查能否完成延迟操作。
(2)因为只有一个消费者,所以延迟操作可以完成,消费组状态从“准备再平衡”改为“等待同步” 。
(3)当前的状态是“等待同步”,表示协调者等待消费者发送“同步组请求” 。当协调者收到主消费者发送的“同步组请求”后,会返回“同步组响应”给消费者,消费组状态从“ 等待同步”改为“稳定” 。

图5-11 中,消费组状态从“等待同步”也可以转到“准备再平衡”,它也会创建一个延迟的操作。无论从“稳定”状态到“准备再平衡”,还是从“等待同步”到“准备再平衡”,只要是进入“准备再平衡”状态,都会创建一个延迟的操作。

kafka java 延迟队列 kafka延迟队列使用_kafka_03

 

kafka java 延迟队列 kafka延迟队列使用_kafka_04

kafka java 延迟队列 kafka延迟队列使用_协调者_05

状态机包括3 部分内容:状态机本身、事件、动作。协调者处理不同事件时, 会根据状态机的状态,执行不同的动作,而执行动作时又会间接影响状态机。消费组的状态有4种:稳定、准备再平衡、等待同步、离开。协调者处理不同消费者的不同请求依赖于消费组的当前状态,在处理过程中协调者又会更改消费组的状态。

如图5-17 所示,如果添加或更新消费者成员时,消费组状态已经是“准备再平衡”,则不需要再执行“准备再平衡操作” 。因为协调者在处理上一个消费者时,在将消费组状态改为“准备再平衡”时已经执行了一次“准备再平衡操作” 。

kafka java 延迟队列 kafka延迟队列使用_kafka_06

 

再平衡超时与会话超时:

  当消费组状态是“准备再平衡”,协调者会创建一个“延迟的加入组”对象。这个对象表示协调者在处理消费者的“加入组请求”时,由于还没有收集完整“重新发送加入组请求”的消费者,所以还不能返回“加入组响应”给发送了“加入组请求”的消费者。当消费组中的所有消费者都发送(或重新发送)了“加入组请求”,“延迟的加入组”对象就可以完成,协调者这时才会返回“加入组响应”给所有的消费者。
  协调者等待“延迟操作”完成有一个时间限制,它会选择消费组巾所有消费者会话超时时间的最大值,作为“再平衡操作的超时时间”,也叫作“延迟操作的超时时间” 。如果是第一个消费者加入组,再平衡操作的时间等于第一个消费者的会话超时时间。但因为目前消费组中只有第一个消费者,所以协调者刚刚创建的延迟操作可以马上完成。当第二个消费者加入组后,再平衡操作的时间会选择两个消费者的会话超时时间最大值。比如第一个消费者的会话超时时间是10秒,第二个消费者的会话超时时间是5 秒,再平衡操作的超时时间等于10 秒,延迟的加入操作会最多等待10秒,等待第一个消费者在这段时间内可以重新发送“加入组请求”。
  为延迟操作设置超时时间是为了防止延迟操作一直无法完成。假设原有的消费者迟迟没有重新发送“加入组请求”,协调者就无法确定何时才可以返回“加入组响应”给已经发送了“加入组请求”的消费者。从创建延迟操作, 经过了“再平衡操作超时时间”之后,延迟操作会被强制完成。在完成延迟操作时,协调者会找出那些没有在规定时间内重新发送“加入组请求”的消费者,将它们从消费
组中移除掉。

如图5-23 所示,在消费组的一次再平衡操作过程中,服务端的协调者只有一个延迟的加入对象( DelayedJoin ),并且它会为每个消费者保存一个延迟的心跳对象( DelayedHeartbeat ) ,用来监控消费者是否及时地发送心跳,具体步骤如下。

(1)消费者发送“加入组请求”时会指定会话的超时时间(简称“会话时间”)。
(2)协调者不能立即返回“加入组响应”给消费者,创建一个消费组级别的“延迟加入” 。
(3)“延迟加入”可以完成,协调者返回“加入组响应”给消费组中的每个消费者。
(4)协调者为每个消费者都创建一个“延迟心跳”,并监控每个消费者是否存活。

 

kafka java 延迟队列 kafka延迟队列使用_kafka java 延迟队列_07

协调者在处理完消费者的“加入组请求”后,会返回“加入组响应”给消费者。消费者收到“加入组响应”后, 就应该在会话时间内及时发送“同步组请求”给协调者; 否则,协调者就会认为消费者出现了故障。协调者在处理“同步组请求”时,有多个地方调用了“完成若调度下一次心跳”方法。

(1)状态为“等待同步”,在设置成员元数据的回调方法后调用。
(2)状态为“稳定” ,在发送“ 同步组响应”给消费者后调用。
(3)状态为“等待同步”,收到主消费者的“同步组请求”,给每个消费者发送“ 同步组响应”后调用。

kafka java 延迟队列 kafka延迟队列使用_kafka_08

kafka java 延迟队列 kafka延迟队列使用_元数据_09