Bootstrap

SpringBoot整合Rabbitmq之死信队列

1. 死信队列

死信:无法被消费方消费掉的消息,称为死信。如果死信一直留在队列中,会导致一直被消费,却从不消费成功。所以 rabbitmq 专门开辟了一个来存放死信的队列,叫死信队列(DLX,dead-letter-exchange

1.1. 死信从何而来

  • 消息消费方调用了 basicNack()basicReject(),并且参数都是 requeue = false,则消息会路由进死信队列
  • 消息过期,过了 TTL 存活时间,就是消费方在 TTL 时间之内没有消费,则消息会路由进死信队列
  • 队列设置了 x-max-length 最大消息数量且当前队列中的消息已经达到了这个数量,再次投递,消息将被挤掉,被挤掉的消息会路由进死信队列
  • 有一种场景需要注意下:消费者设置了自动 ACK,当重复投递次数达到了设置的最大 retry 次数之后,消息也会投递到死信队列,但是内部的原理还是调用了 basicNack()basicReject()
#开启rabbitmq的生产端重试机制,默认是false,默认重试 3 次
spring.rabbitmq.template.retry.enabled=true
#开启rabbitmq的消费端重试机制,默认是false,默认重试 3 次
spring.rabbitmq.listener.simple.retry.enabled=true
#设置重试的次数
spring.rabbitmq.listener.simple.retry.max-attempts=5

1.2. 死信流程

死信流程图

在这里插入图片描述

  • 正常业务消息被投递到正常业务的 Exchange,该 Exchange 根据路由键将消息路由到绑定的正常队列
  • 正常业务队列中的消息变成了死信消息之后,会被自动投递到该队列绑定的死信交换机上(并带上配置的路由键,如果没有指定死信消息的路由键,则默认继承该消息在正常业务时设定的路由键)
  • 死信交换机收到消息后,将消息根据路由规则路由到指定的死信队列
  • 消息到达死信队列后,可监听该死信队列,处理死信消息

死信队列并不是什么特殊的队列,只不过是绑定在死信交换机上的队列。死信交换机也不是什么特殊的交换机,只不过是用来接收死信的交换机,所以可以为任何类型(Direct、Fanout、Topic。一般来说,会为每个业务队列分配一个独有的路由 key,并对应的配置一个死信队列进行监听,也就是说,一般会为 每个重要的业务队列配置一个死信队列

2. 死信队列实现

  • SpringBoot 版本 2.0.6.RELEASE
  • Rabbitmq 版本 3.8.3

分别创建项目消息生产方,消息接收方两个项目

2.1. 消息发送方

2.1.1. 配置文件

server.port=8080

#配置rabbitmq服务器
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#确认消息已发送到交换机
spring.rabbitmq.publisher-confirms=true
#确认消息已发送到队列
spring.rabbitmq.publisher-returns=true

2.1.2. 队列、交换机定义及绑定

@Configuration
public class DeadLetterConfig {
   

    // --------------------------正常业务队列--------------------------
    // 业务队列 A
    @Bean
    public Queue businessQueueA() {
   
        Map<String, Object> args = new HashMap<>();
        // x-dead-letter-exchange:这里声明当前业务队列绑定的死信交换机
        args.put("x-dead-letter-exchange", Constant.DEAD_LETTER_EXCHANGE);
        // x-dead-letter-routing-key:这里声明当前业务队列的死信路由 key
        args.put("x-dead-letter-routing-key", Constant.DEAD_LETTER_QUEUE_A_ROUTING_KEY);
        return new Queue(Constant.BUSINESS_QUEUE_A, true, false, false, args);
    }

    // 业务队列 B
    @Bean
    public Queue businessQueueB() {
   
        Map<String, Object> args = new HashMap<>();
        // x-dead-letter-exchange:这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", Constant.DEAD_LETTER_EXCHANGE);
        // x-dead-letter-routing-key:这里声明当前队列的死信路由 key
        args.put("x-dead-letter-routing-key", Constant.DEAD_LETTER_QUEUE_B_ROUTING_KEY);
        return new Queue(Constant.BUSINESS_QUEUE_B, true, false, false, args);
    }

    // 业务队列的交换机
    @Bean
    public TopicExchange businessTopicExchange() {
   
        return new TopicExchange(Constant.BUSINESS_EXCHANGE, true, false);
    }

    // 业务队列 A 与交换机绑定,并指定 Routing_Key
    @Bean
    public Binding businessBindingA() {
   
        return BindingBuilder.bind(businessQueueA()).to(businessTopicExchange()).with(Constant.BUSINESS_QUEUE_A_ROUTING_KEY);
    }

    // 业务队列 B 与交换机绑定,并指定 Routing_Key
    @Bean
    public Binding businessBindingB() {
   
;