6. RabbitMQ 死信队列的详细操作编写

6. RabbitMQ 死信队列的详细操作编写

@[toc]


1. 死信的概念

先从概念上解释上搞清楚这个定义,死信,顾名思义就是无法被消费者读取/消费的消息 。字面意思可以这样理解,一般来说,producer 将消息投递到 broker 或者直接到 queue 里了,consumer 从 queue 取出消息进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费 ,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有与之对应的 ------> 死信队列

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

死信的来源: (死信的常见方式,大概时如下三种方式:)

  1. 消息的 TTL 过期。
  2. 队列达到最大长度(队列满了,无法再添加数据到 RabbitMQ 当中了)
  3. 当消息被拒绝(basic.reject 或 basic.nack) 并且 requeue = fasle

死信的实战案例:

2. 消息 TTL 过期(触发死信队列)

生产者代码

java 复制代码
package com.rainbowsea.rabbitmq.eight;


import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 死信队列------> 生产者代码
 */
public class Producer {
    // 普通交换机的名称
    public static final String NORMAL_EXCHANGE = "normal_exchange";


    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();
        // 死信消息,设置 TTL时间
        AMQP.BasicProperties properties =
                new AMQP.BasicProperties()
                .builder().expiration("10000").build();

        for (int i = 0; i < 11; i++) {
            String message = "info" + i;
            channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
            //channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", null, message.getBytes());
        }
    }

}

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

特别说明: 配置正常队列,无法处理的队列信息,转发给死信队列的,配置,只需在该正常队列当中配置映射死信队列的信息配置即可,而死信队列就是作为一个正常的队列处理读取/消费死信队列当中的内容即可。

java 复制代码
package com.rainbowsea.rabbitmq.eight;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;


/**
 * 死信队列-消费者1(正常队列)
 */
public class Consumer01 {

    // 普通交换机的名称
    public static final String NORMAL_EXCHANGE = "normal_exchange";
    // 死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";

    // 普通队列的名称
    public static final String NORMAL_QUEUE = "normal_queue";
    // 死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

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

        // 声明普通队列
        Map<String, Object> arguments = new HashMap<>();
        // 过期时间 10s = 10000ms 参数 key 是固定值的不可以随便写
        //arguments.put("x-message-ttl",100000);
        //正常队列设置死信交换机 参数 key 是固定值
        arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        // 设置死信RoutingKey
        arguments.put("x-dead-letter-routing-key", "lisi");
        // 声明的是一个正常的队列
        channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);

        //////////////////////////////////////
        // 声明死信队列
        channel.queueDeclare(DEAD_QUEUE, false, false, false, null);

        // 绑定普通的交换机与普通的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)
        channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
        System.out.println("等待接收消息");

        // 接收消息
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Consumer01 正常队列接收到的消息" + new String(message.getBody(), "UTF-8"));


        };

        channel.basicConsume(NORMAL_QUEUE, true, deliverCallback, consumerTag -> {
        });
    }
}

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

java 复制代码
package com.rainbowsea.rabbitmq.eight;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;


/**
 * 死信队列-消费者1(死信队列)
 */
public class Consumer02 {
    ;
    // 死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";
    // 死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();
        // 声明死信和普通交换机类型为 direct
        channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
        // 绑定死信的交换机与死信的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)
        channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");
        // 接收消息
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Consumer02 死信接收到的消息" + new String(message.getBody(), "UTF-8"));

        };

        channel.basicConsume(DEAD_QUEUE, true, deliverCallback, consumerTag -> {
        });
    }
}

3. 队列超过队列的最大长度(触发死信队列)

  1. 基于上面代码:将生产者代码去掉 TTL 属性
java 复制代码
package com.rainbowsea.rabbitmq.eight;


import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 死信队列------> 生产者代码
 */
public class Producer {
    // 普通交换机的名称
    public static final String NORMAL_EXCHANGE = "normal_exchange";


    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();
        // 死信消息,设置 TTL时间
        //AMQP.BasicProperties properties =
        //        new AMQP.BasicProperties()
        //        .builder().expiration("10000").build();

        for (int i = 0; i < 11; i++) {
            String message = "info" + i;
            //channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
            channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", null, message.getBytes());
        }
    }

}
  1. C1 消费者修改以下代码(启动之后关闭该消费者 模拟其接收不到消息)

