RocketMQ(四)——消息重试

本文探讨了RocketMQ中消息失败后的两种重试机制:Producer端重试与Consumer端重试,并详细解释了如何配置及实现这两种重试机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


对于MQ,可能存在各种异常情况,导致消息无法最终被Consumer消费掉,因此就有了消息失败重试机制。很显示,消息重试分为2种:Producer端重试和Consumer端重试。

一、 Producer端重试

生产者端的消息失败,也就是Producer往MQ上发消息没有发送成功,比如网络抖动导致生产者发送消息到MQ失败。
这种消息失败重试我们可以手动设置发送失败重试的次数,看一下代码:

/**
 * Producer,发送消息
 */
public class Producer {
	public static void main(String[] args) throws MQClientException, InterruptedException {
		DefaultMQProducer producer = new DefaultMQProducer("group_name");
		producer.setNamesrvAddr("192.168.2.222:9876;192.168.2.223:9876");
		producer.setRetryTimesWhenSendFailed(3);
		producer.start();

		for (int i = 0; i < 100; i++) {
			try {
				Message msg = new Message("TopicTest", 				// topic
						"TagA", 									// tag
						("HelloWorld - RocketMQ" + i).getBytes()	// body
				);
				SendResult sendResult = producer.send(msg, 1000);
				System.out.println(sendResult);
			} catch (Exception e) {
				e.printStackTrace();
				Thread.sleep(1000);
			}
		}

		producer.shutdown();
	}
}
*生产者端失败重试*

上图代码示例的处理手段是:如果该条消息在1S内没有发送成功,那么重试3次。

producer.setRetryTimesWhenSendFailed(3); //失败的情况重发3次
producer.send(msg, 1000); //消息在1S内没有发送成功,就会重试


二、 Consumer端重试

消费者端的失败,分为2种情况,一个是exception,一个是timeout。

1. Exception

消息正常的到了消费者,结果消费者发生异常,处理失败了。例如反序列化失败,消息数据本身无法处理(例如话费充值,当前消息的手机号被注销,无法充值)等。
这里涉及到一些问题,需要我们思考下,比如,消费者消费消息的状态有哪些定义?如果失败,MQ将采取什么策略进行重试?假设一次性批量PUSH了10条,其中某条数据消费异常,那么消息重试是10条呢,还是1条呢?而且在重试的过程中,需要保证不重复消费吗?

public enum ConsumeConcurrentlyStatus {
    /**
     * Success consumption
     */
    CONSUME_SUCCESS,
    /**
     * Failure consumption,later try to consume
     */
    RECONSUME_LATER;
}
*ConsumeConcurrentlyStatus枚举的源码*

通过查看源码,消息消费的状态,有2种,一个是成功(CONSUME_SUCCESS),一个是失败&稍后重试(RECONSUME_LATER)
RECONSUME_LATER的策略

RECONSUME_LATER的策略

在启动broker的过程中,可以观察到上图日志,你会发现RECONSUME_LATER的策略:如果消费失败,那么1S后再次消费,如果失败,那么5S后,再次消费,…直至2H后如果消费还失败,那么该条消息就会终止发送给消费者了!
RocketMQ为我们提供了这么多次数的失败重试,但是在实际中也许我们并不需要这么多重试,比如重试3次,还没有成功,我们希望把这条消息存储起来并采用另一种方式处理,而且希望RocketMQ不要再重试呢,因为重试解决不了问题了!这该如何做呢?
看一段代码:

/**
 * Consumer,订阅消息
 */
public class Consumer {

	public static void main(String[] args) throws InterruptedException, MQClientException {
		DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_name");
		consumer.setNamesrvAddr("192.168.2.222:9876;192.168.2.223:9876");
		consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
		consumer.subscribe("TopicTest", "*");

		consumer.registerMessageListener(new MessageListenerConcurrently() {
			@Override
			public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
				try {
					MessageExt msg = msgs.get(0);
					String msgbody = new String(msg.getBody(), "utf-8");
					System.out.println(msgbody + " Receive New Messages: " + msgs);
					if (msgbody.equals("HelloWorld - RocketMQ4")) {
						System.out.println("======错误=======");
						int a = 1 / 0;
					}
				} catch (Exception e) {
					e.printStackTrace();
					if (msgs.get(0).getReconsumeTimes() == 3) {
						// 该条消息可以存储到DB或者LOG日志中,或其他处理方式
						return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;// 成功
					} else {
						return ConsumeConcurrentlyStatus.RECONSUME_LATER;// 重试
					}
				}
				return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
			}
		});

		consumer.start();
		System.out.println("Consumer Started.");
	}
}
RECONSUME_LATER的重试测试代码

生产端发送了10条消息,看一下消费端的运行效果:
RECONSUME_LATER的重试效果

RECONSUME_LATER的重试效果

观察上图发现,HelloWorld - RocketMQ4的消息的***reconsumeTimes***属性值发生了变化,其实这个属性就代表了消息重试的次数!因此我们可以通过reconsumeTimes属性,让MQ超过了多少次之后让他不再重试,而是记录日志等处理,也就是上面代码catch中的内容。

2. Timeout

