RocketMQ5源码(一)POP消费

前言

本章基于rocketmq5.1.1分析pop消费 特性,具体特性的描述和用途参考[RIP-19]。

pop消费的特点在于:

  1. broker负责rebalance,为每个consumer分配queue,支持队列非独占(一个队列分配给组内多个consumer消费);
  2. broker负责管理消费进度,包括拉消息和提交offset;

案例

consumer侧,使用setClientRebalance=false,关闭客户端rebalance。

java 复制代码
public static void main(String[] args) throws Exception {
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP);
    consumer.setNamesrvAddr("localhost:9876");
    consumer.subscribe(TOPIC, "*");
    consumer.registerMessageListener(new MessageListenerConcurrently() {
        @Override
        public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
            System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }
    });
    consumer.setClientRebalance(false); // 【关闭client rebalance】
    consumer.start();
}

broker侧,可通过两种方式开启pop消费。

1、topic+consumerGroup纬度

如通过mqadmin setConsumeMode命令,设置某个broker某个topic在某个consumerGroup下开启pop消费。

注:也可以通过-c选项代替-b选项,设置某个cluster,本质是循环cluster下所有broker。

css 复制代码
mqadmin setConsumeMode -b brokerAddr -t topic -g consumer_group -m POP -n 8

2、broker纬度

配置defaultMessageRequestMode=POP(默认PULL,即老逻辑队列独占),针对所有topic和consumerGroup开启pop消费,优先级低于第一种。

ini 复制代码
defaultMessageRequestMode=POP

consumer

consumer侧消费流程还是大致如下,主要变化在于RebalancePullConsume重试

主流程可参考4.x的逻辑:RocketMQ4源码(三)普通消息消费

broker

rebalance:原来pull模式默认平均分配策略,pop模式如何分配队列

pull/consume:原来pull模式消费进度由每个consumer管理,如果队列非独占,会造成消费进度混乱。pop模式如何管理消费进度

重试:原来pull模式通过consumer主动订阅发布%RETRY%{consumerGroup}消息实现消费重试,pop模式使用changeInvisibleTime实现重试有什么区别?

注:

1)详细的消费逻辑忽略

包括如何通过逻辑offset定位commitlog,拉消息长轮询处理等。

参考RocketMQ4源码(三)普通消息消费

2)忽略各种5.x其他新特性

5.x很多新特性,代码复杂度极高,可以忽略。

比如代码里看到很多CompletableFuture是RIP-57对于多级存储的实现而引入异步api,实际底层还是同步。

Rebalance

consumer启用broker rebalance

开启pop消费的前提是,consumer需要启用broker rebalance,由broker执行queue分配。

RebalanceImpl#doRebalance:客户端rebalance方法入口,执行broker rebalance需要满足两个条件。

RebalancePushImpl#clientRebalance:满足下面条件开启client rebalance老逻辑。

相反,第一,满足下面4个条件,开启broker rebalance逻辑:

  1. 使用push api(lite pull和pull都不支持);
  2. 关闭clientRebalance;
  3. 非顺序消费(并发消费);
  4. 非广播消费(集群消费);

RebalanceImpl#tryQueryAssignment:第二,broker能够正常执行QUERY_ASSIGNMENT

consumer调用某个有对应topic的broker的QUERY_ASSIGNMENT api,如果成功调用,则允许执行broker rebalance。

RebalanceImpl#getRebalanceResultFromBroker:

启用broker rebalance后,consumer从broker获取分配的queue,更新分配给自己的ProcessQueue。

broker分配队列

开启POP消费

默认情况下,即使客户端开启broker rebalance,也仅仅代表由broker分配queue,并没有开启pop消费。

broker端开启pop消费的方式,见前言案例。

SetMessageRequestModeRequestBody是broker端针对topic+consumerGroup的消费方式。

mode=PULL(默认),直接走分配策略(如默认平均分配)为每个客户端分配queue,即队列独占;

mode=POP,开启pop消费,支持共享队列;