注意此时需要把原先队列删除 因为参数改变了,因为 RabbitMQ 不可以将一个一开始形态的队列,修改成另外一个形态的队列。所以需要删除后在创建。

java 复制代码
package com.rainbowsea.rabbitmq.eight;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;


/**
 * 死信队列-消费者1(正常队列)
 */
public class Consumer01 {

    // 普通交换机的名称
    public static final String NORMAL_EXCHANGE = "normal_exchange";
    // 死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";

    // 普通队列的名称
    public static final String NORMAL_QUEUE = "normal_queue";
    // 死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

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

        // 声明普通队列
        Map<String, Object> arguments = new HashMap<>();
        // 过期时间 10s = 10000ms 参数 key 是固定值的不可以随便写
        //arguments.put("x-message-ttl",100000);
        //正常队列设置死信交换机 参数 key 是固定值
        arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        // 设置死信RoutingKey
        arguments.put("x-dead-letter-routing-key", "lisi");
        // 设置正常队列的长度的限制,x-max-length参数 key 是固定值的,不可以随便写
        arguments.put("x-max-length",6);
        // 声明的是一个正常的队列
        channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);

        //////////////////////////////////////
        // 声明死信队列
        channel.queueDeclare(DEAD_QUEUE, false, false, false, null);

        // 绑定普通的交换机与普通的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)
        channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
        System.out.println("等待接收消息");

        // 接收消息
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Consumer01 正常队列接收到的消息" + new String(message.getBody(), "UTF-8"));


        };

        channel.basicConsume(NORMAL_QUEUE, true, deliverCallback, consumerTag -> {
        });
    }
}

同时 C1 正常队列也是读取到信息了

然后,启动之后关闭该消费者C1, 模拟其接收不到消息

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

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

java 复制代码
package com.rainbowsea.rabbitmq.eight;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;


/**
 * 死信队列-消费者1(死信队列)
 */
public class Consumer02 {
    ;
    // 死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";
    // 死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();
        // 声明死信和普通交换机类型为 direct
        channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
        // 绑定死信的交换机与死信的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)
        channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");
        // 接收消息
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Consumer02 死信接收到的消息" + new String(message.getBody(), "UTF-8"));

        };

        channel.basicConsume(DEAD_QUEUE, true, deliverCallback, consumerTag -> {
        });
    }
}

4. 消息被拒(触发死信队列)

  1. .消息生产者代码同上生产者一致,不做修改
java 复制代码
package com.rainbowsea.rabbitmq.eight;


import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 死信队列------> 生产者代码
 */
public class Producer {
    // 普通交换机的名称
    public static final String NORMAL_EXCHANGE = "normal_exchange";


    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();
        // 死信消息,设置 TTL时间
        //AMQP.BasicProperties properties =
        //        new AMQP.BasicProperties()
        //        .builder().expiration("10000").build();

        for (int i = 0; i < 11; i++) {
            String message = "info" + i;
            //channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
            channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", null, message.getBytes());
        }
    }

}
  1. C1 消费者代码(启动之后关闭该消费者 模拟其接收不到消息) 添加上拒绝消息的操作。

注意此时需要把原先队列删除 因为参数改变了,因为 RabbitMQ 不可以将一个一开始形态的队列,修改成另外一个形态的队列。所以需要删除后在创建。

java 复制代码
 // 接收消息
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            // 获取到消息信息
            String msg = new String(message.getBody(), "UTF-8");
            if (msg.equals("info5")) {
                System.out.println("Consumer01 接收的消息是: " + msg + "此消息被 Consumer01 拒绝了");
            } else {
                System.out.println("Consumer01 接收的消息: " + msg);
            }
        };
java 复制代码
package com.rainbowsea.rabbitmq.eight;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;


/**
 * 死信队列-消费者1(正常队列)
 */
public class Consumer01 {

    // 普通交换机的名称
    public static final String NORMAL_EXCHANGE = "normal_exchange";
    // 死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";

    // 普通队列的名称
    public static final String NORMAL_QUEUE = "normal_queue";
    // 死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

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

