RocketMQ消费失败消息深入分析

前言

消息队列是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。目前在生产环境,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等。由于每个消息队列都有它的优势和劣势,我们公司对于不同的场景使用了不同类型的消息队列。对于RocketMQ消费端存在消息消费失败的情况,通常有两种方式,一种是consumer端知道怎么处理,另一种是consumer不能处理(broker处理),本文对后一种情况进行介绍,consumer获取到消息但不能正常处理(ack),接下来这个消费失败的消息在Broker里面如何存储和重新让consumer消费,针对这个流程做了深入的分析。本文中的P代表producer,C代表consumer,本文的consumeQueue对应前面的topic下面的队列。

目录

  • RocketMQ的消费与存储结构
  • RocketMQ的消费失败消息处理逻辑
  • Broker端处理失败消息任务的启动
  • Consumer发回消费失败消息流程
  • Broker写发回失败消息的流程

RocketMQ的消费与存储结构

正常情况下,P发送消息到broker,消息内容写到commitlog,消息内容在commitlog的位置信息(索引)写到consumerQueue,C读取consumerQueue的内容消费消息。流程图.pngRocketMq的存储结构: 
流程图.png本文的内容涉及上面的消费队列服务(consumerQueue,%RETRY%groupName属于consumerQueue),定时消息服务(SCHEDULETOPICXXXX)两个模块,C与broker的的消息消费只涉及到consumerQueue,定时消息服务只在broker内部起作用。

RocketMQ的消费失败消息处理逻辑

consumer消费失败消息处理流程图如下: 
流程图.png在下面的代码和流程分析中请结合这个图进行分析。 其中SCHEDULETOPICXXXX和%RETRY%groupName的queue都存储在目录 ~/store/consumequeue 里面: ll ~/store/consumequeue 如下: 
schedule and retry store.pngll ~/store/consumequeue/SCHEDULETOPICXXXX 如下: 
schedule queue.png从上图可以看出SCHEDULETOPICXXXX的队列名称是从2开始到17,对应的delayLevel为3到18,3对应10s,18对应2h,在类MessageStoreConfig中这样定义延时时间:String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"。SCHEDULETOPICXXXX这个topic只对内部使用,对于consumer只能消费到retry队列的数据。 consumer消费失败的消息发回broker后总是先写到SCHEDULETOPICXXXX里面,然后schedule service读取SCHEDULETOPICXXXX里面的数据写到retry队列,consumer消费retry队列的数据,这样就完成了一个循环,从这个过程也能看到,一个消费失败的消息体每次发回broker需要在commitLog里面存储两份(topic为SCHEDULETOPICXXXX的一份这个主要是为schedule service控制延时用的,topic为%RETRY%groupName的一份)。 
当我们想查看现在的延时消息数量,我们可以查看SCHEDULETOPICXXXX的offset来得知,使用CLI Admin Tool工具输入命令“sh mqadmin brokerStatus”查看处理进度。如下图:shcedule-offset.png其中每行为一个队列,图中第一列为队列的名称,图中第二列参数为当前队列处理的offset,图中第三列为当前队列最大存储的offset。通过第三列和第二列的值相减能得出当前的队列的消息数量。

Broker端处理失败消息任务的启动

Schedule Service Process Class Diagram0.pngScheduleMessageService根据messageDelayLevel维护了每个延迟level对应的队列编号,以及每个队列编号对应的offset。在start方法里面会启动18个timerTask(DeliverDelayedMessageTimerTask),每个对应一个level,初始offset为0。然后就是定时任务读取SCHEDULETOPICXXXX队列里面的消息进行判断,如果消息的delayLevel对应的时间满足重新消费,那么就会忘consumeQueue里面写这个消息,等待C重新来消费。

Consumer发回消费失败消息流程

Send Consume Failed Message Sequence Diagram1.png在ConsumeRequest的run方法里面也就是业务端处理消息的线程里面,对于status是非success的交给ConsumeMessageConcurrentlyService(本文只讨论并行消费的模式,串行模式类似)的sendMessageBack方法处理,这个方法主要设置delayLevel(context.getDelayLevelWhenNextConsume()),然后传递给DefaultMQPushConsumerImpl.sendMessageBack找到对应的消息来源queue,把这个消息发送到这个queue里面,也就是说消费失败的消息发回broker还是会在之前的那个queue里面。发回broker后本地再过5秒重试消费一次,如果这次成功,下次就不再消费。 上面流程的类图:Send Message Class Diagram0.png在ConsumeRequest的run方法里面会调用ConsumeMessageConcurrentlyService.this.processConsumeResult(status, context, this)来处理消费结果状态,在cluster(集群模式)下设置新的消息delayLevle值然后把失败的消息发回Broker,广播模式不发回。注意ConsumeConcurrentlyContext的delayLevelWhenNextConsume属性说明-1直接放到死信队列,0又broker每次对重试消费次数加1来控制重试策略,大于0由consumer控制重试消费策略(在listener的consumeMessage方法里面有个context:context.setDelayLevelWhenNextConsume(4)设置为1分钟延时消费),默认值为0。

Broker写发回失败消息的流程