Pull or Pop

QUERY_ASSIGNMENT 的第一步需要确定MessageRequestMode,即Pull还是Pop。

  1. 优先走topic+consumerGroup纬度的配置;
  2. 默认走broker纬度的配置;
  3. 对于重试topic只能走pull模式老逻辑;

注意:这里重试topic是pull模式的%RETRY%{consumerGroup},后面提到的pop重试topic不会走这里。

针对topic+consumerGroup纬度的配置,都存放在MessageRequestModeManager中。

注:内存数据会持久化到messageRequestMode.json文件中。

分配queue

QueryAssignmentProcessor#doLoadBalance:

和客户端rebalance一样,先要得到topic下所有queue和消费组内所有成员clientId。

QueryAssignmentProcessor#doLoadBalance:

区分pull模式还是pop模式。

如果是pull模式,直接执行原始AllocateMessageQueueStrategy分配,比如默认平均分配策略。

QueryAssignmentProcessor#allocate4Pop:

pop模式分配,有三种情况。

case1:

默认情况下 ,popShareQueueNum=-1,所有consumer实例会全量分配queue

如果popShareQueueNum大于等于consumer实例数量-1,也会全量分配queue。

注意,这里queueId设置为-1,最后MessageQueue去重后,数量为topic对应master broker数量。

相当于客户端不再关心实际broker&topic下有多少个队列。

case2:

如果popShareQueueNum小于consumer实例数量-1,且consumer数量小于等于queue数量

执行popShareQueueNum+1次分配策略,去重后加入结果集。

比如使用平均分配策略,8个queue,3个client,popShareQueueNum=1。

index=0的client分配q0-q5,index=1的client分配q3-q7,index=2的client分配q6-q7和q0-q2。

case3:

不满足上述条件,即popShareQueueNum小于consumer实例数量-1,且consumer数量大于queue数量

QueryAssignmentProcessor#allocate:每个consumer实例分配1个queue

所以不合理的popShareQueueNum+consumer数量+queue数量仍然会导致队列独占,比如8个queue,16个consumer,popShareQueueNum小于15。

最终分配结果转换为MessageQueueAssignment返回客户端。

consumer更新ProcessQueue

RebalanceImpl#updateMessageQueueAssignment:

如果broker开启pop消费,所有MessageQueueAssignment都是POP模式。

重试topic订阅

pull模式,每个consumerGroup会默认订阅重试topic,即%RETRY%{consumerGroup},存在对应ProcessQueue。

pop模式,在topic+consumerGroup纬度还会存在n个订阅,即 %RETRY%{consumerGroup}_{topic} ,不存在ProcessQueue。

在更新ProcessQueue的过程中,会对pop重试topic订阅关系做处理。

RebalanceImpl#updateMessageQueueAssignment:

如果当前是push模式,需要订阅pop重试topic;

如果当前是pop模式,则需要【取消】订阅pop重试topic

此外pop重试topic不存在对应ProcessQueue

pop模式既然不关注重试topic,那是怎么工作的呢?

分配PopProcessQueue&提交PopRequest

对于pull模式,每个MessageQueue对应一个ProcessQueue;

对于pop模式,每个MessageQueue对应一个PopProcessQueue

PopProcessQueue不同于原来的ProcessQueue,不再包含任何offset相关属性。

RebalanceImpl#updateMessageQueueAssignment:

对于移除分配给自己的queue,仍然是标记drop并从RebalanceImpl的popProcessQueueTable中移除,不具体看了。

对于新分配给自己的queue,构造PopRequest提交到PullMessageService线程。

RebalancePushImpl#getConsumeInitMode:

和原来PullRequest的区别是,PopRequest没有指定队列逻辑offset,仅仅指定了consumeInitMode

ConsumeInitMode分为两种:

  1. 默认MAX;
  2. 可配置ConsumeFromWhere=CONSUME_FROM_FIRST_OFFSET,修改为MIN;

Pull

consumer发送pop消息请求

