RabbitMQ学习文档(进阶篇(Demo使用Spring编写))

一、依赖

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.14.2</version>
</dependency>

二、代码

1、死信队列

1.1、TTL过期

1.1.1、生产者

public class Provider {
    
    
    // 队列名称
    private static final String COMMON_EXCHANGE_NAME = "common_exchange";
    private static final String COMMON_QUEUE_NAME = "common_queue";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        /**
         * 使用 try-with-resource 语法,try中代码块执行结束之后将会关闭connection和channel,
         * 原因是:Connection和Channel都实现了AutoCloseable接口,所以close()方法会主动执行
         */
        try (
                // 根据上面设置的参数来创建连接
                Connection connection = factory.newConnection();
                // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
                Channel channel = connection.createChannel();
        ) {
    
    
            /**
             * 创建交换机
             * 参数1:交换机名称
             * 参数2:交换机类型;direct代表直接模式,这种模式根据消息的路由键进行消息转发,其中消息携带路由键,而队列和交换机也通过路由键进行绑定,当两个路由键完全一致的时候,交换机就会把消息发送给这个队列
             */
            channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            /**
             * 消息属性
             * new AMQP.BasicProperties(XXX)是直接从MessageProperties.PERSISTENT_TEXT_PLAIN消息持久化配置中拿过来的
             * expiration(XXX)用来设置消息过期时间,单位是毫秒
             */
            AMQP.BasicProperties properties = new AMQP.BasicProperties("text/plain",
                    null,
                    null,
                    2,
                    0, null, null, null,
                    null, null, null, null,
                    null, null).builder().expiration("10000").build();
            // 消息内容
            String message = "Hello World!";
            /**
             * 发送消息到队列
             * 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
             * 参数1:交换机名称
             *          ""是默认交换机的名称,其中默认交换机隐式绑定到每个队列,路由密钥等于队列名称,无法删除默认交换机
             * 参数2:路由名称
             *          由于默认交换机和队列之间的路由是队列名称,所以消息路由是队列名称
             * 参数3:其他消息属性,比如MessageProperties.PERSISTENT_TEXT_PLAIN就是消息持久化
             * 参数4,消息内容
             *          消息内容是字节数组,所以我们需要使用getBytes()方法
             */
            channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

1.1.2、消费者

public class Consumer {
    
    
    // 队列名称
    private static final String COMMON_EXCHANGE_NAME = "common_exchange";
    private static final String DEAD_EXCHANGE_NAME = "dead_exchange";
    private static final String COMMON_QUEUE_NAME = "common_queue";
    private static final String DEAD_QUEUE_NAME = "dead_queue";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            // 创建普通交换机
            channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            // 创建死信交换机
            channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            // 为死信指明死信交换机和死信路由
            Map<String, Object> arguments = new HashMap<>();
            // 设置死信的目的交换机
            arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
            // 设置死信携带的路由
            arguments.put("x-dead-letter-routing-key", DEAD_QUEUE_NAME);
            /**
             * 创建普通队列
             * 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
             * 参数1:队列名称
             * 参数2:是否持久化
             *          队列持久化之后,即使RabbitMQ重启,队列依然存在
             * 参数3:是否排他
             *          设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
             * 参数4:附加参数
             *          比如可以设置死信队列等
             */
            channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, arguments);
            /**
             * 创建死信队列
             */
            channel.queueDeclare(DEAD_QUEUE_NAME, true, false, false, null);
            // 绑定普通队列和普通交换机
            channel.queueBind(COMMON_QUEUE_NAME, COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME);
            // 绑定死信队列和死信交换机
            channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_QUEUE_NAME);
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println(str);
            };
            /**
             * 消费者异步监听队列消息
             * 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
             * 参数1:队列名称;
             *          指定消费者连接的队列
             * 参数2:是否自动确认;
             *        如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
             *        如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
             * 参数3:消息传递时的回调对象
             * 参数4:消费者被取消时的回调,暂时没有看懂啥意思
             */
            channel.basicConsume(DEAD_QUEUE_NAME, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

1.1.3、说明

TTL代表time to live,即消息存活时间,默认情况下队列中的消息不会过期,不过部分场景需要设置过期时间,比如淘宝购买商品的时候,必须要在两个小时内付款,否则订单自动取消;又或者说在12306上买票的时候,必须在30分钟内付款,否则订单自动取消;这些过程都可以用到死信队列,毕竟这些场景的时间都是定值,所以开发者可以设置一个普通队列,该队列的消息过期时间是2小时或者30分钟,这个根据场景来定,当然这个过程时间也可以由发送者设置,比如我们上面的例子中就是由发送者设置的,但是我们现在说的场景是固定时间由普通队列设置,并且该队列和死信交换机进行绑定,并且设置了相关路由键,也就是一旦消息过期,消息将携带设置的路由键进入死信交换机,然后死信交换机把消息交给死信队列,但是这个普通队列不允许任何消费者监听,毕竟该队列就是进行计时的,不需要被监听,我们真正应该监听的是死信队列,毕竟消息过期的时候也代表时间够了,然后我们消息经由普通队列》死信交换机》死信队列》消费者,然后我们根据订单的状态来做对应的事情,比如订单已经被支付了,那我们什么都不用做,如果订单依然没有被支付,我们就可以进行取消订单操作,如果订单已经被取消了,我们也可以什么都不用做,至于这整个流程我们可以这样来看,如下:

在这里插入图片描述

1.2、消息数量超过队列最大长度

1.2.1、生产者

public class Provider {
    
    
    // 队列名称
    private static final String COMMON_EXCHANGE_NAME = "common_exchange";
    private static final String COMMON_QUEUE_NAME = "common_queue";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        /**
         * 使用 try-with-resource 语法,try中代码块执行结束之后将会关闭connection和channel,
         * 原因是:Connection和Channel都实现了AutoCloseable接口,所以close()方法会主动执行
         */
        try (
                // 根据上面设置的参数来创建连接
                Connection connection = factory.newConnection();
                // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
                Channel channel = connection.createChannel();
        ) {
    
    
            /**
             * 创建交换机
             * 参数1:交换机名称
             * 参数2:交换机类型;direct代表直接模式,这种模式根据消息的路由键进行消息转发,其中消息携带路由键,而队列和交换机也通过路由键进行绑定,当两个路由键完全一致的时候,交换机就会把消息发送给这个队列
             */
            channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            for (int i = 1; i <= 10; i++) {
    
    
                // 消息内容
                String message = "Hello World!" + i;
                /**
                 * 发送消息到队列
                 * 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
                 * 参数1:交换机名称
                 *          ""是默认交换机的名称,其中默认交换机隐式绑定到每个队列,路由密钥等于队列名称,无法删除默认交换机
                 * 参数2:路由名称
                 *          由于默认交换机和队列之间的路由是队列名称,所以消息路由是队列名称
                 * 参数3:其他消息属性
                 *          MessageProperties.PERSISTENT_TEXT_PLAIN:消息持久化,即消息将会存储到磁盘中,只要消息队列也是持久化的,即使RabbitMQ重启,消息依然存在于队列中
                 * 参数4,消息内容
                 *          消息内容是字节数组,所以我们需要使用getBytes()方法
                 */
                channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

1.2.2、普通消费者

/*

 * Copyright (c) 2006-2021, BeiJing Leadal Technology Co.,Ltd.

 * All rights reserved.

 *

 * Project   : kms-wiki

 * Version   : 1.1.0

 * JDK Version : 1.6

 *

 * Comments

 *

 * History

 * Sr Date         ModifiedBy     Why & What is modified

 * 1. 2022/4/22  guoming      Created

 */
package com.atguigu.demo.dead_letter.max_queue_length;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.util.HashMap;
import java.util.Map;

/**
 * 消费者(作用:用来接收普通消息;注意:该消费者只用创建交换机和队列,创建完成之后就可以关闭了)
 *
 * @author guoming
 * @date 2022/4/22 23:14
 */
public class Consumer1 {
    
    
    // 队列名称
    private static final String COMMON_EXCHANGE_NAME = "common_exchange";
    private static final String DEAD_EXCHANGE_NAME = "dead_exchange";
    private static final String COMMON_QUEUE_NAME = "common_queue";
    private static final String DEAD_QUEUE_NAME = "dead_queue";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            // 创建普通交换机
            channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            // 创建死信交换机
            channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            // 为死信指明死信交换机和死信路由
            Map<String, Object> arguments = new HashMap<>();
            // 设置普通队列最大长度,也就是队列同时最多能存在的消息数量
            arguments.put("x-max-length", 6);
            // 设置死信的目的交换机
            arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
            // 设置死信携带的路由
            arguments.put("x-dead-letter-routing-key", DEAD_QUEUE_NAME);
            /**
             * 创建普通队列
             * 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
             * 参数1:队列名称
             * 参数2:是否持久化
             *          队列持久化之后,即使RabbitMQ重启,队列依然存在
             * 参数3:是否排他
             *          设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
             * 参数4:附加参数
             *          比如可以设置死信队列等
             */
            channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, arguments);
            /**
             * 创建死信队列
             */
            channel.queueDeclare(DEAD_QUEUE_NAME, true, false, false, null);
            // 绑定普通队列和普通交换机
            channel.queueBind(COMMON_QUEUE_NAME, COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME);
            // 绑定死信队列和死信交换机
            channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_QUEUE_NAME);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

1.2.3、死信消费者

public class Consumer2 {
    
    
    // 队列名称
    private static final String DEAD_EXCHANGE_NAME = "dead_exchange";
    private static final String DEAD_QUEUE_NAME = "dead_queue";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            // 创建死信交换机
            channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            // 创建死信队列
            channel.queueDeclare(DEAD_QUEUE_NAME, true, false, false, null);
            // 绑定死信队列和死信交换机
            channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_QUEUE_NAME);
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println(str);
            };
            /**
             * 消费者异步监听队列消息
             * 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
             * 参数1:队列名称;
             *          指定消费者连接的队列
             * 参数2:是否自动确认;
             *        如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
             *        如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
             * 参数3:消息传递时的回调对象
             * 参数4:消费者被取消时的回调,暂时没有看懂啥意思
             */
            channel.basicConsume(DEAD_QUEUE_NAME, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

1.2.4、说明

如果队列中的最大消息数量有限制,超出数量的消息将成为死信,我们本次的模拟办法是设置队列的最大消息数量是6,然后我们发送10条消息,那么消息队列只能存储6个,由于队列是先进先出的,所以最前面到来的4个消息将会成为死信。普通队列对应有死信交换机,并且设置了死信消息的路由是死信队列名称,这样我们就可以把死信消息发送到死信队列,然后被消费者消费掉,整个过程如下图:

在这里插入图片描述

1.3、消费者拒绝接收消息,并拒绝将消息重新放回队列

1.3.1、生产者

public class Provider {
    
    
    // 队列名称
    private static final String COMMON_EXCHANGE_NAME = "common_exchange";
    private static final String COMMON_QUEUE_NAME = "common_queue";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        /**
         * 使用 try-with-resource 语法,try中代码块执行结束之后将会关闭connection和channel,
         * 原因是:Connection和Channel都实现了AutoCloseable接口,所以close()方法会主动执行
         */
        try (
                // 根据上面设置的参数来创建连接
                Connection connection = factory.newConnection();
                // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
                Channel channel = connection.createChannel();
        ) {
    
    
            /**
             * 创建交换机
             * 参数1:交换机名称
             * 参数2:交换机类型;direct代表直接模式,这种模式根据消息的路由键进行消息转发,其中消息携带路由键,而队列和交换机也通过路由键进行绑定,当两个路由键完全一致的时候,交换机就会把消息发送给这个队列
             */
            channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            for (int i = 1; i <= 10; i++) {
    
    
                // 消息内容
                String message = "Hello World!" + i;
                /**
                 * 发送消息到队列
                 * 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
                 * 参数1:交换机名称
                 *          ""是默认交换机的名称,其中默认交换机隐式绑定到每个队列,路由密钥等于队列名称,无法删除默认交换机
                 * 参数2:路由名称
                 *          由于默认交换机和队列之间的路由是队列名称,所以消息路由是队列名称
                 * 参数3:其他消息属性
                 *          MessageProperties.PERSISTENT_TEXT_PLAIN:消息持久化,即消息将会存储到磁盘中,只要消息队列也是持久化的,即使RabbitMQ重启,消息依然存在于队列中
                 * 参数4,消息内容
                 *          消息内容是字节数组,所以我们需要使用getBytes()方法
                 */
                channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

1.3.2、普通消费者

public class Consumer1 {
    
    
    // 队列名称
    private static final String COMMON_EXCHANGE_NAME = "common_exchange";
    private static final String DEAD_EXCHANGE_NAME = "dead_exchange";
    private static final String COMMON_QUEUE_NAME = "common_queue";
    private static final String DEAD_QUEUE_NAME = "dead_queue";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            // 创建普通交换机
            channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            // 创建死信交换机
            channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            // 为死信指明死信交换机和死信路由
            Map<String, Object> arguments = new HashMap<>();
            // 设置死信的目的交换机
            arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
            // 设置死信携带的路由
            arguments.put("x-dead-letter-routing-key", DEAD_QUEUE_NAME);
            /**
             * 创建普通队列
             * 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
             * 参数1:队列名称
             * 参数2:是否持久化
             *          队列持久化之后,即使RabbitMQ重启,队列依然存在
             * 参数3:是否排他
             *          设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
             * 参数4:附加参数
             *          比如可以设置死信队列等
             */
            channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, arguments);
            /**
             * 创建死信队列
             */
            channel.queueDeclare(DEAD_QUEUE_NAME, true, false, false, null);
            // 绑定普通队列和普通交换机
            channel.queueBind(COMMON_QUEUE_NAME, COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME);
            // 绑定死信队列和死信交换机
            channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_QUEUE_NAME);
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                /**
                 * 手动拒绝接收消息
                 * 参数1:消息标签
                 * 参数2:是否重新将消息放回原来的队列
                 *       是:消息重新放回队列,然后重新发送消费者,该消息不会生成死信消息
                 *       否:消息不放回队列,该消息会成为死信消息
                 */
                channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
            };
            /**
             * 消费者异步监听队列消息
             * 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
             * 参数1:队列名称;
             *          指定消费者连接的队列
             * 参数2:是否自动确认;
             *        如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
             *        如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
             * 参数3:消息传递时的回调对象
             * 参数4:消费者被取消时的回调,暂时没有看懂啥意思
             */
            channel.basicConsume(COMMON_QUEUE_NAME, false, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

1.3.3、死信消费者

public class Consumer2 {
    
    
    // 队列名称
    private static final String DEAD_EXCHANGE_NAME = "dead_exchange";
    private static final String DEAD_QUEUE_NAME = "dead_queue";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        /**
         * 设置连接参数
         * 比如:host(localhost)、port(5672)、virtualHost(/)、
         * userName(guest)、password(guest)、connectionTimeout(60s)
         * 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
         * 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
         * 下面的代码作用是说明一下如何设置我们想要的值
         */
        factory.setHost("localhost");
        // 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            // 创建死信交换机
            channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            // 创建死信队列
            channel.queueDeclare(DEAD_QUEUE_NAME, true, false, false, null);
            // 绑定死信队列和死信交换机
            channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_QUEUE_NAME);
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println(str);
            };
            /**
             * 消费者异步监听队列消息
             * 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
             * 参数1:队列名称;
             *          指定消费者连接的队列
             * 参数2:是否自动确认;
             *        如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
             *        如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
             * 参数3:消息传递时的回调对象
             * 参数4:消费者被取消时的回调,暂时没有看懂啥意思
             */
            channel.basicConsume(DEAD_QUEUE_NAME, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

1.3.4、说明

如果普通消费者通过basicReject()方法拒绝了消息,并且通过设置requeue参数为false,那代表不将消息放回队列,这样的话消息将成为死信,在上面的例子中,普通队列中的死信将传递给死信交换机,并且消息路由键是死信队列名称,这样的话,死信队列的消费者就可以接收到被拒绝的消息,画图说明如下:

在这里插入图片描述

2、优先级队列

2.1、生产者

public class Provider {
    
    

    // 普通交换机
    public static final String COMMON_EXCHANGE_NAME = "common_exchange3";

    // 普通队列
    public static final String COMMON_QUEUE_NAME = "common_queue3";

    public static void main(String[] args) {
    
    
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (
                Connection connection = factory.newConnection();
                Channel channel = connection.createChannel();
        ) {
    
    
            // 声明交换机
            channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

            // 消息发送
            for (int i = 1; i <= 10; i++) {
    
    
                String message = "测试消息" + i;
                if (i == 5) {
    
    
                    // 默认优先级都是0,设置5号消息的优先级是5
                    AMQP.BasicProperties properties = new AMQP.BasicProperties("text/plain",
                            null,
                            null,
                            2,
                            5, null, null, null,
                            null, null, null, null,
                            null, null).builder().build();
                    channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));
                } else {
    
    
                    channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
                }
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

2.2、消费者

public class Consumer {
    
    

    // 普通交换机
    public static final String COMMON_EXCHANGE_NAME = "common_exchange3";

    // 普通队列
    public static final String COMMON_QUEUE_NAME = "common_queue3";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            // 声明交换机
            channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            // 创建队列
            Map<String, Object> arguments = new HashMap<>(1);
            arguments.put("x-max-priority", 10);
            channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, arguments);
            // 绑定普通队列和普通交换机
            channel.queueBind(COMMON_QUEUE_NAME, COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME);
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println(str);
            };
            channel.basicConsume(COMMON_QUEUE_NAME, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

2.3、说明

默认的消息优先级是0,其中消息优先级越大,在队列中越靠前,然后越早被发送,我们可以设置队列中的最大消息优先级,代码如下:

Map<String, Object> arguments = new HashMap<>(1);
arguments.put("x-max-priority", 10);
channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, arguments);

设置之后代表发送者发送的消息最大优先级不应该超过10,其实在rabbitmq官网上也说明了消息优先级应该是1~255之间的正整数,并且队列中设置的最大优先级不应该超过10,避免优先级过多的时候将会导致 Erlang 进程消耗更多的CPU 资源,然后我们可以在发送者发送消息的时候设置消息优先级,如下:

// 默认优先级都是0,设置5号消息的优先级是5
AMQP.BasicProperties properties = new AMQP.BasicProperties("text/plain",
        null,
        null,
        2,
        5, null, null, null,
        null, null, null, null,
        null, null).builder().build();
channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));

3、自定义延迟交换机

3.1、生产者

public class Provider {
    
    

    // 延时交换机
    public static final String DELAY_EXCHANGE_NAME = "delay_exchange2";

    // 普通队列
    public static final String COMMON_QUEUE_NAME = "common_queue2";

    public static void main(String[] args) {
    
    
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (
                Connection connection = factory.newConnection();
                Channel channel = connection.createChannel();
        ) {
    
    
            // 创建延迟交换机;注意交换机类型是"x-delayed-message",另外该交换机本质上还是一个direct类型交换机
            Map<String, Object> arguments = new HashMap<>(1);
            arguments.put("x-delayed-type", "direct");
            channel.exchangeDeclare(DELAY_EXCHANGE_NAME, "x-delayed-message", true, false, arguments);

            // 10s消息;说明:10s消息应该比2s的消息更晚到达
            String message = "我是10s的消息";
            HashMap<String, Object> header = new HashMap<>();
            // 设置延迟10s:这个延迟操作是在交换机中进行了,10s之后会将消息发送给消息队列,当然本次使用的交换机是延迟交换机
            header.put("x-delay", 10000);
            AMQP.BasicProperties properties = new AMQP.BasicProperties("text/plain",
                    null,
                    null,
                    2,
                    0, null, null, null,
                    null, null, null, null,
                    null, null).builder().headers(header).build();
            channel.basicPublish(DELAY_EXCHANGE_NAME, COMMON_QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));

            // 2s消息;说明:2s消息应该比10s的消息更早到达
            message = "我是2s的消息";
            header = new HashMap<>();
            header.put("x-delay", 2000);
            properties = new AMQP.BasicProperties("text/plain",
                    null,
                    null,
                    2,
                    0, null, null, null,
                    null, null, null, null,
                    null, null).builder().headers(header).build();
            channel.basicPublish(DELAY_EXCHANGE_NAME, COMMON_QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

3.2、消费者

public class Consumer {
    
    

    // 延时交换机
    public static final String DELAY_EXCHANGE_NAME = "delay_exchange2";

    // 普通队列
    public static final String COMMON_QUEUE_NAME = "common_queue2";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();
            // 创建延迟交换机
            Map<String, Object> arguments = new HashMap<>(1);
            arguments.put("x-delayed-type", "direct");
            channel.exchangeDeclare(DELAY_EXCHANGE_NAME, "x-delayed-message", true, false, arguments);
            // 创建队列
            channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, null);
            // 绑定普通队列和延迟交换机
            channel.queueBind(COMMON_QUEUE_NAME, DELAY_EXCHANGE_NAME, COMMON_QUEUE_NAME);
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println(str);
            };
            channel.basicConsume(COMMON_QUEUE_NAME, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

3.3、Windows中安装并启用延迟交换机插件

(1)首先从https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases下载对应版本的插件
在这里插入图片描述
(2)解压到rabbitmq安装目录下面的plugins目录下面
在这里插入图片描述
(3)在rabbitmq安装目录下面的sbin目录下打开DOS窗口,然后执行rabbitmq-plugins enable rabbitmq_delayed_message_exchange就可以让插件起作用
(4)我们打开rabbitmq控制台之后,选择交换机,然后就可以看到如下的交换机类型
在这里插入图片描述

3.3、说明

在此之前,我们如果想实现延时效果,需要借助于过期时间和死信队列来打成,假设我们设置一个消息过期时间是2s,另外一个消息过期时间是10s,然后先将过期时间10s的消息发送到一个队列中,在将过期时间是2s的消息发送到同样队列中,然后让消息过期,之后通过死信交换机把消息交给死信队列,另外我们有消息着在监听着死信队列,我们肯定以为2s的消息会首先被消费,然后10s的消息在被消费,其实不是这样的,而是10s的消息被消费之后,2s的消息才被消费,原因就是队列是先进先出的,所以我们无法实现想要的效果,上面的文字用画图表示如下:

在这里插入图片描述
为了解决该问题,我们安装了延迟交换机插件,其中延时过程是在交换机中进行的,当延时结束之后就会将消息发送给消息队列,所以就不会产生上面的延时失败问题了,在代码编写过程中,可以设置交换机类型为x-delayed-message,即延时交换机,并且在消息头中设置延迟时间,如下所示:

// 创建延迟交换机;注意交换机类型是"x-delayed-message",另外该交换机本质上还是一个direct类型交换机
Map<String, Object> arguments = new HashMap<>(1);
arguments.put("x-delayed-type", "direct");
channel.exchangeDeclare(DELAY_EXCHANGE_NAME, "x-delayed-message", true, false, arguments);

// 10s消息;说明:10s消息应该比2s的消息更晚到达
String message = "我是10s的消息";
HashMap<String, Object> header = new HashMap<>();
// 设置延迟10s:这个延迟操作是在交换机中进行了,10s之后会将消息发送给消息队列,当然本次使用的交换机是延迟交换机
header.put("x-delay", 10000);
AMQP.BasicProperties properties = new AMQP.BasicProperties("text/plain",
        null,
        null,
        2,
        0, null, null, null,
        null, null, null, null,
        null, null).builder().headers(header).build();
channel.basicPublish(DELAY_EXCHANGE_NAME, COMMON_QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));

4、备用交换机

4.1、生产者

public class Provider {
    
    

    // 普通交换机
    public static final String COMMON_EXCHANGE_NAME = "common_exchange5";

    // 备用交换机
    public static final String BACKUP_EXCHANGE_NAME = "backup_exchange5";

    // 普通队列
    public static final String COMMON_QUEUE_NAME = "common_queue5";

    public static void main(String[] args) {
    
    
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (
                Connection connection = factory.newConnection();
                Channel channel = connection.createChannel();
        ) {
    
    
            // 声明普通交换机
            Map<String, Object> arguments = new HashMap<>();
            arguments.put("alternate-exchange", BACKUP_EXCHANGE_NAME);
            channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, arguments);

            // 消息发送
            String message = "测试消息1";
            channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));

            // 消息发送(说明:我们故意将路由键写错,让交换机无法传递消息到普通队列,然后消息被被传递到了备用交换机中,然后我们的备用队列就可以收到消息了)
            message = "测试消息2";
            channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME + "1", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));

        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

4.2、普通消费者

public class Consumer1 {
    
    

    // 普通交换机
    public static final String COMMON_EXCHANGE_NAME = "common_exchange5";

    // 备用交换机
    public static final String BACKUP_EXCHANGE_NAME = "backup_exchange5";

    // 普通队列
    public static final String COMMON_QUEUE_NAME = "common_queue5";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();

            // 声明普通交换机
            Map<String, Object> arguments = new HashMap<>();
            arguments.put("alternate-exchange", BACKUP_EXCHANGE_NAME);
            channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, arguments);

            // 声明备用交换机
            channel.exchangeDeclare(BACKUP_EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

            // 创建队列
            channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, null);
            // 绑定死信队列和死信交换机
            channel.queueBind(COMMON_QUEUE_NAME, COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME);
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new java.lang.String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println("普通队列接收到的消息:" + str);
            };
            channel.basicConsume(COMMON_QUEUE_NAME, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

4.3、备用消费者

public class Consumer2 {
    
    

    // 普通交换机
    public static final String COMMON_EXCHANGE_NAME = "common_exchange5";

    // 备用交换机
    public static final String BACKUP_EXCHANGE_NAME = "backup_exchange5";

    // 普通队列
    public static final String COMMON_QUEUE_NAME = "common_queue5";

    // 备用队列
    public static final String BACKUP_QUEUE_NAME = "backup_queue5";

    public static void main(String[] args) {
    
    
        // 创建连接工厂,配置连接相关设置
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try {
    
    
            // 根据上面设置的参数来创建连接
            Connection connection = factory.newConnection();
            // 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
            Channel channel = connection.createChannel();

            // 声明备用交换机
            channel.exchangeDeclare(BACKUP_EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

            // 创建队列
            channel.queueDeclare(BACKUP_QUEUE_NAME, true, false, false, null);
            // 绑定死信队列和死信交换机
            channel.queueBind(BACKUP_QUEUE_NAME, BACKUP_EXCHANGE_NAME, "");
            // 消息创建中的回调对象;用来处理从队列异步推送过来的消息
            DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
                String str = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println("备用队列接收到的消息:" + str);
            };
            channel.basicConsume(BACKUP_QUEUE_NAME, true, deliverCallback, consumerTag -> {
    
    
            });
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

4.4、说明

如果交换机无法把消息发送给队列,比如通过消息路由键无法匹配到对应队列,那么在我们设置了普通交换机对应的备用交换机的前提下,消息将会发送给备用交换机,进而发送给备用队列,然后消费者就可以消费消息,过程画图如下:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_42449963/article/details/124543692