RocketMQ Consumer消息消费过程(三)、消息的处理

说明

从上一节《Consumer消息消费过程(二)、消息的拉取》中,我们知道,拉取到消息后,会通过调用回调函数的方式对消息进行处理。在本节中,我们就学习下消息的处理过程。

消息处理流程图

在这里插入图片描述

拉取结果处理

1、如果消息拉取失败,则直接调用回调函数的onException方法,该方法会重新将消息拉取请求放入PullMessageService的阻塞队列,重新放入的任务会间隔一段时间(默认3秒)后才执行
在这里插入图片描述
2、如果拉取到消息,则调用回调函数的onSuccess()方法,进行如下处理:
(1)、调用PullAPIWrapper.processPullResult()方法进行结果处理:过滤消息(从这里可见,是拉取到消息再进行过滤)、设置对应的属性
在这里插入图片描述
在这里插入图片描述
(2)、如果拉取到消息:
a、设置下一次消费的offset到PullRequest中、统计相应的信息
在这里插入图片描述
b、将当前拉取到的消息设置到processQueue、提交消息到ConsumeMessageService(ConsumeMessageService分ConsumeMessageConcurrentlyService和ConsumeMessageOrderlyService,具体由Consumer注册的监听器决定。这里以ConsumeMessageConcurrentlyService为例子讲解)
在这里插入图片描述
ConsumeMessageConcurrentlyService处理消息:这里可以看到,拉取到的消息,会分批封装成ConsumeRequest,然后提交给线程池(这里是一个异步处理,而且是多个线程并发处理)进行处理。
在这里插入图片描述
ConsumeRequest的run方法,其实就是调用我们为Consumer注册的监听器(defaultMQPushConsumer.registerMessageListener),执行具体的业务逻辑处理
在这里插入图片描述
对执行监听器的结果进行处理
在这里插入图片描述
这里需要注意,当业务处理完成后,RocketMQ会将每条消息处理的结果反馈给Broker,如果反馈给Broker失败,RocketMQ会隔一段时间后(默认5秒)再重新将消息提交给ConsumeMessageConcurrentlyService的线程池,后续再重新将消息推送给监听器,所以这种情况有可能会导致消息重复消费。
在这里插入图片描述
在这里插入图片描述
c、进行下一次的消息拉取
在这里插入图片描述
(3)、如果拉取成功,但是没有新的消息:则会马上进行下一轮的消息拉取
在这里插入图片描述

当发现没有新的消息时,对结果的解析会得到NO_NEW_MSG,但实际会执行NO_MATCHED_MSG分支,所以会马上进行下一轮的消息拉取。
另外,如果Broker上没有新的消息可以获取时,Broker会对拉取消息的请求进行阻塞(默认是15秒,DefaultMQPushConsumerImpl.BROKER_SUSPEND_MAX_TIME_MILLIS配置),在阻塞时间内有新的消息到达,则返回新的消息;否则Broker返回PULL_NOT_FOUND(处理结果时会转成NO_NEW_MSG),告诉Consumer进行下一轮消息拉取。

ConsumeMessageOrderlyService

ConsumeMessageService有ConsumeMessageConcurrentlyService和ConsumeMessageOrderlyService两个实现类。ConsumeMessageConcurrentlyService是多线程并发对消息进行处理,不保证按消息的顺序进行处理。而ConsumeMessageOrderlyService保证按照消息的顺序进行处理。下面,我们看一下ConsumeMessageOrderlyService如何保证按消息的顺序进行处理。
进入ConsumeMessageOrderlyService.submitConsumeRequest,可以看到,ConsumeMessageOrderlyService对拉取到的消息,整批提交个线程池处理(ConsumeMessageConcurrentlyService会分批次提交,所以达到并发的效果):
在这里插入图片描述
ConsumeRequest.run()方法,会对整个消息队列MessageQueue进行加锁,从而保证同一时刻只有一个线程对MessageQueue进行处理:
在这里插入图片描述
然后从ProcessQueue(MessageQueue和ProcessQueue是一对一的关系,所以从Broker对应的queue拉取到的消息,都同一放在了ProcessQueue里)中取出消息进行处理:
在这里插入图片描述
takeMessages()是按顺序将消息取出:
在这里插入图片描述
取出之后,交给消息监听器处理,进行业务处理,然后返回处理结果:
在这里插入图片描述
对返回的结果进行处理
在这里插入图片描述
如果处理成功,则继续处理其它消息(返回true);如果处理失败(返回false,当前线程不再处理后面的消息,等后面重试再同一处理,所以当前线程就算任务结束了),则重新将当前的消息放回ProcessQueue中,并稍后再提交任务到线程池。
在这里插入图片描述
在这里插入图片描述
从以上的处理步骤,我们知道,有几个关键点:
1、从Broker对应的queue拉取下来的消息,统一存放在ProcessQueue的treeMap中,该map已经对消息的offset进行排序。
2、虽然每拉取到一批消息,就提交到ConsumeMessageOrderlyService进行处理,线程池中会有多个线程对消息进行处理,但是由于对整个MessageQueue进行了加锁操作,所以只有一个线程能拿到锁,拿到锁的线程有可能对所有消息进行处理,后续的线程即使拿到锁,也无消息可以处理,直接完成。
3、当拿到锁的线程从ProcessQueue拿出消息时,是按照offset顺序拿的,从而保证按顺序处理消息。
4、如果业务处理失败,会把当前处理的消息重新放回ProcessQueue中,当前线程直接结束,后续重新启动线程对消息进行处理。所以即使业务处理失败,也能保证按消息的顺序进行处理。

总结

1、消息拉取和消息处理两个处理是分开的,拉取到消息,会提交给对应ConsumeMessageService进行处理。
2、消息是由ConsumeMessageService异步推送给Consumer的监听器处理。
3、如果监听器处理消息失败,消息将缓存在内存中,有可能占用过多的内容。
4、如果监听器处理消息失败,还可能导致ConsumeMessageService线程池中的线程(默认最小和最大线程数都是20)耗尽。该线程池的线程数可以在定义Consumer中定义。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乐观男孩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值