RocketMQ——Consumer篇:PUSH模式下消费消息(顺序和并发两种)

1 接受并处理Broker返回的响应消息

当发送拉取消息在Broker返回响应消息之后调用NettyRemotingAbstract.processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg)方法,大致逻辑如下:

1、根据返回的响应对象RemotingCommand的opaque(请求序列号)从NettyRemotingAbstract.responseTable: ConcurrentHashMap变量中获取ResponseFuture对象(在发送之前存入的该对象);

2、若该ResponseFuture对象为null,则啥也不干,就打警告日志;

3、若该ResponseFuture对象不为null,则将响应对象RemotingCommand赋值给ResponseFuture.responseCommand变量;

4、若ResponseFuture.invokeCallback:InvokeCallback变量不为空(在异步发送的情况下该变量不为空),则首先获取NettyRemotingClient.publicExecutor线程池,若存在该线程池则初始化Runnable匿名线程,将该匿名线程提交到线程池中;该匿名线程的run方法主要是调用ResponseFuture.executeInvokeCallback()方法;若没有该线程池则直接在主线程中调用ResponseFuture.executeInvokeCallback()方法;在executeInvokeCallback方法中,先确保ResponseFuture.executeCallbackOnlyOnce的值为false并且成功更新为true,则再执行InvokeCallback.operationComplete(ResponseFuture)方法,在该方法内部调用回调类PullCallback对象的onSuccess方法;由于executeCallbackOnlyOnce在初始化时为false,若更新失败说明该回调方法已经执行过了,故不在执行。

5、若ResponseFuture.invokeCallback:InvokeCallback变量为空(在同步方式拉取消息的情况下),则调用ResponseFuture.putResponse (RemotingCommand responseCommand)方法首先将响应对象RemotingCommand赋值给ResponseFuture.responseCommand变量,然后唤醒ResponseFuture.waitResponse方法的等待;

2 消费消息的回调类(PullCallback)

该PullCallback类是DefaultMQPushConsumerImpl.pullMessage (PullRequest pullRequest)方法中的匿名内部类,采用异步方式拉取消息时,在收到Broker的响应消息之后,回调该方法执行业务调用者的回调逻辑。

一、 onSucess方法

在收到响应消息之后,先回调InvokeCallback匿名类的operationComplete (ResponseFuture responseFuture)方法,在正常情况下会回调PullCallback类的onSucess方法,大致逻辑如下:

1、调用PullAPIWrapper.processPullResult(MessageQueue mq,PullResult

pullResult, SubscriptionData subscriptionData)方法处理拉取消息的返回对象PullResult,大致逻辑如下:

1.1)调用PullAPIWrapper.updatePullFromWhichNode(MessageQueue mq, long brokerId)方法用Broker返回的PullResultExt.suggestWhichBrokerId变量值更新PullAPIWrapper.pullFromWhichNodeTable:ConcurrentHashMap 变量中当前拉取消息PullRequest.messageQueue对象对应的BrokerId。若以messageQueue为key值从pullFromWhichNodeTable中获取的BrokerId为空则将PullResultExt.suggestWhichBrokerId存入该列表中,否则更新该MessageQueue对应的value值为suggestWhichBrokerId;

1.2)若pullResult.status=FOUND,则继续下面的处理逻辑,否则设置PullResultExt.messageBinary=null并返回该PullResult对象;

1.3)对PullResultExt.messageBinary变量进行解码,得到MessageExt列表;

1.4) Consumer端消息过滤。若SubscriptionData.tagsSet集合(在5.5.1小节中拉取消息之前以topic获取的订阅关系数据)不为空并且SubscriptionData. classFilterMode为false(在初始化DefaultMQPushConsumer时可以设置这两个值),则遍历MessageExt列表,检查每个MessageExt对象的tags值(在commitlog数据的properties字段的"TAGS"属性值)是否在SubscriptionData.tagsSet集合中,只保留MessageExt.tags此tagsSet集合中的MessageExt对象,构成新的MessageExt列表,取名msgListFilterAgain;否则新的列表msgListFilterAgain等于上一步的MessageExt列表;

Consumer收到过滤后的消息后,同样也要执行在Broker端的操作,但是比对的是真实的Message Tag字符串,而不是hashCode。因为在Broker端为了节约空间,过滤规则是存储的HashCode,为了避免Hash冲突而受到错误消息,在Consumer端还进行一次具体过滤规则的过滤,进行过滤修正。

1.5)检查PullAPIWrapper.filterMessageHookList列表是否为空(可在应用层通过DefaultMQPullConsumerImpl.registerFilterMessageHook (FilterMessageHook hook)方法设置),若不为空则调用该列表中的每个FilterMessageHook对象的filterMessage方法;由应用层实现FilterMessageHook接口的filterMessage方法,可以在该方法中对消息再次过滤;