Schedule Sequence Diagram0.pngbroker端收到消费失败消息后通过consumerSendMsgBack(P发送的消息不由这个处理,区分通过消息头的type)方法设置当前消息的delayTimeLevel,这里计算delayTimeLevel,第一次重试默认consumer发回为0,延迟为延迟等级为0+3=3;如果第一次不为0表明是consumer控制的情况,直接取出delayTimeLevel,也就是和ConsumeConcurrentlyContext(consumer端控制)的delayLevelWhenNextConsume配置一致。设置好delayLevelTime后就交给DefaultMessageStore的putMessage方法,DefaultMessageStore的putMessage方法通过Commitlog的putMessage来写入文件,这里需要重点关注的是在这个方法里面通过msg.getDelayTimeLevel() > 0这个条件,修改当前消息topic为SCHEDULETOPICXXXX,原来的topic保留在property里面,在ScheduleMessageService里面判断消息满足条件后会把消息的topic改为真实的topic,通常是retry,接着写到consumeQueue里面,C对于%RETRY%consumerGroup这个topic在程序里面默认是订阅的不需用户指定,然后队列Id的计算方式为queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel()),即msg.getDelayTimeLevel()-1,和前面的截图2到17编号一致,然后消息体写到commitlog文件和索引写到SCHEDULETOPICXXXX队列。类图如下: 
Process Reconsume Message Class Diagram0.pngSendMessageProcessor处理远程发来的消息,包括P和C的,方法里面通过RequestCode.CONSUMERSENDMSG_BACK来判断是不是重试发回的消息。然后会判断这个消息对应的topic为%RETRY%consumerGroup的是否创建过,没有则创建;接下来的处理就和上面的流程图一样了。

总结

本文围绕consumer端消费失败后RocketMQ各个模块的处理逻辑进行了源码的深入分析。相信有了以上的知识学习和实践之后,当业务应用遇到了类似的问题就可以胸有成竹的应对了。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: RocketMQ可以通过以下方式来保证消息顺序消费: 1. 消息发送顺序:在发送消息时,可以指定一个key,RocketMQ会根据这个key来保证消息的顺序性,即相同key的消息会被发送到同一个队列中,保证消费顺序。 2. 消费者顺序消费:在消费者端,可以通过设置消费者组来保证顺序消费。同一个消费者组中的消费者会按照顺序依次消费消息,不同消费者组之间的消费顺序是无法保证的。 3. 单线程消费:在消费者端,可以将消费者线程数设置为1,这样就可以保证消息的顺序消费。 需要注意的是,以上三种方式都只能保证单个队列内的消息顺序消费,如果一个topic有多个队列,那么不同队列之间的消息顺序是无法保证的。因此,在设计topic时,需要根据实际情况来确定队列数量,以保证消息的顺序性。 ### 回答2: RocketMQ是一个开源的分布式消息队列系统,能够提供高吞吐量、可靠性、可扩展性和顺序消费。顺序消费是指消费者按照消息发送的顺序一个一个地消费消息,这样可以保证消息的有序性。 RocketMQ 保证消息顺序消费的主要方式有两种: 1. 消费者组 通过消费者组来保证消息顺序消费。所谓消费者组,是指一组消费者实例的集合,这些消费者实例共同消费同一个主题(topic)的消息RocketMQ会将同一主题下的消息均匀地分配给各个消费者实例来消费,每个消费者实例只负责消费一部分消息。当消费者组中的一个消费者实例宕机或者出现其他异常情况时,RocketMQ会自动将该实例负责的消息分配给其他消费者实例来消费,不会影响消息的顺序。 2. 队列选择器 RocketMQ 提供了队列选择器(QueueSelector)接口,可以自定义消息被发送到哪个队列中。通过控制消息将被发送到哪个队列,可以保证消息被顺序消费。当生产者向同一个主题发送消息时,可以将相对顺序靠前的消息发送到同一个队列中,而将相对顺序靠后的消息发送到另一个队列中。然后,在消费消费消息时,按照队列顺序一个一个地消费,这样可以保证消息顺序消费。 总之,RocketMQ 能够保证消息顺序消费是因为它采用了消费者组和队列选择器等多种机制,在消费消息时逐个消费,严格按照消息的先后顺序来消费消息。这样可以保证消息有序性,更加符合实际的业务需求。 ### 回答3: RocketMQ 是一种可靠的分布式消息中间件,它可以保证消息的顺序性,这主要是因为 RocketMQ 支持 FIFO 的顺序过程。RocketMQ 的顺序消费主要通过以下方法实现: 1.消息分区:RocketMQ消息分区机制可以实现在单个消费者实例上对特定的主题、队列和标记进行顺序消息发送和消费。这个作用跟许多的关系型数据库在实现主键的双重保证,如果我们使用了这个机制,那么我们将总是在同一分区上发送并消费要按顺序排列的消息。 2.消息分组:RocketMQ 支持为不同的消费者实例创建不同的组,每个组只能消费某一个队列的消息。由于同一个分组中只有一个消费者实例能够访问队列,RocketMQ 确保了消息的顺序性。 3. 定时器线程池:RocketMQ 是通过定时器来调度消息的,它的定时器线程池中同时只会有一个线程来处理队列中的消息,这个线程只会按顺序处理队列中的消息。这样可以保证消息消费时必须按照先后顺序进行。 4. 内存映射缓存:RocketMQ消息以哈希表的形式存储到了内存映射缓存中,这样避免了对磁盘的频繁操作。因为磁盘 I/O 是非常慢的,这会影响到消费的速度。所以,RocketMQ 采用了内存映射的缓存机制来减少对磁盘的 I/O 操作。 总之,RocketMQ 在实现顺序消息消费时借助于消息分区、消息分组、定时器线程池和内存映射缓存等技术手段,都是为了保证消息的排序和顺序的连续性,使得消费者能够按顺序消费消息。除此之外,一些实际场景下的问题,比如说如何处理P2P的顺序消息、如何解决强依赖的消息等问题,都需要根据实际情况进行相关的处理,以便得到更完善的顺序消息传输机制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值