PullMessageService#run:

consumer的PullMessage线程消费PopRequest。

DefaultMQPushConsumerImpl#popMessage:

consumer经过一系列校验后,如流控,提交请求。

PullAPIWrapper#popAsync:

pop消息请求仅支持发送给master broker。如果master不存在,延迟3s再提交PopRequest。

注:上一章提到(4.x的HA),普通pull消费是可以走slave的,见PullAPIWrapper#pullKernelImpl。

PullAPIWrapper#popAsync:相较于普通pull消息,pop消息请求的区别是:

  1. 没有commitOffset 参数,不支持由consumer顺便提交offset
  2. 没有queueOffset 参数,拉取逻辑offset,只有一个initMode=MAX或MIN,默认MAX;
  3. 多了invisibleTime参数,默认60s;

broker回复pop消息响应

拉消息主流程

在rebalance阶段知道,pop消费的重试topic是不存在ProcessQueue概念的。

也就是说consumer针对pop重试topic不会主动请求broker来拉消息

PopMessageProcessor#processRequest:

其实broker在pop消息时,broker根据一定概率回复重试消息,这个概率是五分之一

PopMessageProcessor#processRequest:

默认情况下,popShareQueueNum=-1,导致queueId为-1。

从随机下标开始,循环队列数量次数,pop消息

如果根据popShareQueueNum+cid+queue数量分配到实际队列,pop指定队列消息即可。

PopMessageProcessor#processRequest:

最后,如果没拉过重试队列消息(五分之四概率),且普通队列消息没拉满32条(客户端默认指定),才会从重试队列拉消息。

PopMessageProcessor#processRequest:

拉取到消息,正常回复consumer,如果restNum>0,代表还有消息可以拉取,唤醒其他长轮询请求

未拉取到消息,开启长轮询,挂起当前请求。

拉消息细节

PopMessageProcessor#popMsgFromQueue:

pop虽然让consumer侧队列共享,但是在broker侧拉消息请求还是得保证队列独占

topic+consumerGroup+queueId有互斥锁保护。(细节不深入)

一个queue,同时组内多个consumer发来pop请求,只有一个能成功拉取。

假设场景:

客户端c1的一次pop请求,所有queue都没成功获取锁,那么最终会导致进入长轮询。

其他客户端的pop请求,成功获取锁,如果最后有消息没拉完,即restNum>0,那么将唤醒c1。

这个就有点像JDK的AQS的共享模式,AbstractQueuedSynchronizer#acquireShared。

接下来,单线程处理一个队列的拉消息逻辑

PopMessageProcessor#popMsgFromQueue:

首先查询pop消费进度(后面再看),然后判断是否已经拉满32条,如果拉满直接返回。

PopMessageProcessor#popMsgFromQueue:

根据getPopOffset得到的消费进度拉消息,得到GetMessageResult,总体逻辑大致了解(按4.x理解)。

PopMessageProcessor#popMsgFromQueue:拉到消息

  1. 记录checkpoint
  2. 构建startOffsetInfo,拼接上当前queue和这批消息的逻辑offset开始位置,用于响应客户端
  3. 构建msgOffsetInfo,拼接上当前queue和每条消息对应逻辑offset,用于响应客户端

CheckPoint

PopCheckPoint 记录一次pop请求针对某个queue成功拉到消息的情况

PopMessageProcessor#appendCheckPoint:

PopBufferMergeService#addCkJustOffset:

  1. 存储checkpoint;
  2. 内存缓存lockKey(topic+consumerGroup+queueId)对应checkpoint列表;
  3. 内存缓存checkpoint唯一键(topic+consumerGroup+queueId+起始消费进度+pop请求时间戳+broker名)对应checkpoint;

PopBufferMergeService#putCkToStore:

本质上checkpoint的存储方式,还是发一个消息到一个系统topic=rmq_sys_REVIVE_LOG_{clusterName} ,tag=ck