1.6)向NameServer发送GET_KV_CONFIG请求码获取NAMESPACE_PROJECT_CONFIG和本地IP下面的value值,赋值给projectGroupPrefix变量。若该值为空则将PullResult.minOffset和PullResult.maxOffset值设置到每个MessageExt对象的properties属性中,其中属性名称分别为"MIN_OFFSET"和"MAX_OFFSET";若不为空则除了将PullResult.minOffset和PullResult.maxOffset值设置到每个MessageExt对象的properties属性中之外,还从projectGroupPrefix变量值开头的topic中去掉projectGroupPrefix值部分,然后将新的topic设置到MessageQueue、SubscriptionData的topic以及每个MessageExt对象的topic变量;

1.7)将新组建的MessageExt列表msgListFilterAgain赋值给PullResult.msgFoundList变量;

1.8)设置PullResultExt.messageBinary=null,并返回该PullResult对象;

2、下面根据PullResult.status变量的值执行不同的业务逻辑,若PullResult.status=FOUND,大致逻辑如下:

2.1)该PullRequest对象的nextOffset变量值表示本次消费的开始偏移量,赋值给临时变量prevRequestOffset;

2.2)取PullResult.nextBeginOffset的值(Broker返回的下一次消费进度的偏移值)赋值给PullRequest.nextOffset变量值;

2.3)若PullResult.MsgFoundList列表为空,则调用DefaultMQPushConsumerImpl.executePullRequestImmediately(PullRequest pullRequest)方法将该拉取请求对象PullRequest重新延迟放入PullMessageService线程的pullRequestQueue队列中,然后跳出该onSucess方法;否则继续下面的逻辑;

2.4)调用该PullRequest.ProcessQueue对象的putMessage(List msgs)方法,将MessageExt列表存入ProcessQueue.msgTreeMap:TreeMap变量中,放入此变量的目的是:第一在顺序消费时从该变量列表中取消息进行消费,第二可以用此变量中的消息做流控;大致逻辑如下:

A)遍历List列表,以每个MessageExt对象的queueOffset值为key值,将MessageExt对象存入msgTreeMap:TreeMap变量中;该变量类型根据key值大小排序;

B)更新ProcessQueue.msgCount变量,记录消息个数;

C)经过第A步处理之后,若msgTreeMap变量不是空并且ProcessQueue.consuming为false(初始化为false)则置consuming为true(在该msgTreeMap变量消费完之后再置为false)、置临时变量dispatchToConsume为true;否则置临时变量dispatchToConsume为false表示没有待消费的消息或者msgTreeMap变量中存入了数据还未消费完,在没有消费完之前不允许在此提交消费请求,在消费完msgTreeMap之后置consuming为false;

D)取List列表的最后一个MessageExt对象,该对象的properties属性中取MAX_OFFSET的值,减去该MessageExt对象的queueOffset值,即为Broker端该topic和queueId下消息队列中未消费的逻辑队列的大小,将该值存入ProcessQueue.msgAccCnt变量,用于MQClientInstance. adjustThreadPool()方法动态调整线程池大小(在MQClientInstance中启动定时任务定时调整线程池大小);

E)返回临时变量dispatchToConsume值;

2.5)调用ConsumeMessageService.submitConsumeRequest(List msgs,ProcessQueue processQueue,MessageQueue messageQueue, boolean dispathToConsume)方法,其中dispathToConsume的值由上一步所得,在顺序消费时使用,为true表示可以消费;大致逻辑如下:

A)若是 顺序消费 ,则调用ConsumeMessageOrderlyService. submitConsumeRequest(List msgs, ProcessQueue processQueue, MessageQueue messageQueue, boolean dispathToConsume)方法;在该方法中,若上次的msgTreeMap变量中的数据还未消费完(即在2.4步中返回dispathToConsume=false)则不执行任何逻辑;若dispathToConsume=true(即上一次已经消费完了)则以ProcessQueue和MessageQueue对象为参数初始化ConsumeMessageOrderlyService类的内部线程类ConsumeRequest;然后将该线程类放入ConsumeMessageOrderlyService.consumeExecutor线程池中。从而可以看出顺序消费是从ProcessQueue对象的TreeMap树形列表中取消息的。

