RocketMQ之消费重试以及原理

1.4 消费重试

image.png

对于顺序消息,当消费者消费消息失败后,消费者会在本地自动不断进行消息重试,每次间隔时间为 1 秒,重试最大值是 Integer.MAX_VALUE。

对于无序消息(普通、定时、延时、事务消息)当消费者消费消息失败时可以通过设置返回状态达到重试的目的。

广播模式下消息队列RocketMQ保证每条消息至少被每台客户端消费一次,但是并不会对消费失败的消息进行失败重投,因此业务方需要关注消费失败的情况。

1.4.1顺序消息自动重试:需要避免阻塞

当消费者消费消息失败后,消费者会在本地自动不断进行消息重试,每次间隔时间为 1 秒,重试最大值是 Integer.MAX_VALUE。这时,应用会出现消息消费被阻塞的情况。

因此,在使用顺序消息时,务必保证应用能够及时监控并处理消费失败的情况,避免阻塞现象的发生。

1.4.2无序消息重试:广播模式不会失败重投

无序消息的重试只针对集群消费方式生效。

广播方式不提供失败重试特性,即消费失败后,失败消息不再重试,继续消费新的消息。

广播模式下消息队列 RocketMQ保证每条消息至少被每台客户端消费一次,但是并不会对消费失败的消息进行失败重投,因此业务方需要关注消费失败的情况,可以自己做一些重试的操作比如消费失败做持久化,使用定时器重新重试数据库中持久化的数据。

不会失败重投的原因是一旦消费失败做了消费重投,所有的消费者都需要再消费一次!

1.4.3无序消息重试:集群模式需要返回消费失败状态

对于无序消息(普通、定时、延时、事务消息)当消费者消费消息失败时可以通过设置返回状态达到重试的目的。

无序消息的重试只针对集群消费方式生效。

广播方式不提供失败重试特性,即消费失败后,失败消息不再重试,继续消费新的消息。

默认消息消费重试次数:16

默认最大重试次数为16,所以一个消息最多可以消费17次。

为什么最多是17次?

默认设置延迟级别范围为3到18。因为18个等级,从3开始重试,18-3=16。

加上消费失败的那一次也就是17次。

消息队列 RocketMQ 默认允许每条消息最多重试 16 次,每次重试的间隔时间如下:

第几次重试与上次重试的间隔时间第几次重试与上次重试的间隔时间
110 秒97 分钟
230 秒108 分钟
31 分钟119 分钟
42 分钟1210 分钟
53 分钟1320 分钟
64 分钟1430 分钟
75 分钟151 小时
86 分钟162 小时

如果消息重试 16 次后仍然失败,消息将不再投递。如果严格按照上述重试时间间隔计算,某条消息在一直消费失败的前提下,将会在接下来的 4 小时 46 分钟之内进行 16 次重试,超过这个时间范围消息将不再重试投递。

注意: 一条消息无论重试多少次,这些重试消息的 Message ID 不会改变。

自定义消息最大重试次数

消息队列 RocketMQ 允许 Consumer 启动的时候设置最大重试次数,重试时间间隔将按照如下策略:

  • 最大重试次数小于等于16 次,则重试时间间隔同上表描述。
  • 最大重试次数大于 16 次,超过 16 次的重试时间间隔均为每次 2 小时。
 

java

代码解读

复制代码

Properties properties = new Properties(); //配置对应 Group ID 的最大消息重试次数为 20 次 properties.put(PropertyKeyConst.MaxReconsumeTimes,"20"); Consumer consumer =ONSFactory.createConsumer(properties);

注意:

  • 消息最大重试次数的设置对相同 Group ID 下的所有 Consumer 实例有效。
  • 如果只对相同 Group ID 下两个 Consumer 实例中的其中一个设置了 MaxReconsumeTimes,那么该配置对两个 Consumer 实例均生效。
  • 配置采用覆盖的方式生效,即最后启动的 Consumer 实例会覆盖之前的启动实例的配置
获取消息重试次数

消费者收到消息后,可按照如下方式获取消息的重试次数:

 

java

代码解读

复制代码

public class MessageListenerImpl implements MessageListener {    @Override    public Action consume(Message message, ConsumeContext context) {        //获取消息的重试次数        System.out.println(message.getReconsumeTimes());        return Action.CommitMessage;   } }

消费失败后,重试配置方式

集群消费方式下,消息消费失败后期望消息重试,需要在消息监听器接口的实现中明确进行配置(三种方式任选一种):