checkpoint消息包含几个特殊的属性:

  1. deliverTimeMs:pop请求时间+invisibleTime(60s)-1;
  2. checkpoint唯一键

收到消息(Public线程)

DefaultMQPushConsumerImpl#popMessage:

consumer的io线程收到消息后,交给public线程处理。

DefaultMQPushConsumerImpl#popMessage$PopCallback:和4.x逻辑差不多

  1. 反序列化解析为PopResult
  2. 投递一个ConsumeRequest到consume线程池。和pull模式一致,每个消费组consumer实例一个线程池,默认20线程+LinkedBlockingQueue;
  3. 立即发起下一次长轮询,即继续提交PopRequest到PullMessage线程池;

ConsumeMessagePopConcurrentlyService.ConsumeRequest

  • msgs:默认批处理大小为1,就一条消息;
  • processQueue:Pop处理队列;
  • messageQueue:分配队列;
  • popTime:broker收到pop请求的时间戳;
  • invisibleTime:consumer发送pop请求时设置,默认60s;

Consume

consumer

ConsumeRequest#run:执行用户Listener业务代码,如果pop未超时,处理消费结果。

ConsumeRequest#isPopTimeout:

如果broker收到pop请求(popTime )后超过60s(invisibleTime)还未执行用户消费逻辑,

则本次pop请求认为超时,不会执行用户消费逻辑,也不会处理消费结果。

ConsumeMessagePopConcurrentlyService#processConsumeResult:

pop消费成功,循环处理每一条被ack的消息。

回顾传统pull模式,还需要移除ProcessQueue中缓存的message,而PopProcessQueue几乎只是个pojo。

DefaultMQPushConsumerImpl#ackAsync:

consumer异步ack(ACK_MESSAGE)一条消息的offset,和拉消息一样也仅支持发送请求给master。

回顾传统pull模式,这里是提交offset到本地缓存,pull请求或后台定时向broker提交offset(UPDATE_CONSUMER_OFFSET)。

broker

AckMessageProcessor#processRequest:

broker收到ack后,发送一个AckMsg 到topic=rmq_sys_REVIVE_LOG_{clusterName} ,tag=ack

queueId为之前pull消息阶段存储的checkpoint消息(tag=ck)的queueId(rqId)。

checkpoint和ack消息发送到同一个队列,前者tag是ck,后者tag是ack

broker消费进度管理

PopBufferMergeService扫描

回顾Pull消息流程,在broker侧除了回复consumer之外做了三件事情。

PopBufferMergeService#addCkJustOffset:

  1. 存储checkpoint,即发送ck消息到topic=rmq_sys_REVIVE_LOG_{clusterName};
  2. 内存缓存lockKey(topic+consumerGroup+queueId)对应checkpoint列表,即PopBufferMergeService#commitOffsets
  3. 内存缓存checkpoint唯一键(topic+consumerGroup+queueId+起始消费进度+pop请求时间戳+broker名)对应checkpoint,即PopBufferMergeService#buffer

PopBufferMergeService 同时也是个后台线程ServiceThread 负责管理offset

PopBufferMergeService 每隔5ms扫描内存中的buffercommitOffsets进行offset提交。

PopBufferMergeService#scan:

先扫描buffer,后扫描commitOffsets。

buffer扫描

PopBufferMergeService#scan:

对于checkpoint存储成功,buffer可以移除

PopBufferMergeService#scan:

实际在pull消息时,ck消息允许发送不成功 ,此时在扫描buffer时重新尝试存储checkpoint

commitOffsets扫描

PopBufferMergeService#scanCommitOffset:

消费commitOffsets内存队列,在checkpoint存储成功的情况下,尝试提交offset

这意味着,在consumer从broker拉消息成功之后,offset就可能会被提交

类似于自动提交,至于怎么保证at least once,依靠后续补偿手段。

注:为什么在拉消息后自动提交,而不是在consumer ack时提交,

PopBufferMergeService#commitOffset:

获取topic+consumerGroup+queueId的互斥锁之后(与同队列拉消息pop请求互斥),提交offset到内存offsetTable

查询offset

PopMessageProcessor#getPopOffset:broker在pop拉消息时查询offset的逻辑。

1)先查内存offsetTable消费进度,即底层消费进度;

2)如果底层消费进度不存在,根据initMode设置初始消费进度,默认MAX,即最大逻辑offset,可选MIN,即最小逻辑offset;

3)最后查询PopBufferMergeService中的commitOffsets进度,取最大值作为本次拉取消息的offset;

PopBufferMergeService#getLatestOffset:

PopBufferMergeService取commitOffsets中最后一个checkpoint的下一个offset返回。

enablePopBufferMerge

通过设置enablePopBufferMerge =true(默认false),在拉消息和ack消息时,可以不发送ck和ack消息,仅仅将数据更新到buffer和commitOffsets中。

PopBufferMergeService#addCk:

broker接收pop请求拉消息,仅仅将checkppint放入buffer和commitOffsets。

PopBufferMergeService#addAk:

broker接收ack请求,匹配buffer中的checkpoint,设置bitmap中ack进度(拉一次消息最多同一个队列拉32条,32条消息对应一个checkpoint,一个ack可更新其中1个bit代表消费成功)。

PopBufferMergeService#scan:

PopBufferMergeService线程扫描时,根据isCkDone方法判断ck是否收到ack,提交消费进度。

PopBufferMergeService#scanCommitOffset:

但开启enablePopBufferMerge的问题是,如果部分消息消费慢(或网络问题收不到ack),会导致消费进度无法提交

PopBufferMergeService#scan:

对于这种情况,在broker侧就只能通过一些超时时间来控制。

当超时,broker存储checkpoint和ack,直接跳过这条消息,不深入分析。

重试

在pull模式中,重试由consumer发起:

1)将offset直接提交;

2)发送CONSUMER_SEND_MSG_BACK请求,将消息重新投递;

而在pop模式中,offset在pull之后就可能就由broker在PopBufferMergeService后台线程中提交了。

consumer通过changePopVisibleTime的方式进行重试。

consumer changePopVisibleTime

ConsumeMessagePopConcurrentlyService#processConsumeResult:

在consumer侧,如果消费失败,根据情况执行changePopVisibleTime。

如果重试未超过16次,直接执行changePopVisibleTime;

如果重试大于等于16次,根据消息发送时间决策是否执行changePopVisibleTime;

ConsumeMessagePopConcurrentlyService#checkNeedAckOrDelay:

如果消息重试超过16次,且距离消息发送时间超出4h,直接ack消息

如果消息重试超过16次,但是距离消息发送时间不超出4h,根据距离消息发送时间,找延迟级别,执行changePopInvisibleTime。

DefaultMQPushConsumerImpl#changePopInvisibleTimeAsync:

根据delayLevel延迟级别转换得到一个invisibleTime,发送CHANGE_MESSAGE_INVISIBLETIME请求。

broker changePopVisibleTime

ChangeInvisibleTimeProcessor#processRequest:

对于CHANGE_MESSAGE_INVISIBLETIME请求,broker做了两件事情:

  1. 发送一个新的ck消息,存储checkpoint;
  2. 对于老的ck消息,发送一个对应的ack消息;

补偿

拉消息自动提交offset,提高了消费处理速度,但是无法正确判断consumer是否消费成功

上述流程中,ck和ack消息的作用是为了补偿无法正确处理消费进度的问题,比如:

1)broker拉消息自动提交,consumer消费成功,但broker未收到ACK_MESSAGE;

此时只有ck消息,不存在ack消息,无法判断消费者是否真的消费成功

2)broker拉消息自动提交,但consumer消费失败,broker收到CHANGE_MESSAGE_INVISIBLETIME;

此时有ck和ack消息(也可能没有ack,被try-catch了,那么情况同1),还有一条新的ck消息,新ck消息的invisibleTime是根据delayLevel计算得到的;

