一、RocketMQ的消息重试机制
1.1、概述
消息重试,顾名思义是指消息发送失败或者消费失败时,生产者端进行重新发送或者消费者端重新消费,目的是为了尽可能的保证消息不丢失。RocketMQ中的消息重试分为两大类,即:生产者端重试和消费者端重试。对于生产者端重试的次数默认为2,源码如下:
对于消费者端来说,不同的模式,其重试次数又是不一样的,具体情况如下:
(一)并发模式:则最多重试16次,依次按照【10s、30s、1min、2min、3min、4min、5min、6min、7min、8min、9min、10min、20min、30min、1h、2h】的时间间隔进行重试,最多重试16次,如果16次还未成功,则进入死信队列;
(二)顺序模式:则最多重试Integer.MAX_VALUE次(顺序模式下,如果不设置的话,重试这么多次,简直爽歪歪!)
1.2、原则
消息重试既可以在生产者端设置,也可以在消费端设置,但是一般情况下是在消费者端进行设置的。原因是消费者端更偏向于处理业务,可以对消息做各种更复杂的业务处理。
1.3、哪些场景消费者会触发重试机制?
RocketMQ中触发消费者重试消费消息,有以下三种方式,即:业务报错了、返回null、返回RECONSUME_LATER,下面通过案例演示其中的一种,其他场景大家可以根据需要自己在电脑上测试。
1.4、 并发模式客户端消息重试(默认重试次数)
1.4.1、Demo09MQTestApp
/**
* @Author : 一叶浮萍归大海
* @Date: 2023/12/25 15:39
* @Description: RocketMQ消息重试机制
*/
@Slf4j
public class Demo09MQTestApp {
/**
* 发送消息
*/
@Test
public void demo9Producer() throws Exception {
// 1、创建一个生产者
DefaultMQProducer producer = new DefaultMQProducer("retry-producer-group");
// 2、连接NameServer
producer.setNamesrvAddr(RocketMQConstant.NAME_SERVER_ADDR);
// 生产者发送消息失败时,默认会重试2次
// producer.setRetryTimesWhenSendFailed();
// producer.setRetryTimesWhenSendAsyncFailed();
// 3、启动
producer.start();
// 4、创建消息
Message message = new Message("retry-topic", "这是一个Retry消息".getBytes(StandardCharsets.UTF_8));
// 5、发送消息
producer.send(message);
log.info("【demo9Producer】发送消息成功!");
// 关闭producer
producer.shutdown();
}
/**
* 接收广播消息
*/
@Test
public void demo9PushConsumer() throws Exception {
// 1、创建一个消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("retry-consumer-group");
// 2、连接NameServer
consumer.setNamesrvAddr(RocketMQConstant.NAME_SERVER_ADDR);
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
// 3、订阅消息,*表示订阅该主题所有的消息
consumer.subscribe("retry-topic", "*");
// 4、设置监听器(采用异步回调方式,一直监听)
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> messages, ConsumeConcurrentlyContext context) {
/**
* 随机生成一个[0,5)之间的随机数,模拟消费异常,大于3消费成功,小于等于3认为失败,观察默认的重试机制
*/
Integer number = new Random().nextInt(5);
log.info("number:{}", number);
if (number > 3) {
for (MessageExt message : messages) {
log.info("我是消费者【demo9PushConsumer】,我收到的消息是:{}",StrUtil.utf8Str(message.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
/**
* 返回值:消费消息成功与否
* CONSUME_SUCCESS:表明消费成功,消息会从MQ出队
* RECONSUME_LATER:表明消费失败,消息会重新回到队里,过一会儿再重新投递出来给当前消费者或者其他消费者
* 什么时候消费者会重试?
* (1)业务报错了
* (2)返回null
* (3)返回RECONSUME_LATER
*/
// return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
log.info("【消费者demo9PushConsumer重试】");
throw new RuntimeException("业务出异常了,请联系管理员!");
}
});
// 5、启动
consumer.start();
log.info("【demo9PushConsumer】启动成功,正在等待接收消息...");
// 6、挂起当前JVM
System.in.read();
}
}
1.4.2、结果
结果分析:
通过控制台的打印的日志可以看出,消费者端并发模式默认情况下的重试时间间隔是根据上述介绍的是一样的,会按照一定的时间间隔重试16次,直到进入死信队列!但是,现实的业务是各种各样的,如果用户希望自定义重试次数应该怎么做呢?请看下面的分享!
1.5、并发模式客户端消息重试(自定义重试次数)
1.5.1、Demo09MQTestApp
/**
* @Author : 一叶浮萍归大海
* @Date: 2023/12/25 15:39
* @Description: RocketMQ消息重试机制
*/
@Slf4j
public class Demo09MQTestApp {
/**
* 发送消息
*/
@Test
public void demo9Producer() throws Exception {
// 1、创建一个生产者
DefaultMQProducer producer = new DefaultMQProducer("retry-producer-group");
// 2、连接NameServer
producer.setNamesrvAddr(RocketMQConstant.NAME_SERVER_ADDR);
// 生产者发送消息失败时,默认会重试2次
// producer.setRetryTimesWhenSendFailed();
// producer.setRetryTimesWhenSendAsyncFailed();
// 3、启动
producer.start();
// 4、创建消息
Message message = new Message("retry-topic", "这是一个Retry消息".getBytes(StandardCharsets.UTF_8));
// 5、发送消息
producer.send(message);
log.info("【demo9Producer】发送消息成功!");
// 关闭producer
producer.shutdown();
}
/**
* 接收广播消息
*/
@Test
public void demo9PushConsumer2() throws Exception {
// 1、创建一个消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("retry-consumer-group");
// 2、连接NameServer
consumer.setNamesrvAddr(RocketMQConstant.NAME_SERVER_ADDR);
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
// 3、订阅消息,*表示订阅该主题所有的消息
consumer.subscribe("retry-topic", "*");
// 设定重试次数
consumer.setMaxReconsumeTimes(2);
// 4、设置监听器(采用异步回调方式,一直监听)
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> messages, ConsumeConcurrentlyContext context) {
/**
* 随机生成一个[0,5)之间的随机数,模拟消费异常,大于3消费成功,小于等于3认为失败,观察默认的重试机制
*/
Integer number = new Random().nextInt(5);
log.info("number:{}", number);
if (number > 5) {
for (MessageExt message : messages) {
log.info("我是消费者【demo9PushConsumer】,我收到的消息是:{}",StrUtil.utf8Str(message.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
/**
* 返回值:消费消息成功与否
* CONSUME_SUCCESS:表明消费成功,消息会从MQ出队
* RECONSUME_LATER:表明消费失败,消息会重新回到队里,过一会儿再重新投递出来给当前消费者或者其他消费者
* 什么时候消费者会重试?
* (1)业务报错了
* (2)返回null
* (3)返回RECONSUME_LATER
*/
// return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
log.info("【消费者demo9PushConsumer重试】");
throw new RuntimeException("业务出异常了,请联系管理员!");
}
});
// 5、启动
consumer.start();
log.info("【demo9PushConsumer】启动成功,正在等待接收消息...");
// 6、挂起当前JVM
System.in.read();
}
}
1.5.2、结果
结果分析:
通过上述控制台日志的打印,可以清晰的看到消费者端在消息消费失败的情况下,执行了我们自定义的重试此次数,重试了两次没有成功,不再进行重试!虽然说消费者不用那么来回的重试了,但是未被消费的消息去哪里了呢?这就涉及到了死信队列了,关于死信队列请看下篇文章分享!