重试机制
生产者
DefaultMQProducer producer = new DefaultMQProducer("retry-producer-group");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
// 发送失败重试次数
producer.setRetryTimesWhenSendFailed(2);
producer.setRetryTimesWhenSendAsyncFailed(2);
String key = UUID.randomUUID().toString();
Message message = new Message("retryTopic", "vip1", "我是一条重试消息".getBytes());
producer.send(message);
producer.shutdown();
消费者
消费者的重试机制是基于延迟消息来实现的
并发模式 MessageListenerConcurrently
- 最重试次数:16(默认)
- 重试间隔:10s, 30s, 1m, 2m, 3m, 4m, 5m, 6m, 7m, 8m, 9m, 10m, 20m, 30m, 1h, 2h
- 消费失败,未超过最大重试次数时,消息进入重试队列,队列的 Topic 为
%RETRY%消费者组名称
这时可以通过getReconsumeTimes()
获取到重试次数 - 消费失败,超过最大重试次数时,消息进入死信队列,队列的 Topic 为
%DLQ%消费者组名称
- 进入死信队列的消息处理:创建订阅这个死信队列 Topic 的消费者
业务中可以在重试达到多少次的时候记录下来,人工进行干预,避免进入死信队列
/**
* 如果进入死信队列,这个队列的 Topic 为:%DLQ%retry-consumer-group
*/
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("retry-consumer-group");
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.subscribe("retryTopic", "*");
// 并发模式:设置最大重试次数
consumer.setMaxReconsumeTimes(10);
// 并发模式:设置重试队列的延迟级别
consumer.setDelayLevelWhenNextConsume(3);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
MessageExt messageExt = msgs.get(0);
// 重试次数(并发模式下才会重试,并发模式下才能通过此方法获取当前消息的重试次数)
int times = messageExt.getReconsumeTimes();
if (times > 3){
// 3次都消费失败,记录下来,返回成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
// 业务处理,报错会视为消费失败消息重新回到队列
...
}
});
consumer.start();
顺序模式 MessageListenerOrderly
- 最大重试次数:Integer.MAX_VALUE
- 重试间隔:默认 1000ms(1秒)
- 和并发模式不一样:重试时是在原始队列重试(不会进入专门的重试队列)、不会进入死信队列,会无限次重试,直至成功(会阻塞当前队列)
// 为每个队列维护错误计数器
private ConcurrentMap<Integer, AtomicInteger> queueErrorCounters = new ConcurrentHashMap<>();
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("retry-consumer-group");
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.subscribe("retryTopic", "*");
// 顺序模式:设置顺序消费挂起时间(毫秒)
consumer.setSuspendCurrentQueueTimeMillis(5000);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
consumeMessage(msgs, context);
}
});
consumer.start();
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
MessageExt msg = msgs.get(0);
int queueId = msg.getQueueId();
try {
// 业务处理
queueErrorCounters.remove(queueId); // 成功则清除计数器
return ConsumeOrderlyStatus.SUCCESS;
} catch (Exception e) {
// 错误计数+1
AtomicInteger counter = queueErrorCounters.computeIfAbsent(queueId, k -> new AtomicInteger(0));
int retryCount = counter.incrementAndGet();
// 超过阈值则跳过(需记录告警)
if(retryCount > MAX_RETRY){
log.error("消息[{}]重试超过{}次,人工介入", msg.getMsgId(), MAX_RETRY);
return ConsumeOrderlyStatus.SUCCESS; // 慎用!可能造成数据不一致
}
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}
}