3)broker拉消息自动提交,但consumer消费失败,broker未收到CHANGE_MESSAGE_INVISIBLETIME;

此时只有ck消息,不存在ack消息,无法判断消费者是否真的消费成功

在AckMessageProcessor中构建了n(默认8)个PopReviveService线程。

每个PopReviveService 负责消费receive topic中的一个队列,匹配ck消息和ack消息

如果未匹配,代表客户端消费超时或失败。

注:其实这个和事务消息回查机制就比较像了。

PopReviveService线程:

  1. 拉取receive消息,匹配ck和ack;
  2. 对于超出invisibleTime的原始消息,重试,提交receive消费进度;

ConsumeReviveObj每轮批处理的上下文对象:

  • map:本次捞取的checkpoint;
  • sortList:根据receive topic的消息offset排序的checkpoint;
  • oldOffset:本轮receive topic消费初始进度;
  • newOffset:本轮receive topic消费最终进度;
  • endTime:最后一条receive消息的deliverTimeMs(大概是这个意思,并不严格),即popTime+invisibleTime;

拉取receive消息

PopReviveService#consumeReviveMessage:

以group=CID_RMQ_SYS_REVIVE_GROUP,获取receive topic对应队列下的消息。

PopReviveService#consumeReviveMessage:

将拉取到的receive消息,放入ConsumeReviveObj.map,同时ack消息匹配ck消息(bitmap)。

消费receive消息

PopReviveService#mergeAndRevive:

循环处理每个checkpoint,对于超出invisibleTime(broker收到pop请求超过60s)的checkpoint需要处理,最终提交receive topic的消费进度。

PopReviveService#reviveMsgFromCk:

如果ck未匹配ack,即实际消费情况未知,需要查询原始消息并重新发送消费重试changeInvisibleTime正是通过这里实现

PopReviveService#reviveRetry:

发送重试消息,topic=拉消息提到过的 %RETRY%{group}_{topic} ,队列id固定0(一个topic只有一个队列)。

注:最后也会唤醒长轮询客户端。

总结

Rebalance

使用pop消费的前提是,开启broker rebalance,客户端需要满足下面4个条件:

  1. 使用push api:DefaultMQPushConsumer;
  2. 关闭clientRebalance:DefaultMQPushConsumer#setClientRebalance(false);
  3. 并发消费:MessageListenerConcurrently;
  4. 集群消费:MessageModel.CLUSTERING;

broker rebalance

broker根据队列数量consumer数量popShareQueueNum的关系执行rebalance。

case1:popShareQueueNum=-1(默认)popShareQueueNum >= consumer实例数量-1

broker为每个consumer全量分配queue,实现方式是返回master broker数量个queueId=-1的MessageQueue;

case2:popShareQueueNum < consumer实例数量-1consumer数量 <= queue数量

broker为每个consumer执行多次分配策略

比如默认使用平均分配策略,8个queue,3个client,popShareQueueNum=1。

index=0的client分配q0-q5,index=1的client分配q3-q7,index=2的client分配q6-q7和q0-q2。

case3:popShareQueueNum < consumer实例数量-1consumer数量 > queue数量

每个consumer分配一个queue,仍然是队列独占

consumer processQueue

consumer收到broker rebalance分配的MessageQueue。

每个MessageQueue对应一个PopProcessQueue,不再像ProcessQueue一样需要缓存Message管理offset。

Pull

consumer针对每个PopProcessQueue,提交一个PopRequest从broker拉取消息,但是PopRequest不包含offset,默认还是最大拉32条消息。

broker在不满32条消息的情况下,从多个队列拉消息。

  1. 20%概率,先拉pop重试topic=%RETRY%{consumerGroup}_{topic};
  2. 如果queueId=-1(默认),即分配所有队列,从随机下标开始的queueId开始拉取消息,直到满32;
  3. 如果queueId非-1,从指定queueId拉取消息;
  4. 在没有拉过pop重试topic消息的情况下(80%),如果没满32,再拉pop重试topic;