B)若是 并发消费 ,则调用ConsumeMessageConcurrentlyService.submitConsumeRequest(List msgs, ProcessQueue processQueue, MessageQueue messageQueue, boolean dispatchToConsume)方法;在该方法中,则根据批次中最大信息条数(由DefaultMQPushConsumer.consumeMessageBatchMaxSize设置,默认为1)来决定是否分提交到线程池中,大致逻辑为:首先比较List列表的个数是否大于了批处理的最大条数,若没有则以该List队列、ProcessQueue对象、MessageQueue对象初始化ConsumeMessageConcurrentlyService的内部类 ConsumeRequest 的对象,并放入ConsumeMessageConcurrentlyService.consumeExecutor线程池中;否则遍历List列表,每次从List列表中取consumeMessageBatchMaxSize个MessageExt对象构成新的List列表,然后以新的MessageExt队列、ProcessQueue对象、MessageQueue对象初始化ConsumeMessageConcurrentlyService的内部类 ConsumeRequest 的对象并放入ConsumeMessageConcurrentlyService.consumeExecutor线程池中,直到该队列遍历完为止。从而可以看出并发消费是将从Broker获取的MessageExt消息列表分配到各个 ConsumeRequest 线程中进行并发消费。

2.6)检查拉取消息的间隔时间(DefaultMQPushConsumer.pullInterval,默认为0),若大于0,则调用DefaultMQPushConsumerImpl. executePullRequestLater方法,在间隔时间之后再将PullRequest对象放入PullMessageService线程的pullRequestQueue队列中;若等于0(表示立即再次进行拉取消息),则调用DefaultMQPushConsumerImpl. executePullRequestImmediately方法立即继续下一次拉取消息,从而形成一个循环不间断地拉取消息的过程;

3、若PullResult.status=NO_NEW_MSG或者NO_MATCHED_MSG时:

3.1)取PullResult.nextBeginOffset的值(Broker返回的下一次消费进度的偏移值)赋值给PullRequest.nextOffset变量值;

3.2)更新消费进度offset。调用DefaultMQPushConsumerImpl.correctTagsOffset(PullRequest pullRequest)方法。若没有获取到消息(即ProcessQueue.msgCount等于0)则更新消息进度。对于LocalFileOffsetStore或RemoteBrokerOffsetStore类,均调用updateOffset(MessageQueue mq, long offset, boolean increaseOnly)方法,而且方法逻辑是一样的,以MessageQueue对象为key值从offsetTable:ConcurrentHashMap变量中获取values值,若该values值为空,则将MessageQueue对象以及offset值(在3.1步中获取的PullResult.nextBeginOffset值)存入offsetTable变量中,若不为空,则比较已经存在的值,若大于已存在的值才更新;

3.3)调用DefaultMQPushConsumerImpl.executePullRequestImmediately方法立即继续下一次拉取;

4、若PullResult.status=OFFSET_ILLEGAL

4.1)取PullResult.nextBeginOffset的值(Broker返回的下一次消费进度的偏移值)赋值给PullRequest.nextOffset变量值;

4.2)设置PullRequest.processQueue.dropped等于true,将此该拉取请求作废;

4.3)创建一个匿名Runnable线程类,然后调用DefaultMQPushConsumerImpl.executeTaskLater(Runnable r, long timeDelay)方法将该线程类放入PullMessageService.scheduledExecutorService: ScheduledExecutorService调度线程池中,在10秒钟之后执行该匿名线程类;该匿名线程类的run方法逻辑如下:

A)调用OffsetStore.updateOffset(MessageQueue mq, long offset, boolean increaseOnly)方法更新更新消费进度offset;

B)调用OffsetStore.persist(MessageQueue mq)方法:对于广播模式下offsetStore初始化为LocalFileOffsetStore对象,该对象的persist方法没有处理逻辑;对于集群模式下offsetStore初始化为RemoteBrokerOffsetStore对象,该对象的persist方法中,首先以入参MessageQueue对象为key值从RemoteBrokerOffsetStore.offsetTable: ConcurrentHashMap变量中获取偏移量offset值,然后调用updateConsumeOffsetToBroker(MessageQueue mq, long offset)方法向Broker发送UPDATE_CONSUMER_OFFSET请求码的消费进度信息;
C)以PullRequest对象的messageQueue变量为参数调用RebalanceImpl.removeProcessQueue(MessageQueue mq)方法,在该方法中,首先从RebalanceImpl.processQueueTable: ConcurrentHashMap变量中删除MessageQueue记录并返回对应的ProcessQueue对象;然后该ProcessQueue对象的dropped变量设置为ture;最后以MessageQueue对象和ProcessQueue对象为参数调用removeUnnecessaryMessageQueue方法删除未使用的消息队列的消费进度,具体逻辑详见5.3.4小节;

二、 onException方法

在发送消息失败或者等待响应超时或者其他异常时回调该onException方法。在该方法中,调用DefaultMQPushConsumerImpl.executePullRequestLater方法,3秒钟之后再将该PullRequest请求重新放入PullMessageService线程的pullRequestQueue队列中;

3 顺序消费(ConsumeMessageOrderlyService)

