RabbitMQ 高级特性——重试机制

文章目录

前言

前面我们学习了 RabbitMQ 保证消息传递可靠性的机制------消息确认、持久化和发送发确认,那么对于消息确认和发送方确认,如果接收方没有收到消息,那么这时该怎么做呢?这就要提到 RabbitMQ 的重试机制了,当发送方发送的消息没有成功到达接收方,那么发送方就会使用重试机制来向接收方重新发送消息,如果接收方因为程序逻辑错误引起的,那么不管发送方重新发送多少次,接收方都不能正确的处理这个消息,那么发送方也不能无休止的向接收方发送消息,当发送的次数超过某个数量的时候,那么这个消息就会被标记为死信,然后被处理掉。那么这篇文章将详细的说明一下 RabbitMQ 的重试机制。

重试机制

RabbitMQ的重试机制是一种强大的功能,它允许在消息处理失败时自动重试,从而提高系统的可靠性和稳定性。

重试机制的触发条件

  1. 消息发送失败:当消息发送到RabbitMQ服务器时,如果因为网络问题、认证失败或其他原因导致发送失败,RabbitMQ会尝试重试发送。
  2. 消息消费失败:当消费者从队列中获取消息并尝试处理时,如果因为某种原因(如业务逻辑错误、系统异常等)导致处理失败,RabbitMQ会根据预设的重试策略进行重试。

我们都知道 Spring AMQP 提供了四种确认策略:NONE、MANUAL、AUTO。当确认策略为 NONE 的时候队列不管这个消息是否被成功处理都会将这个消息从队列中删除,而确认策略为 MANUAL 的时候则更多的靠程序本身处理,那么重试机制更多的使用在确认策略为 AUTO 的情况下。

配置文件设置

yaml 复制代码
spring:
  rabbitmq:
    addresses: amqp://admin:admin@x.x.x.x:5672/test
    listener:
      simple:
        acknowledge-mode: auto # 确认模式
        retry:
          enabled: true # 开始重试机制
          initial-interval: 5000ms # 每次重试的间隔时间
          max-attempts: 5 # 最大重试次数

生命交换机、队列和绑定关系

java 复制代码
public class Constants {
    public static final String RETRY_EXCHANGE = "retry.exchange";
    public static final String RETRY_QUEUE = "retry.queue";
}
java 复制代码
@Configuration
public class RabbitConfig {
    @Bean("retryExchange")
    public DirectExchange retryExchange() {
        return ExchangeBuilder.directExchange(Constants.RETRY_EXCHANGE).durable(true).build();
    }

    @Bean("retryQueue")
    public Queue retryQueue() {
        return QueueBuilder.durable(Constants.RETRY_QUEUE).build();
    }

    @Bean("retryBinding")
    public Binding retryBinding(@Qualifier("retryQueue") Queue queue, @Qualifier("retryExchange") DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("retry");
    }
}

生产者发送消息

java 复制代码
@RequestMapping("/producer")
@RestController
public class ProducerController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @RequestMapping("/retry")
    public String retry() {
        rabbitTemplate.convertAndSend(Constants.RETRY_EXCHANGE,"retry","rabbitmq retry");
        return "消息发送成功";
    }
}

消费消息

java 复制代码
@Component
public class RetryListener {
    @RabbitListener(queues = Constants.RETRY_QUEUE)
    public void listener(Message message, Channel channel) {
        System.out.println("接收到消息:" + message + channel);
        //制造出异常使得消费者不能正常处理消息
        int ret = 3/0;
        System.out.println("消息处理成功");
    }
}

我们先将重试机制的配置给注释掉,然后运行之后会发现消费者会重复读取一条消息并且一直抛异常,这是因为:

由于监听器方法抛出了异常并且没有被捕获,Spring AMQP将不会发送ACK,RabbitMQ将不断重新投递这条消息给消费者,直到它最终被确认、被拒绝(并可能发送到死信队列)或队列被删除。

