RabbitMQ之死信队列

RabbitMQ之死信队列

    • [1. 死信的概念](#1. 死信的概念)
    • [2. 死信的来源](#2. 死信的来源)
    • [3. 死信实战](#3. 死信实战)
      • [3.1 代码架构图](#3.1 代码架构图)
      • [3.2 消息 TTL 过期](#3.2 消息 TTL 过期)
      • [3.3 队列达到最大长度](#3.3 队列达到最大长度)
      • [3.4 消息被拒](#3.4 消息被拒)

1. 死信的概念


死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer将消息投递到 broker 或者直接到 queue 里了,consumer 从 queue 取消息进行消费,但某些时候由于特定的原因 queue 中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。

应用场景:为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息消费发生异常时,将消息投入死信队列中。还有比如说:用户在商场下单成功并点击支付后在指定时间未支付时自动失效。

2. 死信的来源


  • 消息TLL过期
  • 队列达到最大长度(队列满了,无法再添加数据到 mq 中)
  • 消息被拒绝(basic.reject 或 basic.nack )并且 requeue = false

3. 死信实战

3.1 代码架构图


3.2 消息 TTL 过期


生产者代码

java 复制代码
public class Producer {
    private static final String NORMAL_EXCHANGE = "normal_exchange";

    public static void main(String[] args) throws Exception {
        try (Channel channel = RabbitMqUtils.getChannel()) {
            channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
            // 设置消息的TTL时间
            new AMQP.BasicProperties().builder().expiration("10000").build();
            // 该消息是用作演示队列个数限制
            for (int i = 0; i < 11; i++) {
                String message = "info" + i;
                channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", null, message.getBytes());
                System.out.println("生产者发送消息:" + message);
            }
        }
    }
}

消费者 C1 代码(启动之后关闭消费者 模拟其接收不到消息

java 复制代码
public class Consumer01 {
    // 普通交换机名称
    private static final String NORMAL_EXCHANGE = "normal_exchange";
    // 死信交换机名称
    private static final String DEAD_EXCHANGE = "dead_exchange";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        // 声明死信和普通交换机 类型为direct
        channel.exchangeDeclare(NORMAL_EXCHANGE, "direct");
        channel.exchangeDeclare(DEAD_EXCHANGE, "direct");

        // 声明死信队列
        String deadQueue = "dead-queue";
        channel.queueDeclare(deadQueue, false, false, false, null);
        // 死信队列绑定死信交换机 routingkey
        channel.queueBind(deadQueue, DEAD_EXCHANGE, "lisi");

        // 正常队列绑定死信队列信息
        Map<String, Object> params = new HashMap<>();
        // 正常队列设置死信交换机 参数key 是固定值
        params.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        // 正常队列设置死信 routing-key 参数是固定值
        params.put("x-dead-letter-routing-key", "lisi");

        String normalQueue = "normal-queue";
        channel.queueDeclare(normalQueue, false, false, false, null);
        channel.queueBind(normalQueue, NORMAL_EXCHANGE, "zhangsan");

        System.out.println("等待接受消息.....");
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println("Consumer01 接受消息" + message);
        };
        channel.basicConsume(normalQueue, true, deliverCallback, consumerTag -> {
        });
    }
}

生产者未发送消息

生产者发送了10条消息,此时正常消息队列有10条未消费信息

时间过去10秒,正常队列里面的消息由于没有被消费,消息进入死信队列

消费者 C2 代码(以上步骤完成后 启动 C2 消费者 它消费死信队列里面的消息)

java 复制代码
public class Consumer02 {
    private static final String DEAD_EXCHANGE = "dead_exchange";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        channel.exchangeDeclare(DEAD_EXCHANGE, "direct");
        String deadQueue = "dead-queue";
        channel.queueDeclare(deadQueue, false, false, false, null);
        channel.queueBind(deadQueue, DEAD_EXCHANGE, "lisi");
        System.out.println("等待接受死信队列消息.....");
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println("Consumer02 接受死信队列的消息" + message);
        };
        channel.basicConsume(deadQueue, true, deliverCallback, consumerTag -> {
        });
    }
}

3.3 队列达到最大长度


  1. 消费生产者代码去掉 TTL 属性

    java 复制代码
    public class Producer {
        private static final String NORMAL_EXCHANGE = "normal_exchange";
    
        public static void main(String[] args) throws Exception {
            try (Channel channel = RabbitMqUtils.getChannel()) {
                channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
                // 该消息是用作演示队列个数限制
                for (int i = 0; i < 11; i++) {
                    String message = "info" + i;
                    channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", null, message.getBytes());
                    System.out.println("生产者发送消息:" + message);
                }
            }
        }
    }
  2. C1 消费者修改以下代码(启动之后关闭该消费者 模拟其接收不到消息)

    java 复制代码
      // 正常队列绑定死信队列信息
      Map<String, Object> params = new HashMap<>();
      // 正常队列设置死信交换机 参数key 是固定值
      params.put("x-dead-letter-exchange", DEAD_EXCHANGE);
      // 正常队列设置死信 routing-key 参数是固定值
      params.put("x-dead-letter-routing-key", "lisi");
      // 设置正常队列长度的限制
      params.put("x-max-length", 6);// 添加该代码

    注意此时需要把原先队列删除,因为参数改变了

  3. C2 消费者代码不变(启动 C2 消费者)

3.4 消息被拒


  1. 消息生产者代码同上生产者一致

  2. C1 消费者代码(启动之后关闭该消费者,模拟接收不到消息)

    java 复制代码
    public class Consumer01 {
        // 普通交换机名称
        private static final String NORMAL_EXCHANGE = "normal_exchange";
        // 死信交换机名称
        private static final String DEAD_EXCHANGE = "dead_exchange";
    
        public static void main(String[] args) throws Exception {
            Channel channel = RabbitMqUtils.getChannel();
            // 声明死信和普通交换机 类型为direct
            channel.exchangeDeclare(NORMAL_EXCHANGE, "direct");
            channel.exchangeDeclare(DEAD_EXCHANGE, "direct");
    
            // 声明死信队列
            String deadQueue = "dead-queue";
            channel.queueDeclare(deadQueue, false, false, false, null);
            // 死信队列绑定死信交换机 routingkey
            channel.queueBind(deadQueue, DEAD_EXCHANGE, "lisi");
    
            // 正常队列绑定死信队列信息
            Map<String, Object> params = new HashMap<>();
            // 正常队列设置死信交换机 参数key 是固定值
            params.put("x-dead-letter-exchange", DEAD_EXCHANGE);
            // 正常队列设置死信 routing-key 参数是固定值
            params.put("x-dead-letter-routing-key", "lisi");
    
            String normalQueue = "normal-queue";
            channel.queueDeclare(normalQueue, false, false, false, null);
            channel.queueBind(normalQueue, NORMAL_EXCHANGE, "zhangsan");
    
            System.out.println("等待接受消息.....");
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                String message = new String(delivery.getBody(), "UTF-8");
                if (message.equals("info5")) {
                    System.out.println("Consumer01 接收到消息" + message + "并拒绝签收该消息");
                    // requeue 设置为false 代表拒绝重新入队 该队列如果配置了死信交换机将发送到死信队列中
                    channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false);
                } else {
                    System.out.println("Consumer01 接受消息" + message);
                    channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                }
            };
            boolean autoAck = false;
            channel.basicConsume(normalQueue, autoAck, deliverCallback, consumerTag -> {
            });
        }
    }

    生产者发送消息之后

  3. C2 消费者代码不变

    启动消费者1 然后再启动消费者2

相关推荐
hrrrrb11 分钟前
【Spring Security】Spring Security 概念
java·数据库·spring
小信丶11 分钟前
Spring 中解决 “Could not autowire. There is more than one bean of type“ 错误
java·spring
翰林小院1 小时前
【RabbitMQ】 RabbitMQ Overview
分布式·rabbitmq
周杰伦_Jay1 小时前
【Java虚拟机(JVM)全面解析】从原理到面试实战、JVM故障处理、类加载、内存区域、垃圾回收
java·jvm
程序员小凯5 小时前
Spring Boot测试框架详解
java·spring boot·后端
豐儀麟阁贵5 小时前
基本数据类型
java·算法
_extraordinary_5 小时前
Java SpringMVC(二) --- 响应,综合性练习
java·开发语言
程序员 Harry6 小时前
深度解析:使用ZIP流式读取大型PPTX文件的最佳实践
java
wxweven6 小时前
校招面试官揭秘:我们到底在寻找什么样的技术人才?
java·面试·校招
陈陈爱java7 小时前
新知识点背诵
java