RabbitMQ 11 死信队列

死信队列

概述

消息队列中的数据,如果迟迟没有消费者来处理,就会一直占用消息队列的空间。

比如抢车票的场景,用户下单高铁票之后,会进行抢座,然后再进行付款,但是如果用户下单之后并没有及时的付款,这张票不可能一直让这个用户占用着,因为这样别人就买不到这张票了,所以会在一段时间后超时,让这张票可以继续被其他人购买。

这时,就可以使用死信队列,将那些用户超时未付款的或是用户主动取消的订单,进行进一步的处理。

那么如何构建这样的一种使用模式呢?实际上本质就是一个死信交换机+死信队列

当正常队列中的消息被判定为死信时,会被发送到对应的死信交换机,然后再通过交换机发送到死信队列中,死信队列也有对应的消费者去处理消息。

判定为死信一般是3种情况:

  • 消息被拒绝(basic.reject / basic.nack),并且requeue = false
  • 消息超时未消费。
  • 消息队列达到最大长度。

产生死信

消息被拒绝

  1. 在配置类中创建一个新的死信交换机和死信队列,并进行绑定。

    import org.springframework.amqp.core.Binding;
    import org.springframework.amqp.core.BindingBuilder;
    import org.springframework.amqp.core.Exchange;
    import org.springframework.amqp.core.ExchangeBuilder;
    import org.springframework.amqp.core.Queue;
    import org.springframework.amqp.core.QueueBuilder;
    import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * RabbitMQ配置类
     */
    @Configuration
    public class RabbitMqConfig {
    
        ...
    
        /**
         * 定义消息队列
         * @return 消息队列对象
         */
        @Bean("testQueue")
        public Queue queue(){
            return QueueBuilder
                    // 非持久化类型
                    .nonDurable("test_springboot")
                    // 指定死信交换机
                    .deadLetterExchange("dl.direct")
                    // 指定死信RoutingKey
                    .deadLetterRoutingKey("dl_test_springboot_key")   
                    .build();
        }
    
        /**
         * 构建死信交换机
         * @return 死信交换机
         */
        @Bean
        public Exchange dlExchange(){
            // 创建一个新的死信交换机
            return ExchangeBuilder.directExchange("dl.direct").build();
        }
    
        /**
         * 构建死信队列
         * @return 死信队列
         */
        @Bean
        public Queue dlQueue(){
            return QueueBuilder
                    .nonDurable("dl_test_springboot")
                    .build();
        }
    
        /**
         * 死信交换机和死信队列绑定
         * @param exchange 死信交换机
         * @param queue 死信队列
         * @return 绑定对象
         */
        @Bean
        public Binding dlBinding(@Qualifier("dlExchange") Exchange exchange,
                                 @Qualifier("dlQueue") Queue queue){
            return BindingBuilder
                    .bind(queue)
                    .to(exchange)
                    .with("dl_test_springboot_key")
                    .noargs();
        }
        
        ...
        
    }
  2. 监听正常队列和死信队列消息。

    import cn.codesail.rabbitmq.entity.User;
    import com.rabbitmq.client.Channel;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.amqp.support.AmqpHeaders;
    import org.springframework.messaging.handler.annotation.Header;
    import org.springframework.stereotype.Component;
    
    /**
     * 直连队列监听器
     */
    @Component
    public class DirectListener {
    
        /**
         * 监听正常队列消息
         */
        @RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
        public void receiver(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, User user) throws Exception {
            // 拒绝消息。第二个参数为true则消息返回队列,第二个参数为false则消息不返回队列,成为死信
            channel.basicReject(deliveryTag, false);
            System.out.println("正常队列接收到消息:" + user);
        }
        
        /**
         * 监听死信队列消息
         */
        @RabbitListener(queues = "dl_test_springboot", messageConverter = "jackson2JsonMessageConverter")
        public void receiverDl(User user) {
            System.out.println("死信队列接收到消息:" + user);
        }
    }

    正常队列消息的监听种拒绝了消息,且不返回队列,成为了死信,就会被死信队列的监听接收到。

  3. 删除原队列。删除了原队列才能创建与死信队列绑定的队列。

  4. 实现生产者。

    import cn.codesail.rabbitmq.entity.User;
    import org.junit.jupiter.api.Test;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    @SpringBootTest
    class RabbitMqSpringBootTests {
    
        /**
         * RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
         */
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        /**
         * 生产者
         */
        @Test
        void producer() {
            
            // 发送Json消息
            User user = new User();
            user.setName("张三");
            user.setAge(18);
            rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", user);
        }
    
    }
  5. 启动生产者发送消息:

    可以看到,死信队列接收到了消息。