那么我们配置重试机制的重试次数之后再看看什么效果:

可以发现,当配置了重试次数之后,包括第一次投递消息,队列一共给消费者发送了五次消息,并且这个五次的消息的 deliveryTag 是一样的,也就说明是队列的重试机制投递的,五次之后队列便不再向该消费者发送该消息,这样就避免了因消费者的程序逻辑问题而导致队列无休止的向消费者发送消息的问题,并且还保证了消息传递的可靠性。

那么我们将消息的确认策略更换为 MANUAL 手动确认的方式,然后看看这个重试机制的配置会不会生效:

java 复制代码
@RabbitListener(queues = Constants.RETRY_QUEUE)
public void listener(Message message, Channel channel) throws IOException, InterruptedException {
    long deliveryTag = message.getMessageProperties().getDeliveryTag();
    try {
        System.out.println("接收到消息:" + message + channel);
        //制造出异常使得消费者不能正常处理消息
        int ret = 3/0;
        System.out.println("消息处理成功");
        channel.basicAck(deliveryTag,false);
    } catch (Exception e) {
        Thread.sleep(1000);
        //第三个参数requeue表示被拒绝的消息是否重新进入队列,true表示重新进入队列并且重新投递这个消息,否则直接丢弃
        channel.basicNack(deliveryTag,false,true);
    }
}

发现当确认策略为 manual 手动确认的时候,我们的重试机制的配置是不生效的,并且可以发现我们的消息的 deliveryTag 是不断递增的,也就是说之前拒绝的消息每次都会重新入队列然后重新投递,跟重试机制是不一样的。

而如果我们 basicNack 方法的第三个参数 requeue 参数的值为 fasle 时候,那么这个被拒绝的消息就会被丢弃或者投递到的死信队列中。

使用重试机制时需要注意:

  1. 自动确认模式下:程序逻辑出现异常,即使多次重试还是失败,消息也会被自动确认,那么这条消息就会丢失。

  2. 在手动确认模式下,消费者需要显式地发送ACK(Acknowledgment)或NACK来告诉RabbitMQ消息是否已被成功处理。如果发生异常且没有发送ACK,消息将保持为unacked状态,这允许RabbitMQ重新将消息发送给其他消费者(如果配置了多个消费者)或根据队列的其他配置(如死信队列)来处理未确认的消息。然而,如果所有消费者都无法处理该消息,它将导致消息在队列中积压,除非配置了适当的超时机制或死信队列来处理这些长时间未确认的消息。

相关推荐
z落落2 小时前
C# 事件(Event)+自定义带参数事件例子
开发语言·分布式·c#
我是一颗柠檬4 小时前
【Java项目技术亮点】分库分表+数据路由策略:单表5000万后的架构升级方案
java·开发语言·分布式·架构
半夜修仙4 小时前
RabbitMQ中如何保证消息的可靠性传输
java·分布式·中间件·rabbitmq·github·java-rabbitmq
小二·7 小时前
Redis 7 分布式缓存架构实战
redis·分布式·缓存
zhuhai_xigedian7 小时前
源网荷储一体化 vs 传统供用电模式:差异、优势与转型路径
大数据·人工智能·分布式·系统架构·能源
凯源智能9 小时前
屋顶分布式光伏箱变远程测控实战:宝鸡法士特项目高效交付解析
分布式
Amy1870211182310 小时前
东南亚智慧物流园区的“隐形守护者”:有源滤波柜如何驯服变频器5/7次谐波
分布式·能源
闪电悠米11 小时前
黑马点评-Redis 消息队列-04_stream_seckill_order
数据库·redis·分布式·缓存·oracle·junit·lua
HLAIA光子11 小时前
分布式锁与事务:你的微服务可能根本不需要它们
分布式·后端·微服务
bmjIjFNC811 小时前
Redis分布式锁进第九十一篇
数据库·redis·分布式