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

相关推荐
夏天的味道٥4 小时前
使用 Java 执行 SQL 语句和存储过程
java·开发语言·sql
冰糖码奇朵5 小时前
大数据表高效导入导出解决方案,mysql数据库LOAD DATA命令和INTO OUTFILE命令详解
java·数据库·sql·mysql
好教员好5 小时前
【Spring】整合【SpringMVC】
java·spring
浪九天6 小时前
Java直通车系列13【Spring MVC】(Spring MVC常用注解)
java·后端·spring
堕落年代7 小时前
Maven匹配机制和仓库库设置
java·maven
功德+n7 小时前
Maven 使用指南:基础 + 进阶 + 高级用法
java·开发语言·maven
香精煎鱼香翅捞饭7 小时前
java通用自研接口限流组件
java·开发语言
ChinaRainbowSea8 小时前
Linux: Centos7 Cannot find a valid baseurl for repo: base/7/x86_64 解决方案
java·linux·运维·服务器·docker·架构
囧囧 O_o8 小时前
Java 实现 Oracle 的 MONTHS_BETWEEN 函数
java·oracle
去看日出8 小时前
RabbitMQ消息队列中间件安装部署教程(Windows)-2025最新版详细图文教程(附所需安装包)
java·windows·中间件·消息队列·rabbitmq