对于顺序消费的三把锁:1)首先在ConsumeMessageOrderlyService类中定义了定时任务每隔20秒执行一次lockMQPeriodically()方法,获取该Consumer端在Broker端锁住的MessageQueue集合(即分布式锁),并将RebalanceImpl.processQueueTable:ConcurrentHashMap集合中获得分布式锁的MessageQueue对象(消费队列)对应的ProcessQueue对象(消费处理队列)加上本地锁(即该对象的lock等于ture)以及加锁的时间,目的是为了在消费时在本地检查消费队列是否锁住;2)在进行消息队列的消费过程中,对MessageQueue对象进行本地同步锁,保证同一时间只允许一个线程消息一个ConsumeQueue队列;3)在回调业务层定义的ConsumeMessageOrderlyService.messageListener:MessageListenerOrderly类的consumeMessage方法之前获取ProcessQueue.lockConsume:ReentrantLock变量的锁即消费处理队列的锁,该锁的粒度比消息队列的同步锁粒度更小,该锁的目的是保证在消费的过程中不会被解锁。

3.1 回调业务层定义的消费方法

在回调DefaultMQPushConsumerImpl.pullMessage方法中的内部类PullCallback.onSucess方法时,调用ConsumeMessageOrderlyService. submitConsumeRequest方法提交消费请求ConsumeRequest对象,该消费请求就是在ConsumeMessageOrderlyService类的内部定义了ConsumeRequest线程类,将此对象提交到ConsumeMessageOrderlyService类的线程池中,由该线程完成回调业务层定义的消费方法,该内部线程类的run方法逻辑如下:

1、检查ProcessQueue.dropped是否为true,若不是则直接返回;

2、对MessageQueue对象加锁,保证同一时间只允许一个线程使用该MessageQueue对象。调用ConsumeMessageOrderlyService.messageQueueLock. fetchLockObject(MessageQueue mq)获取锁对象。下面的处理逻辑均在获得该Object对象的互斥锁(synchronized)后进行处理;从而保证了在并发情况下一个MessageQueue对象只有一个线程使用,大致逻辑为:

2.1)根据MessageQueue对象从MessageQueueLock.mqLockTable: ConcurrentHashMap中获取Object对象,若获取到Object对象不为null,则直接返回该对象;

2.2)若获取的Object对象为null,则创建一个Object对象,并保存到mqLockTable变量中,为了防止并发采用putIfAbsent方法存入该列表中,若已经有该MessageQueue对象,则返回已经存在的Object对象,若不为空,则返回该已存在的Object对象;

3、若消息模式是广播或者对应的ProcessQueue.locked等于true且锁的时间未过期(根据获取锁locked的时候设置的lastLockTimestamp值来判断),则初始化局部变量continueConsume=true,然后无限期的循环执行下面的逻辑,直到局部变量continueConsume=false为止或者跳出循环,便终止了该方法的执行,否则执行第4步操作;

3.1)执行for循环下面的逻辑,直到该continueConsume等于false或者直接跳出循环为止;

3.2)检查ProcessQueue.dropped是否为true,若不是则跳出循环;

3.3)若消息模式是集群并且ProcessQueue.locked不等于true(未锁住)或者锁住了但是锁已经超时,则调用ConsumeMessageOrderlyService.tryLockLaterAndReconsume(MessageQueue mq, ProcessQueue processQueue, long delayMills)方法,在该方法中初始化一个线程并将线程放入ConsumeMessageOrderlyService. scheduledExecutorService线程池中,然后跳出循环。该线程在延迟delayMills毫秒之后被执行,该线程的功能是获取MessageQueue队列的分布式锁,然后再调用ConsumeMessageOrderlyService.submitConsumeRequestLater (MessageQueue mq, ProcessQueue processQueue, long delayMills)方法。

3.4)若该循环从开始到现在连续执行的时间已经超过的最大连续执行值(由property属性中的"rocketmq.client.maxTimeConsumeContinuously"设定,默认为60秒),则调用ConsumeMessageOrderlyService. submitConsumeRequestLater(MessageQueue mq, ProcessQueue processQueue, long delayMills)方法,其中delayMills=100,表示在100毫秒之后再调用ConsumeMessageOrderlyService.submitConsumeRequest(List msgs, ProcessQueue processQueue, MessageQueue messageQueue, boolean dispathToConsume)方法,在该方法中重新创建ConsumeRequest对象,并放入ConsumeMessageOrderlyService.consumeExecutor线程池中重新被消费;

