当前位置:   article > 正文

RabbitMQ之消息重试机制_rabbitmq消息重试

rabbitmq消息重试

1.消息重试机制

消费者消费消息的时候,发生异常情况,导致消息未确认,该消息会被重复消费(默认没有重复次数,即无限循环消费),但可以通过设置重试次数以及达到重试次数之后的消息处理

在这里插入图片描述

spring:
  rabbitmq:
    port: 5672
    host: 127.0.0.1
    username: guest
    password: guest
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.案例重现

首先来看一个案例:
自动确认模式

package com.yzm.rabbitmq_04.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    public static final String RETRY_QUEUE = "retry_queue";
    public static final String RETRY_EXCHANGE = "retry.exchange";
    public static final String RETRY_KEY = "retry.key";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
package com.yzm.rabbitmq_04.sender;

import com.yzm.rabbitmq_04.config.RabbitConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Sender {

    private final RabbitTemplate rabbitTemplate;

    public Sender(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    @GetMapping("/retry")
    public void retry() {
        String message = "Hello World !";
        rabbitTemplate.convertAndSend(RabbitConfig.RETRY_EXCHANGE, RabbitConfig.RETRY_KEY, message);
        System.out.println(" [ 生产者 ] Sent ==> '" + message + "'");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

这里创建队列和交换机是通过注解实现的,跟前面的篇章的创建方式是等同的
消费者在处理消息时发生异常情况

package com.yzm.rabbitmq_04.receiver;

import com.yzm.rabbitmq_04.config.RabbitConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class Receiver {

    private int count = 1;

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = RabbitConfig.RETRY_QUEUE),
            exchange = @Exchange(value = RabbitConfig.RETRY_EXCHANGE, type = ExchangeTypes.DIRECT),
            key = {RabbitConfig.RETRY_KEY}
    ))
    public void retry(Message message) {
    	log.info("当前执行次数:{}", count++);
        log.info(" [ 消费者@A号 ] 接收到消息 ==> '" + new String(message.getBody()));
        // 制造异常
        int i = 1 / 0;
        log.info(" [ 消费者@A号 ] 消费了消息 ==> '" + new String(message.getBody()));
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

启动,运行结果
在这里插入图片描述
无限循环报错
停止后,消息重回Ready状态
在这里插入图片描述

3.实现消息重试

启动重试机制

spring:
  rabbitmq:
    port: 5672
    host: 127.0.0.1
    username: guest
    password: guest
    listener:
      simple:
        retry:
          enabled: true
          max-attempts: 5 # 重试次数
          max-interval: 10000   # 重试最大间隔时间
          initial-interval: 2000  # 重试初始间隔时间
          multiplier: 2 # 间隔时间乘子,间隔时间*乘子=下一次的间隔时间,最大不能超过设置的最大间隔时间
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

重启,运行结果:
在这里插入图片描述
首先重试次数是5次(包括自身初次消费的那次)没问题,
第一个执行时间:09:50:39,第二次执行时间:09:50:41 ,跟第一次的时间间隔2秒(初始间隔)没问题
第三次执行时间:09:50:45,跟第二次的时间间隔 = 初始间隔(2秒) * 间隔因子(2) = 4秒没问题
以此类推… 2s、4s、8s、16s(最后一次16s,但由于设置的最大间隔10s,所以16s就变成了10s)
最后查看retry_queue队列,消息没有了,也就是说重试5次失败之后就会移除该消息
移除操作是由日志中的这个类处理:RejectAndDontRequeueRecoverer(拒绝和不要重新排队)

具体实现可以看SimpleRabbitListenerContainerFactoryConfigurer类中的MessageRecoverer接口,这个接口有一个cover方法,用来实现重试完成之后对消息的处理,源码如下:
在这里插入图片描述
RejectAndDontRequeueRecoverer 是 接口 MessageRecoverer 的一个实现类
同时 MessageRecoverer 还有另外两个实现类,分别是
RepublishMessageRecoverer(重新发布消息)和ImmediateRequeueMessageRecoverer(立即重新返回队列)

先来使用下 ImmediateRequeueMessageRecoverer 重新排队
在RabbitConfig中配置

package com.yzm.rabbitmq_04.config;

import org.springframework.amqp.rabbit.retry.ImmediateRequeueMessageRecoverer;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

	public static final String RETRY_QUEUE = "retry_queue";
    public static final String RETRY_EXCHANGE = "retry.exchange";
    public static final String RETRY_KEY = "retry.key";    

    @Bean
    public MessageRecoverer messageRecoverer() {
        return new ImmediateRequeueMessageRecoverer();
    }  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

重启,运行结果:
在这里插入图片描述
可以看出:重试5次之后,返回队列,然后再重试5次,周而复始直到不抛出异常为止,这样还是会影响后续的消息消费。

接着使用 RepublishMessageRecoverer 重新发布
在RabbitConfig中配置

package com.yzm.rabbitmq_04.config;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.retry.ImmediateRequeueMessageRecoverer;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    public static final String RETRY_EXCHANGE = "retry.exchange";
    public static final String RETRY_QUEUE = "retry_queue";
    public static final String RETRY_FAILURE_QUEUE = "retry_failure_queue";
    public static final String RETRY_FAILURE_KEY = "retry.failure.key";
    public static final String RETRY_KEY = "retry.key";

//    @Bean
    public MessageRecoverer messageRecoverer() {
        return new ImmediateRequeueMessageRecoverer();
    }

    @Bean
    public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate) {
    	// 需要配置交换机和绑定键
        return new RepublishMessageRecoverer(rabbitTemplate, RETRY_EXCHANGE, RETRY_FAILURE_KEY);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

创建重试失败消息监听

package com.yzm.rabbitmq_04.receiver;

import com.yzm.rabbitmq_04.config.RabbitConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class Receiver {

    private int count = 1;

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = RabbitConfig.RETRY_QUEUE),
            exchange = @Exchange(value = RabbitConfig.RETRY_EXCHANGE, type = ExchangeTypes.DIRECT),
            key = {RabbitConfig.RETRY_KEY}
    ))
    public void retry(Message message) {
        log.info("当前执行次数:{}", count++);
        log.info(" [ 消费者@A号 ] 接收到消息 ==> '" + new String(message.getBody()));
        // 制造异常
        int i = 1 / 0;
        log.info(" [ 消费者@A号 ] 消费了消息 ==> '" + new String(message.getBody()));
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = RabbitConfig.RETRY_FAILURE_QUEUE),
            exchange = @Exchange(value = RabbitConfig.RETRY_EXCHANGE),
            key = {RabbitConfig.RETRY_FAILURE_KEY}
    ))
    public void retryFailure(Message message) {
        log.info(" [ 消费者@重试失败号 ] 接收到消息 ==> '" + new String(message.getBody()));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

重启,运行结果:
在这里插入图片描述
重试5次之后,将消息 Republishing failed message to exchange ‘retry.exchange’ with routing key retry-key 转发到重试失败队列,由重试失败消费者消费

相关链接

首页
上一篇:交换机
下一篇:死信队列

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号