对于每个拉消息的方法:

  1. 先要获取queue级别互斥锁 ,保证同一个队列【按顺序】分配n条消息给不同consumer
  2. 读取内存消费进度,包含底层offsetTable和pop缓存commitOffsets两部分逻辑;
  3. 读消息;
  4. 记录checkpoint
  5. 释放queue级别互斥锁;

PopCheckPoint 可以代表一次pop请求针对某个queue成功拉到消息的情况

记录checkpoint包含三个事情:

  1. 一个ck消息,topic=rmq_sys_REVIVE_LOG_{clusterName} ,tag=ck,body=PopCheckPoint,用于自动提交补偿;
  2. 内存缓存lockKey(topic+consumerGroup+queueId)对应checkpoint列表,即PopBufferMergeService#commitOffsets,用于offset提交;
  3. 内存缓存checkpoint唯一键(topic+consumerGroup+queueId+起始消费进度+pop请求时间戳+broker名)对应checkpoint,即PopBufferMergeService#buffer,用于保证ck消息持久化;

对于没有获取到消息的pop请求(可能因为获取queue级别互斥锁失败),会被挂起,等待新消息到来或长轮询超时(这部分忽略了)。

Consume

消费成功

consumer发送一个ACK_MESSAGE请求给broker。

broker存储一个ack消息,topic=rmq_sys_REVIVE_LOG_{clusterName} ,tag=ack,body=AckMessage。

消费失败

consumer在不超出次数和时间限制的情况下,发送一个CHANGE_MESSAGE_INVISIBLETIME请求给broker。

其中根据失败次数,设置invisibleTime从10s到2h

broker:

  1. 存储一个新的ck消息,invisibleTime根据consumer请求指定
  2. 针对老ck消息存储一个ack消息

消费进度管理

broker开启1个PopBufferMergeService线程,管理所有队列的pop消费进度。

PopBufferMergeService扫描内存PopBufferMergeService#buffer,针对未持久化的checkpoint补偿执行持久化(pull的时候允许ck消息存储失败),持久化成功的checkpoint从buffer中移除。

PopBufferMergeService扫描内存PopBufferMergeService#commitOffsets ,针对持久化成功的checkpoint,在获取queue级别互斥锁 的情况下(与Pull消息互斥 ), 【按顺序】提交offset (内存offsetTable,异步刷盘)。可认为是一种自动提交,即pull消息成功直接提交。

针对pop消费进度查询,会取max(offsetTable,commitOffsets)。

补偿

多种情况下无法确认consumer实际消费结果(类似于事务消息的处理思路) ,所以引入ck和ack消息。

ck消息,代表consumer收到消息,ack消息,代表consumer消费成功。

针对topic=rmq_sys_REVIVE_LOG 的每个队列(默认8个),开启一个PopReviveService线程消费。

只有当ck在invisibleTime(处理pop请求后60s内)时间内收到对应ack消息,才认为consumer实际消费成功 ,否则将重新投递原始消息,投递topic= %RETRY%{group}_{topic} ,队列id固定0。

相关推荐
徐*红2 分钟前
java 线程池
java·开发语言
尚学教辅学习资料2 分钟前
基于SSM的养老院管理系统+LW示例参考
java·开发语言·java毕设·养老院
2401_857636392 分钟前
计算机课程管理平台:Spring Boot与工程认证的结合
java·spring boot·后端
1 9 J4 分钟前
Java 上机实践4(类与对象)
java·开发语言·算法
Code apprenticeship5 分钟前
Java面试题(2)
java·开发语言
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
憨子周1 小时前
2M的带宽怎么怎么设置tcp滑动窗口以及连接池
java·网络·网络协议·tcp/ip
霖雨3 小时前
使用Visual Studio Code 快速新建Net项目
java·ide·windows·vscode·编辑器
SRY122404193 小时前
javaSE面试题
java·开发语言·面试
Fiercezm3 小时前
JUC学习
java