消息超时未消费

  1. 设定队列TTL值。

    import org.springframework.amqp.core.Queue;
    import org.springframework.amqp.core.QueueBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * RabbitMQ配置类
     * 
     * @author CodeSail
     */
    @Configuration
    public class RabbitMqConfig {
    
        ...
    
        /**
         * 定义消息队列
         * @return 消息队列对象
         */
        @Bean("testQueue")
        public Queue queue(){
            return QueueBuilder
                    // 非持久化类型
                    .nonDurable("test_springboot")
                    // 指定死信交换机
                    .deadLetterExchange("dl.direct")
                    // 指定死信RoutingKey
                    .deadLetterRoutingKey("dl_test_springboot_key")
                    // 如果5秒没处理,就自动删除
                    .ttl(5000)
                    .build();
        }
    
        ...
    }
  2. 取消正常队列监听。

    import cn.codesail.rabbitmq.entity.User;
    import com.rabbitmq.client.Channel;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.amqp.support.AmqpHeaders;
    import org.springframework.messaging.handler.annotation.Header;
    import org.springframework.stereotype.Component;
    
    /**
     * 直连队列监听器
     */
    @Component
    public class DirectListener {
    
        /**
         * 监听正常队列消息
         */
    //    @RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
    //    public void receiver(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, User user) throws Exception {
    //        // 拒绝消息。第二个参数为true则消息返回队列,第二个参数为false则消息不返回队列,成为死信
    //        channel.basicReject(deliveryTag, false);
    //        System.out.println("正常队列接收到消息:" + user);
    //    }
        
        /**
         * 监听死信队列消息
         */
        @RabbitListener(queues = "dl_test_springboot", messageConverter = "jackson2JsonMessageConverter")
        public void receiverDl(User user) {
            System.out.println("死信队列接收到消息:" + user);
        }
    }
  3. 实现生产者。

    import cn.codesail.rabbitmq.entity.User;
    import org.junit.jupiter.api.Test;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import java.util.concurrent.TimeUnit;
    
    @SpringBootTest
    class RabbitMqSpringBootTests {
    
        /**
         * RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
         */
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        /**
         * 生产者
         */
        @Test
        void producer() throws InterruptedException {
            
            // 发送Json消息
            User user = new User();
            user.setName("张三");
            user.setAge(18);
            rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", user);
        }
    
    }
  4. 删除原队列。删除了原队列才能创建与死信队列绑定的设定了TTL的队列。

  5. 启动服务,监听消息。

  6. 启动生产者发送消息,等待5秒:

    可以看到,死信队列接收到了消息。

消息队列达到最大长度

  1. 设置队列最大消息长度。

    import org.springframework.amqp.core.Binding;
    import org.springframework.amqp.core.BindingBuilder;
    import org.springframework.amqp.core.Exchange;
    import org.springframework.amqp.core.ExchangeBuilder;
    import org.springframework.amqp.core.Queue;
    import org.springframework.amqp.core.QueueBuilder;
    import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * RabbitMQ配置类
     */
    @Configuration
    public class RabbitMqConfig {
    
        ...
    
        /**
         * 定义消息队列
         * @return 消息队列对象
         */
        @Bean("testQueue")
        public Queue queue(){
            return QueueBuilder
                    // 非持久化类型
                    .nonDurable("test_springboot")
                    // 指定死信交换机
                    .deadLetterExchange("dl.direct")
                    // 指定死信RoutingKey
                    .deadLetterRoutingKey("dl_test_springboot_key")
                    // 最大长度设定为3
                    .maxLength(3)
                    .build();
        }
    
        ...
        
    }
  2. 取消正常队列监听。

    import cn.codesail.rabbitmq.entity.User;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    /**
     * 直连队列监听器
     */
    @Component
    public class DirectListener {
    
        /**
         * 监听正常队列消息
         */
    //    @RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
    //    public void receiver(User user) {
    //        System.out.println("正常队列接收到消息:" + user);
    //    }
        
        /**
         * 监听死信队列消息
         */
        @RabbitListener(queues = "dl_test_springboot", messageConverter = "jackson2JsonMessageConverter")
        public void receiverDl(User user) {
            System.out.println("死信队列接收到消息:" + user);
        }
    }
  3. 删除原队列。删除了原队列才能创建与死信队列绑定的设定了最大长度的队列。

  4. 定义生产者。

    import cn.codesail.rabbitmq.entity.User;
    import org.junit.jupiter.api.Test;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import java.util.concurrent.TimeUnit;
    
    @SpringBootTest
    class RabbitMqSpringBootTests {
    
        /**
         * RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
         */
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        /**
         * 生产者
         */
        @Test
        void producer() throws InterruptedException {
            
            for (int i = 0; i < 4; i++) {
                User user = new User();
                user.setName("张三" + i);
                user.setAge(18);
                rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", user);
            }
        }
    
    }
  5. 启动生产者发送消息。

    可以看到,队列的第一个元素被挤出成为了死信。

    队列就类似于一个管道,当管道的人占满了,最后进去的人就会把最前面的人挤出去。


猜你喜欢

转载自blog.csdn.net/qq_37770674/article/details/130097887