3.4)获取一次批量消费的消息个数batchSize(由参数DefaultMQPushConsumer.consumeMessageBatchMaxSize指定,默认为1),然后调用ProcessQueue.takeMessags(int batchSize),在该方法中,从ProcessQueue.msgTreeMap变量中获取batchSize个数的List列表;并且从msgTreeMap中删除,存入临时变量msgTreeMapTemp中,返回List列表,若该列表为空,表示该msgTreeMap列表中的消息已经消费完了,置ProcessQueue.consuming等于false;

3.5)检查返回的List列表是否为空,若为空,则置局部变量continueConsume=false表示不在循环执行,本次消息队列的消费结束;否则继续执行下面的步骤;

3.6)检查DefaultMQPushConsumerImpl.consumeMessageHookList: ArrayList是否为空,若不是,则初始化ConsumeMessageContext对象,并调用ArrayList列表中每个ConsumeMessageHook对象的consumeMessageBefore (ConsumeMessageContextcontext)方法,该ArrayList列表由业务层调用 DefaultMQPushConsumerImpl.registerConsumeMessageHook (ConsumeMessageHook hook)方法设置;

3.7)执行业务层定义的消费消息的业务逻辑并返回消费结果。先调用ProcessQueue. lockConsume:ReentrantLock变量的lock方法获取锁(目的是防止在消费的过程中,被其他线程将此消费队列解锁了,从而引起并发消费的问题),然后调用ConsumeMessageOrderlyService. messageListener:MessageListenerOrderly类的consumeMessage方法,保证同一个ProcessQueue在同一时间只能有一个线程调用consumeMessage方法,由应用层实现该MessageListenerOrderly接口的consumeMessage方法,执行完成之后调用调用ProcessQueue. lockConsume:ReentrantLock变量的unlock方法释放锁;

3.8)当consumeMessage方法的返回值status为空时,将结果状态status赋值为ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;

3.9)检查DefaultMQPushConsumerImpl.consumeMessageHookList: ArrayList是否为空,若不是,则初始化ConsumeMessageContext对象,并调用ArrayList列表中每个ConsumeMessageHook对象的consumeMessageAfter (ConsumeMessageContextcontext)方法,该ArrayList列表由业务层调用DefaultMQPushConsumerImpl

.registerConsumeMessageHook(ConsumeMessageHook hook)方法设置;

3.10)处理回调方法consumeMessage的消费结果,并将消费结果赋值给变量continueConsume。调用ConsumeMessageOrderlyService. processConsumeResult(List msgs, ConsumeOrderlyStatus status, ConsumeOrderlyContext context, ConsumeRequest consumeRequest)方法,详见5.7.2小节;

3.11)继续从第3.1步开始遍历,不间断地从ProcessQueue对象的List列表中获取消息对象并消费;直到消费完为止;

4、检查ProcessQueue.dropped是否为true,若不为true则直接返回,否则调用ConsumeMessageOrderlyService.tryLockLaterAndReconsume (MessageQueue mq, ProcessQueue processQueue, long delayMills)方法,其中delayMills=100,即在100毫秒之后重新获取锁后再次进行消费;

3.2 根据消费结果进行相应处理

以回调应用层定义的ConsumeMessageOrderlyService.consumeMessage方法的返回处理结果为参数调用ConsumeMessageOrderlyService. processConsumeResult(List msgs, ConsumeOrderlyStatus status, ConsumeOrderlyContext context, ConsumeRequest consumeRequest) 方法。大致逻辑如下:

1、若ConsumeOrderlyContext.autoCommit为true(默认为true,可以在应用层的实现类MessageListenerOrderly的consumeMessage方法中设置该值);根据consumeMessage方法返回的不同结果执行不同的逻辑:

1.1)若status等于SUCCESS,则调用ProcessQueue.commit()方法,大致逻辑如下:

A)获取ProcessQueue.lockTreeMap:ReentrantReadWriteLock锁对该方法的整个处理逻辑加锁;

B)从msgTreeMapTemp(在从msgTreeMap中获取消息并消费时存入的)中获取最后一个元素的key值,即最大的offset;

C)将ProcessQueue.msgCount值减去临时变量msgTreeMapTemp中的个数,即表示剩下的未消费的消息个数;

D)清空临时变量msgTreeMapTemp列表的值;

E)返回最大的offset+1的值并赋值给局部变量commitOffset值用于更新消费进度之用;

F)然后调用ConsumerStatsManager.incConsumeOKTPS(String group, String topic, long msgs)方法进行消费统计;

1.2)若status等于SUSPEND_CURRENT_QUEUE_A_MOMENT,稍后重新消费。大致逻辑如下:

1.2.1)首先调用ProcessQueue.makeMessageToCosumeAgain (List msgs)方法将List列表的对象重新放入msgTreeMap变量中。大致逻辑如下:

A)获取ProcessQueue.lockTreeMap:ReentrantReadWriteLock锁对该方法的整个处理逻辑加锁;