比如由于网络原因导致消息压根就没有从MQ到消费者上,那么在RocketMQ内部会不断的尝试发送这条消息,直至发送成功为止!(比如集群中一个broker失败,就尝试另一个broker)
延续Exception的思路,也就是消费端没有给RocketMQ返回消费的状态,即没有return ConsumeConcurrentlyStatus.CONSUME_SUCCESS或return ConsumeConcurrentlyStatus.RECONSUME_LATER,这样的就认为没有到达Consumer端。
下面进行模拟:

1)消费端有consumer1和consumer2这样一个集群。
2)consumer1端的业务代码中暂停1分钟并且不发送接收状态给RocketMQ。

public class Consumer1 {

	public static void main(String[] args) throws InterruptedException, MQClientException {
		DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_name");
		consumer.setNamesrvAddr("192.168.2.222:9876;192.168.2.223:9876");
		consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
		consumer.subscribe("Topic1", "Tag1 || Tag2 || Tag3");

		consumer.registerMessageListener(new MessageListenerConcurrently() {
			@Override
			public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
				try {					
						String topic = msg.getTopic();
						String msgBody = new String(msg.getBody(),"utf-8");
						String tags = msg.getTags();
						System.out.println("收到消息:" + " topic:" + topic + " ,tags:" + tags + " ,msg:" + msgBody);
						
						// 表示业务处理时间
						System.out.println("=========开始暂停==========");
						Thread.sleep(60000);
					}
				} catch (Exception e) {
					e.printStackTrace();
					return ConsumeConcurrentlyStatus.RECONSUME_LATER;// 重试
				}
				return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
			}
		});

		consumer.start();
		System.out.println("Consumer Started.");
	}
}
Consumer1端Timeout异常测试代码

3)启动consumer1和consumer2。
4)启动Producer,只发送一条数据。
看一下此时consumer1和consumer2的运行结果:
consumer1

Consumer1

Consumer2-未接收到消息

Consumer2-未接收到消息

发现consumer1接收到消息并且暂停,consumer2未接收到消息。

5)关闭consumer1。
观察consumer2的运行结果:
Consumer2-接收到消息

Consumer2-接收到消息

总结

Producer端没什么好说的,Consumer端值得注意。对于消费消息而言,存在2种指定的状态(成功 OR 失败重试),如果一条消息在消费端处理没有返回这2个状态,那么相当于这条消息没有达到消费者,势必会再次发送给消费者!也即是消息的处理必须有返回值,否则就进行重发。

### RocketMQ 消息重试机制详解 #### 生产端消息重试配置与使用 在生产环境中,为了确保消息能够可靠地发送到目标队列,RocketMQ 提供了生产端的消息重试功能。当客户端向服务端发起消息发送请求时,如果遇到诸如网络故障、服务异常等问题导致调用失败,RocketMQ 的客户端 SDK 内置有请求重试逻辑来保障消息的成功送达。 要启用此特性,在创建 `DefaultMQProducer` 实例时设置相应的参数即可: ```java // 创建生产者实例并指定组名 DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // 设置名称服务器地址 producer.setNamesrvAddr("localhost:9876"); // 开启发送消息失败自动重试其他 Broker 配置,默认关闭 producer.setRetryAnotherBrokerWhenNotStoreOK(true); // 启动生产者 producer.start(); ``` 上述代码片段展示了如何开启当消息未能成功存储于当前 Broker 上时切换至另一可用 Broker 进行重试的功能[^5]。 #### 消费端消息重试策略 除了生产端外,消费端同样具备强大的重试能力。一旦消费者接收到某条消息后处理过程中出现问题无法完成正常业务操作,则该消息会被重新放回原 Topic 下等待下次拉取继续尝试执行直至达到预设的最大重试次数为止;对于无序消息而言,默认情况下每条记录最多可经历 16 轮次的重复读取消耗过程[^4]。 具体来说,可以通过调整 Consumer 端程序中的属性来自定义这些行为: ```java // 初始化默认消费者对象 DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("example_group_name"); // 订阅主题及标签表达式 consumer.subscribe("TopicTest", "*"); // 注册监听器用于接收并解析数据包体内容 consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) { try { // 处理消息... return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } catch (Exception e) { logger.error("Failed to process message.", e); return ConsumeConefullyStatus.RECONSUME_LATER; // 返回稍后再试的状态码表示需要再次消费这条消息 } } }); // 执行启动命令使整个链路生效 consumer.start(); ``` 这里的关键在于捕获任何可能出现的异常情况,并显式告知框架应采取何种行动——即通过返回特定枚举值告诉系统是否应当立即放弃还是安排后续进一步的动作[^2]。 #### 自定义重试间隔与时长控制 针对不同应用场景下的需求差异,有时开发者希望更精细地掌控每次重试之间的时间差以及总的超时期限。对此,RocketMQ 设计了一套灵活易用的方案允许用户自定义这一系列参数。例如,可以修改消费者的最大重试次数或者设定具体的延迟级别等。 以下是部分常用选项及其含义说明: | 属性 | 描述 | | --- | --- | | maxReconsumeTimes | 单个消息最大的重试次数限制,默认为 16 次 | | delayLevelWhenNextConsume | 当前轮次结束后距离下一轮开始之前所经过的时间长度 | 需要注意的是,以上表格仅列举了一些较为重要的字段作为参考指南,实际项目里可根据官方文档获取更多细节指导。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值