  • 返回 Action.ReconsumeLater (推荐)
  • 返回 Null
  • 抛出异常
 

java

代码解读

复制代码

public class MessageListenerImpl implements MessageListener {    @Override    public Action consume(Message message, ConsumeContext context) {        //处理消息        doConsumeMessage(message);        //方式1:返回 Action.ReconsumeLater,消息将重试        return Action.ReconsumeLater;        //方式2:返回 null,消息将重试        return null;        //方式3:直接抛出异常, 消息将重试        throw new RuntimeException("Consumer Message exceotion");   } }

消费失败后,不重试配置方式

集群消费方式下,消息失败后期望消息不重试,需要捕获消费逻辑中可能抛出的异常,最终返回 Action.CommitMessage,此后这条消息将不会再重试。

 

java

代码解读

复制代码

public class MessageListenerImpl implements MessageListener {    @Override    public Action consume(Message message, ConsumeContext context) {        try {            doConsumeMessage(message);       } catch (Throwable e) {            //捕获消费逻辑中的所有异常,并返回 Action.CommitMessage;            return Action.CommitMessage;       }        //消息处理正常,直接返回 Action.CommitMessage;        return Action.CommitMessage;   } }

当然 我们也可以根据

重试原理

发送到重试消费Topic 是%RETRY% + 消费组名 注意是消费组名

我们思考一下为什么是消费者组名而不是topic的名字?

A消费者组消费成功 B消费者组消费失败

如果发回原topic就有问题了,A又会消费一次。

消息消费失败会设置DelayLevel并发回重试主题,重试主题 是%RETRY% + 消费组名,发回失败会立刻重试1次。

发回重试主题时,DelayTimeLeve从3开始

我们知道RocketMQ的默认的配置是messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,分别代表延迟level1-level18,为什么不是从1开始呢?

 

less

代码解读

复制代码

newMsg.setDelayTimeLevel(3 + msg.getReconsumeTimes());//默认的重试次数是0

所以默认重试时,实际是从3开始的。也就是第一次重试的时间间隔是10秒,这也解释了为什么是16次!3-18可不就是16次!

SCHEDULE_TOPIC_XXXX的队列名称是从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"。

延迟消息对应的Topic是SCHEDULE_TOPIC_XXXX,注意就是SCHEDULE_TOPIC_XXXX,XXXX不是某某某的意思。

image.png

SCHEDULE_TOPIC_XXXX 介绍 SCHEDULE_TOPIC_XXXX 是 RocketMQ 一个系统类型的 Topic,用于标识延时消息。

这个 Topic 有 18 个队列,分别唯一对应着 RocketMQ 的 18 个延时等级,对应关系为:queueId = delayTimeLevel – 1。

ScheduleMessageService 介绍 这是 Broker 中的一个延时服务,专门消费 Topic 为 SCHEDULE_TOPIC_XXXX 的延时消息,并将其投递到目标 Topic 中。

ScheduleMessageService 在启动时,会创建一个定时器 Timer,并根据延迟级别的个数,启动对应数量的 TimerTask,每个TimerTask负责一个延迟级别的消费与投递。

SCHEDULE_TOPIC_XXXX的队列名称是从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"。

SCHEDULE_TOPIC_XXXX这个topic只对内部使用,对于consumer只能消费到自己所在的消费者组的重试topic的数据。

比如A是OrderConsumer组的一个消费者,OrderConsumer消费的主题Topic是Order。

那么消费者A在启动的时候,会订阅Order主题的同时,还会订阅%RETRY%_OrderConsumer。

即订阅Order主题的同时,还会订阅%RETRY%+消费者组的主题。

consumer消费失败的消息发回broker后总是先写到SCHEDULE_TOPIC_XXXX里面,然后schedule service在延迟时间到了以后会读取SCHEDULE_TOPIC_XXXX里面的数据然后重新发回到重试主题,consumer订阅了重试主题,所以会重新消费失败的数据,这样就完成了一个循环。

rocketmq 先将不同延时等级的消息存入内部对应延时队列中,然后不断的从延时队列中拉取消息判断是否到期,然后进行投递到对应的topic中。

从这个过程也能看到,一个消费失败的消息体每次发回broker需要在commitLog里面存储两份。

topic为SCHEDULE_TOPIC_XXXX的一份这个主要是为schedule service控制延时用的。

topic为%RETRY%groupName的一份。

转自:https://juejin.cn/post/7248995705292865593

<think>嗯,我现在需要帮助用户理解RocketMQ的顺序消费原理。根据用户提供的引用资料,我得先整理一下这些信息,然后按照系统级指令来组织回答。首先,用户要求用中文回答,行内数学表达式用$...$,独立公式用$$...$$,不过看起来这个问题可能不太需要数学公式,但如果有的话要注意格式。然后需要生成相关问题,回答要结构清晰,分步骤说明。 首先,我需要确认用户的问题:RocketMQ的顺序消息消费是如何实现的?用户希望得到深入解析,所以需要详细说明各个部分的工作原理。 根据引用[2],顺序消息的基本原理是确保消息按特定顺序被消费,比如同一订单的操作需要按顺序处理。RocketMQ采用队列级别的顺序性,即同一业务标识的消息会被分配到同一个队列中,消费者串行处理。这里可能需要解释队列和分区的关系,以及如何保证同一队列的消息顺序。 引用[3]提到,RocketMQ的Rebalance机制由每个Consumer自己处理,没有Leader,这可能影响消息分配。而引用[4]和[5]提到了ProcessQueue中的consumingMsgOrderlyTreeMap和锁机制,说明在消费时如何临时存储消息并保证顺序处理。 接下来,我需要将这些点组织成一个逻辑流程。比如,顺序消息的生产、存储、分配和消费。生产阶段需要指定业务标识,比如订单ID,通过哈希算法分配到同一队列。存储时队列本身是FIFO的。消费阶段,消费者通过Rebalance获取队列,然后每个队列由独立的线程处理,加锁确保串行处理,避免并发问题。 可能还需要提到分布式锁,如引用[5]中的tryLockLaterAndReconsume方法,用于在消费失败后重试,保持顺序。此外,ProcessQueue中的TreeMap结构用来维护消息的顺序,确保处理时按顺序取出。 用户可能存在的疑问包括:如何保证同一队列的顺序?消费者故障时如何处理?Rebalance如何影响顺序消费?需要逐一解答这些问题。 在回答结构上,应该分点解析,比如消息生产、队列分配、消费流程、锁机制、异常处理等。每个部分引用对应的资料来支持说明,比如引用[2]说明队列分配,引用[4]说明TreeMap的使用,引用[5]说明锁机制。 另外,需要生成相关问题,可能涉及顺序消息的应用场景、与Kafka的区别、性能优化等,根据引用资料中的内容来设计问题。 最后,确保回答中没有使用Markdown格式,行内公式正确,独立公式单独成段,但当前问题可能不需要数学公式,所以可能不需要处理这部分。但如果有涉及到算法或公式,比如哈希算法,可能需要用行内公式表示,比如$hash(key) \% queueNum$,不过根据示例,用户可能接受中文描述。 检查所有引用是否恰当,每个引用标识对应正确的资料,比如引用[2]对应顺序消息的基本原理,引用[4]对应consumingMsgOrderlyTreeMap,引用[5]对应锁机制等。确保回答真实可靠,结构清晰,帮助用户逐步理解。</think>### RocketMQ顺序消息消费实现原理分步解析 #### 一、核心设计原则 RocketMQ通过**队列级顺序性**实现顺序消息消费,遵循三个基本原则: 1. **相同业务标识的消息分配到同一队列** 生产者通过$hash(key) \% queueNum$算法将同一业务标识(如订单ID)的消息固定分配到特定队列[^2] 2. **队列存储保持FIFO特性** 每个队列内部严格保证消息的写入顺序与存储顺序一致 3. **消费者串行处理队列消息** 每个队列由独立的消费线程处理,禁止并发操作 #### 二、实现流程详解 1. **消息生产阶段** ```java Message message = new Message("OrderTopic", "OrderID_1001", "订单创建".getBytes()); SendResult sendResult = producer.send(message, new MessageQueueSelector() { @Override public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) { // 根据订单ID选择队列 int index = arg.hashCode() % mqs.size(); return mqs.get(index); } }, "OrderID_1001"); ``` 通过`MessageQueueSelector`保证相同订单ID的消息始终发送到同一队列[^2] 2. **消息存储结构 $$存储结构=Topic+Queue+MessageList$$ 每个队列维护物理偏移量(commitlog)和逻辑偏移量(consumequeue)的双层索引结构 3. **消费分配阶段** - 采用**主动拉取+长轮询**模式 - 消费者通过`RebalanceService`完成队列分配 - 每个消费者独立维护分配到的队列列表[^3] 4. **消息处理流程** ```plantuml @startuml group 消费线程 -> 获取队列锁 -> 从ProcessQueue拉取消息 -> 存入consumingMsgOrderlyTreeMap -> 执行业务处理 -> 提交消费位点 -> 释放队列锁 @enduml ``` - 使用`consumingMsgOrderlyTreeMap`临时存储待处理消息,维护消息顺序[^4] - 通过`ConsumeMessageOrderlyService`实现顺序消费服务[^5] #### 三、关键控制机制 1. **分布式锁控制** ```java public void run() { if (this.processQueue.isLocked()) { // 执行消息处理 } else { ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume( this.messageQueue, this.processQueue, 100); } } ``` 消费前必须获取队列锁,失败时触发重试机制[^5] 2. **异常处理策略** | 异常类型 | 处理方式 | |---|---| | 业务处理失败 | 自动重试当前消息(需业务保证幂等性) | | 网络中断 | 触发Rebalance重新分配队列 | | Broker宕机 | 切换至从节点继续消费 | 3. **顺序性保障边界** $$保障范围=同一队列+单线程处理+异常恢复机制$$ 在以下场景可能破坏顺序性: - 生产者跨线程发送同一业务标识的消息 - Broker主从切换时存在未复制消息 - 消费者并行处理多个队列 #### 四、性能优化实践 1. **批量加锁机制**:对连续消息进行批量锁申请 2. **本地缓存预取**:提前加载后续消息到消费端内存 3. **零拷贝传输**:使用`FileChannel.map()`实现内存映射
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值