        // 声明普通队列
        Map<String, Object> arguments = new HashMap<>();
        // 过期时间 10s = 10000ms 参数 key 是固定值的不可以随便写
        //arguments.put("x-message-ttl",100000);
        //正常队列设置死信交换机 参数 key 是固定值
        arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        // 设置死信RoutingKey
        arguments.put("x-dead-letter-routing-key", "lisi");
        // 设置正常队列的长度的限制,x-max-length参数 key 是固定值的,不可以随便写
        //arguments.put("x-max-length", 6);
        // 声明的是一个正常的队列
        channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);

        //////////////////////////////////////
        // 声明死信队列
        channel.queueDeclare(DEAD_QUEUE, false, false, false, null);

        // 绑定普通的交换机与普通的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)
        channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
        System.out.println("等待接收消息");

        // 接收消息
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            // 获取到消息信息
            String msg = new String(message.getBody(), "UTF-8");
            if (msg.equals("info5")) {
                System.out.println("Consumer01 接收的消息是: " + msg + "此消息被 Consumer01 拒绝了");
                // 拒绝对应 message.getEnvelope().getDeliveryTag() 的信息
                channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
            } else {
                System.out.println("Consumer01 接收的消息: " + msg);
                // 手动应答,接收消息
                channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
            }
        };

        channel.basicConsume(NORMAL_QUEUE, false, deliverCallback, consumerTag -> {
        });
    }
}
  1. C2 消费者代码不变;启动消费者 1 然后再启动消费者 2
java 复制代码
package com.rainbowsea.rabbitmq.eight;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;


/**
 * 死信队列-消费者1(死信队列)
 */
public class Consumer02 {
    ;
    // 死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";
    // 死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();
        // 声明死信和普通交换机类型为 direct
        channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
        // 绑定死信的交换机与死信的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)
        channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");
        // 接收消息
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Consumer02 死信接收到的消息" + new String(message.getBody(), "UTF-8"));

        };

        channel.basicConsume(DEAD_QUEUE, true, deliverCallback, consumerTag -> {
        });
    }
}

总结: 上述的,消息TTL过期,队列超过最大长度,消息被拒等等这三种方式,都是让消息无法被正常消费者读取掉,由于消费者没有读取到该消息,有为了防止,该消息被丢失,所以就将其消息放入到一个死信交换机------>死信队列中 ,最后让一个死信消费者读取到保存起来,这样队列当中的消息就没有被丢失了。

特别说明: 配置正常队列,无法处理的队列信息,转发给死信队列的,配置,只需在该正常队列当中配置映射死信队列的信息配置即可,而死信队列就是作为一个正常的队列处理读取/消费死信队列当中的内容即可。

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

    // 普通队列的名称
    public static final String NORMAL_QUEUE = "normal_queue";
    // 死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue"; 

Channel channel = RabbitMQUtils.getChannel();
        // 声明死信和普通交换机类型为 direct
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);

        // 声明普通队列
        Map<String, Object> arguments = new HashMap<>();
        // 过期时间 10s = 10000ms 参数 key 是固定值的不可以随便写
        //arguments.put("x-message-ttl",100000);
        //正常队列设置死信交换机 参数 key 是固定值
        arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        // 设置死信RoutingKey
        arguments.put("x-dead-letter-routing-key", "lisi");
        // 设置正常队列的长度的限制,x-max-length参数 key 是固定值的,不可以随便写
        //arguments.put("x-max-length", 6);
        // 声明的是一个正常的队列
        channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);

        //////////////////////////////////////
        // 声明死信队列
        channel.queueDeclare(DEAD_QUEUE, false, false, false, null);

        // 绑定普通的交换机与普通的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)
        channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
        System.out.println("等待接收消息");

5. 最后:

"在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。"

相关推荐
mghio8 小时前
Dubbo 中的集群容错
java·微服务·dubbo
咖啡教室13 小时前
java日常开发笔记和开发问题记录
java
咖啡教室13 小时前
java练习项目记录笔记
java
鱼樱前端14 小时前
maven的基础安装和使用--mac/window版本
java·后端
RainbowSea14 小时前
5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明
java·消息队列·rabbitmq
李少兄16 小时前
Unirest:优雅的Java HTTP客户端库
java·开发语言·http
此木|西贝16 小时前
【设计模式】原型模式
java·设计模式·原型模式
可乐加.糖16 小时前
一篇关于Netty相关的梳理总结
java·后端·网络协议·netty·信息与通信