B)将List列表的对象重新放入msgTreeMap变量中。遍历List列表的每个MessageExt对象,以该对象的queueoffset值为key值从msgTreeMapTemp中删除对应的MessageExt对象;将MessageExt对象以MessageExt对象的queueoffset为key值重新加入到ProcessQueue.msgTreeMap变量中;

1.2.2)然后调用ConsumeMessageOrderlyService. submitConsumeRequestLater(ProcessQueue processQueue, MessageQueue messageQueue, long suspendTimeMillis)方法,其中suspendTimeMillis由ConsumeOrderlyContext.suspendCurrentQueueTimeMillis变量在应用层的回调方法中设定,默认为1000,表示在1000毫秒之后调用ConsumeMessageOrderlyService.submitConsumeRequest(List msgs, ProcessQueue processQueue, MessageQueue messageQueue, boolean dispathToConsume)方法,在该方法中重新创建ConsumeRequest对象,并放入ConsumeMessageOrderlyService.consumeExecutor线程池中;

1.2.3)置局部变量continueConsume=false;

1.2.4)调用ConsumerStatsManager.IncConsumeFailedTPS方法进行消费统计;

2、若ConsumeOrderlyContext.autoCommit为false,根据consumeMessage方法返回的不同状态执行不同的逻辑:

2.1)若status等于SUCCESS,仅调用ConsumerStatsManager.incConsumeOKTPS(String group, String topic, long msgs)方法进行消费统计,并未调用ProcessQueue.commit()方法清理队列数据;

2.2)若status等于COMMIT时,才调用ProcessQueue.commit()方法,并返回消费最大的offset值并赋值给局部变量commitOffset值用于更新消费进度之用;

2.3)若status等于ROLLBACK,表示要重新消费,

A)首先调用ProcessQueue.rollback()方法,将msgTreeMapTemp变量中的内容全部重新放入msgTreeMap变量中,同时清理msgTreeMapTemp变量;

B)然后调用ConsumeMessageOrderlyService.submitConsumeRequestLater (ProcessQueue processQueue, MessageQueue messageQueue, long suspendTimeMillis)方法,其中suspendTimeMillis由ConsumeOrderlyContext. suspendCurrentQueueTimeMillis变量在应用层的回调方法中设定,默认为1000,表示在1000毫秒之后调用ConsumeMessageOrderlyService. submitConsumeRequest(List msgs, ProcessQueue processQueue, MessageQueue messageQueue, boolean dispathToConsume)方法,在该方法中重新创建ConsumeRequest对象,并放入ConsumeMessageOrderlyService. consumeExecutor线程池中;

C)置局部变量continueConsume=false;

2.4)若status等于SUSPEND_CURRENT_QUEUE_A_MOMENT,将msgTreeMapTemp变量中的内容全部重新放入msgTreeMap变量中,然后结束此轮消费(continueConsume=false),等待1秒之后再次提交消费,大致逻辑如下:

A)首先调用ProcessQueue.makeMessageToCosumeAgain(List msgs)方法,详见1.2.1步;

B)然后调用ConsumeMessageOrderlyService.submitConsumeRequestLater (ProcessQueue processQueue, MessageQueue messageQueue, long suspendTimeMillis)方法,其中suspendTimeMillis由ConsumeOrderlyContext. suspendCurrentQueueTimeMillis变量在应用层的回调方法中设定,默认为1000;

C)置局部变量continueConsume=false;

D)调用ConsumerStatsManager.IncConsumeFailedTPS方法进行消费统计;

3、若上面两步得到的commitOffset值大于0,则调用OffsetStore.updateOffset(MessageQueue mq, long offset, boolean increaseOnly)方法更新消费进度。对于LocalFileOffsetStore或RemoteBrokerOffsetStore类,该方法逻辑是一样的,以MessageQueue对象为key值从offsetTable: ConcurrentHashMap变量中获取values值,若该values值为空,则将MessageQueue对象以及commitOffset值存入offsetTable变量中,若不为空,则比较已经存在的值,若大于已存在的值才更新;

4、返回continueConsume值;若该值为false则结束此轮消费。

3.3 重新获取分布式锁后再消费(tryLockLaterAndReconsume)

在集群模式下,若ProcessQueue未锁或者锁已经超时,则调用ConsumeMessageOrderlyService.tryLockLaterAndReconsume(MessageQueue mq, ProcessQueue processQueue, long delayMills)方法从Broker重新获取锁之后再进行消费。

在该方法中初始化一个Runnable匿名线程,并在delayMills毫秒之后再执行该匿名线程,该匿名线程的run方法逻辑如下:

1、先调用ConsumeMessageOrderlyService.lockOneMQ(MessageQueue mq)方法获取MessageQueue队列的锁,向该MessageQueue对象的brokerName下面的主用Broker发送LOCK_BATCH_MQ请求码的请求消息,请求Broker将发送的MessageQueue对象锁住;若该请求的MessageQueue对象在Broker返回的锁住集合中,则锁住成功了;

2、调用ConsumeMessageOrderlyService.submitConsumeRequestLater(ProcessQueue processQueue, MessageQueue messageQueue, long suspendTimeMillis)方法,若锁住成功则suspendTimeMillis=10;若未锁住,则suspendTimeMillis=3000。

3、在ConsumeMessageOrderlyService.submitConsumeRequestLater方法中,初始化一个匿名的Runnable线程类,在suspendTimeMillis毫秒之后,执行该线程类,在该类的run方法中调用ConsumeMessageOrderlyService.submitConsumeRequest(List msgs, ProcessQueue processQueue, MessageQueue messageQueue, boolean dispathToConsume)方法,其中dispathToConsume=true;在该方法中根据ProcessQueue 和MessageQueue对象初始化ConsumeRequest对象,并放入ConsumeMessageOrderlyService.consumeExecutor线程池中;

4 并发消费(ConsumeMessageConcurrentlyService)

4.1 回调业务层定义的消费方法

在回调DefaultMQPushConsumerImpl.pullMessage方法中的内部类PullCallback.onSucess方法时,调用ConsumeMessageConcurrentlyService.submitConsumeRequest方法提交消费请求ConsumeRequest对象,该消费请求对象是在ConsumeMessageConcurrentlyService类的内部定义了ConsumeRequest线程类;将此对象提交到ConsumeMessageConcurrentlyService类的线程池中,由该线程完成回调业务层定义的消费方法,该内部线程类的run方法逻辑如下:

1、检查ProcessQueue.dropped是否为true,若不是则直接返回;

2、检查DefaultMQPushConsumerImpl.consumeMessageHookList: ArrayList是否为空,若不是,则初始化ConsumeMessageContext对象,并调用ArrayList列表中每个ConsumeMessageHook对象的consumeMessageBefore (ConsumeMessageContext context)方法,该consumeMessageHookList变量由业务层调用DefaultMQPushConsumerImpl.registerConsumeMessageHook (ConsumeMessageHook hook)方法设置,由业务层自定义ConsumeMessageHook接口的实现类,实现该接口的consumeMessageBefore(final ConsumeMessageContext context)和consumeMessageAfter(final ConsumeMessageContext context)方法,分别在回调业务层的具体消费方法之前和之后调用者两个方法。

3、遍历List列表,检查每个消息(MessageExt对象)中topic值,若该值等于"%RETRY%+consumerGroup",则从消息的propertis属性中获取"RETRY_TOPIC"属性的值,若该属性值不为null,则将该属性值赋值给该消息的topic值;对于重试消息,会将真正的topic值放入该属性中;

4、调用ConsumeMessageConcurrentlyService.messageListener: MessageListenerConcurrently的consumeMessage方法;该messageListener变量是由DefaultMQPushConsumer.registerMessageListener (MessageListener messageListener)方法在业务层设置的,对于并发消费,则在业务层就要实现MessageListenerConcurrently接口的consumeMessage方法;该回调方法consumeMessage返回ConsumeConcurrentlyStatus. CONSUME_SUCCESS或者ConsumeConcurrentlyStatus.RECONSUME_LATER值;

5、检查上一部分返回值status,若为null,则置status等于ConsumeConcurrentlyStatus.RECONSUME_LATER;

5、检查DefaultMQPushConsumerImpl.consumeMessageHookList: ArrayList是否为空,若不是,则初始化ConsumeMessageContext对象,并调用ArrayList列表中每个ConsumeMessageHook对象的executeHookAfter(ConsumeMessageContext context)方法,该ArrayList列表是应用层设置的回调类;

6、处理消费失败的消息。对于回调方法consumeMessage的执行结果为失败的消息,以内部Producer的名义重发到Broker端用于重试消费。若ProcessQueue.dropped为false,调用ConsumeMessageConcurrentlyService. processConsumeResult(ConsumeConcurrentlyStatus status, ConsumeConcurrentlyContext context, ConsumeRequest consumeRequest)方法,该方法的大致逻辑如下:

6.1)由于ConsumeConcurrentlyContext.ackIndex初始的默认值为Integer.MAX_VALUE,表示消费成功的个数,该变量值可由业务层在执行回调方法失败之后重新设定;若status等于CONSUME_SUCCESS则判断ackIndex是否大于ConsumeRequest中MessageExt列表的个数,如果大于则表示该列表的消息全部消费成功,则将ackIndex置为List列表的个数减1,如果小于则ackIndex表示消费失败的消息在列表的位置;然后对消费成功/失败个数进行统计;若status等于RECONSUME_LATER则表示消息全部消费失败,置ackIndex=-1,并对消费失败个数进行统计;

