RabbitMQ死信队列

1,149 阅读2分钟

RabbitMQ死信队列

死信队列介绍

当一个消息在一个队列中变成死信之后,它能被重新自动推送到另一个交换机——死信交换机,我们声明一个死信队列,和私信交换机进行绑定之后,死信就会被路由到死信队列。

消息变为死信有以下几种情况:

  • 消息被否定确认,使用channel.basicNackchannel.basicReject,并且此时requeue被设置为false。
  • 消息在队列的存活时间超过设置的TTL。
  • 消息队列的消息数量超过最大长度。

死信队列的目的是防止消息丢失,处理消费过程中的异常情况。

死信队列演示

下面演示消息被否定的情况。

  1. 配置

      rabbitmq:
        addresses: localhost:5672
        username: root
        password: root
        virtual-host: /
        listener:
          simple:
            # 被拒绝的消息不会重新放回队列
            default-requeue-rejected: false
            # 消费者需手动确认消息
            acknowledge-mode: manual
    
  2. 在Configuration中声明queue、exchange并绑定

        private String bizExchange = "biz.direct.exchange";
        private String bizQueue = "biz.queue";
        private String routingKey = "key1";
        private String dlExchange = "dl.exchange";
        private String dlQueue = "dl.queue";
    
        // 声明业务queue,设置x-dead-letter-exchange参数,指定dlx
        // 本例中dlx是fanout类型的,不用设置x-dead-letter-routing-key
        @Bean
        public Queue bizQueue() {
            return new Queue(bizQueue, true, false, false, Collections.singletonMap("x-dead-letter-exchange", dlExchange));
        }
    
        // 声明业务exchange
        @Bean
        public DirectExchange bizExchange() {
            return new DirectExchange(bizExchange, true, false);
        }
    
        // 声明绑定关系
        @Bean
        public Binding bizBinding(Queue bizQueue, DirectExchange bizExchange) {
            return BindingBuilder.bind(bizQueue).to(bizExchange).with(routingKey);
        }
    
        // 声明dead-letter-queue,dlq就是一个普通的队列
        @Bean
        public Queue dlQueue() {
            return new Queue(dlQueue, true, false, false);
        }
    
        // 声明dead-letter-exchange,dlx可以是任何类型的,这里是fanout类型
        @Bean
        public FanoutExchange dlExchange() {
            return new FanoutExchange(dlExchange, true, false);
        }
    
        // 声明dlq和dlx绑定关系
        @Bean
        public Binding dlBinding(Queue dlQueue, FanoutExchange dlExchange) {
            return BindingBuilder.bind(dlQueue).to(dlExchange);
        }
    
  3. 消费者直接nack

        @RabbitListener(queues = "biz.queue")
        public void handle(Message message, Channel channel) throws IOException {
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
        }
    
  4. 生产者

        public static void main(String[] args) throws InterruptedException {
            ApplicationContext context = SpringApplication.run(Main.class, args);
            RabbitTemplate template = context.getBean(RabbitTemplate.class);
            template.convertAndSend("biz.direct.exchange", "key1", "abc");
        }
    
  5. 启动后在RabbitMQ管理页面上可以看到,消息最终被路由进了dlQueue

消息过期

设置消息的TTL有两种方式,第一种是直接对消息本身进行单独设置(expiration),每条消息的TTL可以不同;第二种是通过队列属性设置队列中所有消息的过期时间(x-message-ttl)。两种方式一起使用时,消息的TTL以两者中较小的数值为准。

  1. 设置消息单独的TTL

    消息属性带上expiration

            MessageProperties messageProperties = new MessageProperties();
            messageProperties.setExpiration(String.valueOf(2000)); // 2000ms
            template.send("biz.direct.exchange", "key1", new Message("abcd".getBytes(), messageProperties));
    
  2. 设置队列中消息的TTL

    创建队列时,带上x-message-ttl参数:

    new Queue("test.queue", false, false, true, Collections.singletonMap("x-message-ttl", Integer.valueOf(2000))); // 2000ms
    

队列长度限制

队列长度限制有两种方式,第一种是对队列中消息的条数进行限制x-max-length;第二种是对消息的总字节量进行限制x-max-length-bytes,只计算消息体,不包括消息header、properties。

同样是在创建或声明队列的时候指定参数。