6.2)若此消息模式为广播(DefaultMQPushConsumer.messageModel设置),则从消费失败的列表位置(ackIndex+1)开始遍历List列表,打印消费失败的MessageExt日志信息;

6.3)若此消息模式为集群(DefaultMQPushConsumer.messageModel设置),则从消费失败的列表位置(ackIndex+1)开始遍历List列表,对于消费失败的MessageExt对象,调用 ConsumeMessageConcurrentlyService.sendMessageBack (MessageExt msg, ConsumeConcurrentlyContext context)方法向Broker发送CONSUMER_SEND_MSG_BACK请求码;对于发送BACK消息失败之后发送RETRY消息也失败的MessageExt消息构建一个临时列表msgBackFailedList;

6.4)检查上一步的临时列表msgBackFailedList是否为空,若不为空则说明有发送重试消息失败的,则首先从ConsumeRequest.msgs: List变量列表中删除消费失败的这部分消息,然后调用ConsumeMessageConcurrentlyService.submitConsumeRequestLater(List msgs, ProcessQueue processQueue, MessageQueue messageQueue)方法,在该方法中初始化一个匿名线程,并将该线程放入调度线程池(ConsumeMessageConcurrentlyService.scheduledExecutorService)中,延迟5秒之后执行该匿名线程,该匿名线程的run方法调用ConsumeMessageConcurrentlyService.submitConsumeRequest(List msgs, ProcessQueue processQueue, MessageQueue messageQueue, boolean dispatchToConsume)方法,将发送BACK消息失败之后发送RETRY消息也失败的List列表封装成ConsumeRequest线程对象,再次提交到ConsumeMessageConcurrentlyService.consumeExecutor线程池中;

6.5)将消费成功了的消息(ConsumeRequest.msgs列表中剩下的)从 ProcessQueue.msgTreeMap列表中删除,同时更新 ProcessQueue.msgCount并返回该列表中第一个MessageExt元素的key值,即该元素的queueoffset值;

6.6)若上一步返回的queueoffset大于等于0,则调用 OffsetStore.updateOffset( MessageQueue mq, long offset, boolean increaseOnly)更新该消费队列的消费进度;

4.2 对消费失败的信息发送重试消息给Broker(用于消息重试)

在业务层消费消息失败并且是集群模式下,会调用ConsumeMessageConcurrentlyService.sendMessageBack(MessageExt msg, ConsumeConcurrentlyContext context)方法。在该方法中调用DefaultMQPushConsumerImpl.sendMessageBack(MessageExt msg,int delayLevel, String brokerName)方法,大致逻辑如下:

1、根据brokerName获取Broker地址;

2、调用MQClientAPIImpl.consumerSendMessageBack(String addr, MessageExt msg, String consumerGroup, int delayLevel, long timeoutMillis)方法,其中delayLevel参数由ConsumeConcurrentlyContext. delayLevelWhenNextConsume可在业务层的回调方法中设置,默认为0(表示由服务器根据重试次数自动叠加);构建ConsumerSendMsgBackRequestHeader对象,其中该对象的offset等于MessageExt.commitLogOffset、originTopic等于MessageExt.topic、originMsgId等于MessageExt.msgId;然后发送CONSUMER_SEND_MSG_BACK请求码的信息给Broker(详见3.1.17小节);

3、如果发送重试消息出现异常,则构建以%RETRY%+consumerGroup为topic值的新Message消息,构建过程如下:

3.1)从MessageExt消息的properties属性中获取"ORIGIN_MESSAGE_ID"属性值,若没有则以MessageExt消息的msgId来设置新Message信息的properties属性的ORIGIN_MESSAGE_ID属性值,若有该属性值则以该属性值来设置新Message信息的properties属性的ORIGIN_MESSAGE_ID属性值。保证同一个消息有多次发送失败能获取到真正消息的msgId;

3.2)将MessageExt消息的flag赋值给新Message信息的flag值;

3.3)将MessageExt消息的topic值存入新Message信息的properties属性中"RETRY_TOPIC"属性的值;

3.4)每次消费重试将MessageExt消息的properties属性中"RECONSUME_TIME"值加1;然后将该值再加3之后存入新Message信息的properties属性中"DELAY"属性的值;

4、调用在启动Consumer时创建的名为"CLIENT_INNER_PRODUCER"的DefaultMQProducer对象的send(Message msg)方法,以Consumer内部的消息Producer重发消息(该消息是消费失败且回传给Broker也失败的);

你可能感兴趣的:(RocketMQ)