RabbitMQ

RabbitMq

一、介绍

​ MQ(Message Queue),本质是一个FIFO队列,先入先出,队列中存放的是消息,是一种跨进程的通讯机制,对通信的双方进行解耦,使得发送方只依赖MQ

MQ功能:

1.流量削峰

使用消息队列做缓存,把同时间的大量请求放入MQ中,进行排队处理,解决高并发问题

2.应用解耦

在多个系统之间调用中,如果其中某一个系统故障,会导致一系列的操作不可用。使用MQ可以减少系统调

用,当某个系统故障时,该系统需要处理的数据会被缓存在MQ中,在系统修复后,可以继续处理相关数据

在这里插入图片描述

3.异步处理

存在两个服务,服务A调用服务B,需要知道服务B是否处理完,在服务B处理完成后,将信息发送到MQ,然

后MQ再将信息发送个服务A,通知它处理完成

在这里插入图片描述

消息队列分类:

1.kafka

​ 基于pull模式处理消息,适用处理大数据量消息,吞吐量高,适合做日志采集

2.RocketMQ

​ 阿里开发用于双11交易,可靠性高,适合处理高并发场景

3.RabbitMQ

​ 结合erlang语言,性能好,时效性在微秒级

4.ActiveMQ
是Apache下的开源项目,老牌稳定性高的消息队列

二、基本概念

RabbitMQ作为一个消息中间件,它不处理消息,而是进行接受、存储、转发消息

1.生产者

产生数据发送消息的程序

2.交换机

接受来自生产者的消息,将消息推送到队列中

3.队列

消息缓冲区,生产者发送的消息存放在队列中,消费者从队列中接受数据

4.消费者

接受消息的程序

在这里插入图片描述

Broker:接受分发消息的应用,即RabbitMQ

Virtual Host:多个用户连接同一个RabbitMQ时,划分出不同的vhost,每个用户在自己的vhost中创建交换机

和队列

Connection:生产者、消费者和Broker之间的连接

Channel:Rabbitmq建立连接开销巨大,Channel是内部逻辑连接通过Channel进行通讯,每一个Channel之

间时完全隔离的

三、安装

RabbitMQ推荐使用Linux环境安装,首先查看Linux对应的版本:

uname -a

在这里插入图片描述

由于RabbitMQ基于erlang语言开发,需要下载erlang对应的安装包

RabbitMQ和Erlang版本要求 :https://www.rabbitmq.com/which-erlang.html

由于我的Linux环境为el7版本,erlang 24、25版本只支持el8、el9,我只能选择erlang23.3.4.11,相应的

rabbitmq选择3.9.14

erlang下载地址:https://github.com/rabbitmq/erlang-rpm/releases?q=23.3.4.11&expanded=true

rabbitmq下载地址:https://github.com/rabbitmq/rabbitmq-server/releases?q=3.9.14&expanded=true

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

然后将安装包传到Linux系统中

在这里插入图片描述

安装rpm文件:

rpm -ivh erlang-23.3.4.11-1.el7.x86_64.rpm
yum install socat -y //rabbitmq-server依赖包
rpm -ivh rabbitmq-server-3.9.14-1.el7.noarch.rpm

添加开机启动RabbitMQ服务:

chkconfig rabbitmq-server on

启动服务:

/sbin/service rabbitmq-server start

查看服务状态:

/sbin/server rabbitmq-server status

停止服务:

/sbin/server rabbitmq-server stop

开启web管理插件(需要关闭RabbitMQ服务):

rabbitmq-plugins enable rabbitmq_management

重启服务后,浏览器访问 ip地址:15672(需要关闭Linux防火墙,如果是云服务器,需要配置安全组规则,开

启端口15672),使用默认账户密码:guest/guest访问

在这里插入图片描述

没有权限登录,需要自己创建用户:

rabbitmqctl add_user 用户名 密码

设置用户角色:

rabbitmqctl set_user_tags 用户名 administrator //管理员角色

设置用户权限:

//设置用户具有'/'这个vhost中所有资源的配置、写、读权限
//set_permissions [-p <vhostpath>] <user> <conf> <write> <read>
rabbitmqctl set_permissions -p "/" 用户名 ".*" ".*" ".*"	

显示当前用户和角色

rabbitmqctl list_users

使用创建的用户登录

在这里插入图片描述

四、Java操作队列

创建maven工程,导入依赖:

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

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>
1.Hello world(最简单的队列)

在这里插入图片描述

包含一个生产者、一个队列、一个消费者

/**
 * 生产者
 */
public class Producer {

    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        //创建工厂
        ConnectionFactory factory = new ConnectionFactory();
        //配置IP地址
        factory.setHost("ip地址");
        //配置用户名
        factory.setUsername("用户名");
        //配置密码
        factory.setPassword("密码");
        //创建连接
        Connection connection = factory.newConnection();
        //创建通道
        Channel channel = connection.createChannel();
        //生成队列
        // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        //  队列名称         是否持久化     是否只能一个消费者消费    是否自动删除   其他参数
        Map<String, Object> argument = new HashMap<>();
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 发送的消息
        String message = "Hello World!";

        // 发送消息
        // String exchange, String routingKey, BasicProperties props, byte[] body
        // 发送到那个交换机(空串使用默认交换机)    路由key值    其他参数       消息体
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
    }
}
/**
 * 消费者
 */
public class Consumer {

    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        //创建工厂
        ConnectionFactory factory = new ConnectionFactory();
        //配置IP地址
        factory.setHost("ip地址");
        //配置用户名
        factory.setUsername("用户名");
        //配置密码
        factory.setPassword("密码");

        //创建连接
        Connection connection = factory.newConnection();
        //创建通道
        Channel channel = connection.createChannel();

        DeliverCallback deliverCallback = (consumerTag, message) -> System.out.println(new String(message.getBody()));

        CancelCallback cancelCallback = System.out::println;

        //消费消息
        // String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback
        // 队列名称   消费成功是否自动应答   消费者成功消费的回调     消费者取消消费的回调
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
        System.out.println("消费者等待接收消息:");
    }
}

在这里插入图片描述

2.优先级队列

定义队列时设置最大优先级(0 ~ 255),发送消息时指定优先级,在消费者进行消费时,会根据优先级排列的

顺序消费消息

public class PriorityProducer {

    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitmqUtils.getChannel();

        Map<String, Object> argument = new HashMap<>();

        // 设置队列最大优先级为10, 优先级范围为0 ~ 10,默认为0 ~ 255  优先级太多 排序消耗的资源太多
        argument.put("x-max-priority", 10);
        channel.queueDeclare(QUEUE_NAME, false, false, false, argument);

        //需要先将所有消息发送到队列之中后才能让消费者消费
        for (int i = 0; i <= 10; i++) {
            String message = String.valueOf(i);
            AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().priority(i).build();
            channel.basicPublish("", QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));
            System.out.println("发送消息成功:" + message);
        }
    }
}

测试:

消费者消费的消息优先级由高到低
在这里插入图片描述

在这里插入图片描述

3.Work Queue(工作队列)

在这里插入图片描述

包含一个生产者、一个队列、二个消费者

封装工具类:

/**
 * Rabbitmq工具类: 连接工厂、创建信道
 */
public class RabbitmqUtils {

    public static Channel getChannel() throws IOException, TimeoutException {
        //创建工厂
        ConnectionFactory factory = new ConnectionFactory();
        //配置IP地址
        factory.setHost("ip地址");
        //配置用户名
        factory.setUsername("用户名");
        //配置密码
        factory.setPassword("密码");
        //创建连接
        Connection connection = factory.newConnection();
        //返回创建的通道
        return connection.createChannel();
    }
}
/**
 * 生产者
 */
public class Producer {
    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        //使用工具类获取信道
        Channel channel = RabbitmqUtils.getChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        Scanner scanner = new Scanner(System.in);

        String flag;

        // 循环遍历发送消息
        do
        {
            System.out.print("请输入发送的消息内容:");
            String message = scanner.nextLine();
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println("成功发送消息:\t" + message);
            System.out.print("是否结束发送消息(y/n):");
            flag = scanner.nextLine();
        }while(!flag.equalsIgnoreCase("y"));
        System.out.println("---------------发送消息结束--------------");
    }
}
/**
 * 多个消费者 默认轮询消费消息
 */
public class Consumer {
    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {

        Channel channel = RabbitmqUtils.getChannel();

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println(Thread.currentThread().getName() + "接受消息:" + new String(message.getBody()));
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println(Thread.currentThread().getName() + "取消消费" + consumerTag);
        };

        channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
        System.out.println("-----------" + Thread.currentThread().getName() +Thread.currentThread().getId() + "等待接受消息-----------");
    }
}

在这里插入图片描述

先启动两个消费者,然后启动生产者发送消息(不然消息会被一个消费者消费):
在这里插入图片描述

两个消费者各自消费两条消息,是轮询的:

在这里插入图片描述

在这里插入图片描述

4.消息应答

为保证消息在发送过程中不丢失,使用消息应答机制,即消费者在接受消息并处理消息之后,告诉

RabbitMQ它已经处理了,此时RabbitMQ才能将消息删除

自动应答:

没有对传递消息数量进行限制,可能导致消费者接受不及最终内存耗尽崩溃,仅使用于消费者可以高效并快

速处理大量消息的情况下

手动应答:

Channel.basicAck(用于肯定应答)

void basicAck(long deliveryTag, boolean multiple)

multiple:指定是否批量应答

在这里插入图片描述

**注意:**不推荐使用批量确认,确认消息处理完才进行应答,保证消息不丢失

Channel.basicNack(用于否定应答)

void basicNack(long deliveryTag, boolean multiple, boolean requeue)

requeue:是否将不确认的消息重新入队列

Channel.basicReject(用于否定应答) 不处理该消息直接拒绝,将消息丢弃

void basicReject(long deliveryTag, boolean requeue)

手动应答保证消息不丢失:

/**
* 生产者
*/
public class Producer {

    private static final String QUEUE_NAME = "ack";

    public static void main(String[] args) throws IOException, TimeoutException {

        Channel channel = RabbitmqUtils.getChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        Scanner scanner = new Scanner(System.in);

        String flag;

        do
        {
            System.out.print("请输入发送的消息内容:");
            String message = scanner.nextLine();
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println("时间:"+ LocalDateTime.now() + "成功发送消息:\t" + message);
            System.out.print("是否结束发送消息(y/n):");
            flag = scanner.nextLine();
        }while(!flag.equalsIgnoreCase("y"));
        System.out.println("---------------发送消息结束--------------");
    }
}
/**
* 消费者1(处理消息快)
*/
public class ConsumerAck {
    private static final String QUEUE_NAME = "ack";

    public static void main(String[] args) throws IOException, TimeoutException {

        Channel channel = RabbitmqUtils.getChannel();

        System.out.println("Consumer1等待接受消息,处理时间短...");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("时间:"+ LocalDateTime.now() + "Consumer1接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));

            //消息应答
            //long deliveryTag, boolean multiple
            //  消息的标记          是否批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("Consumer1取消消费消息:" + consumerTag);
        };
        
        channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
    }

}
/**
 * 消费者2(处理消息慢)
 * 如果消费者宕机或由于其他原因,消息未应答,消息会自动重新入队,交给其他消费者消费应答
 */
public class ConsumerLongAck {
    private static final String QUEUE_NAME = "ack";

    public static void main(String[] args) throws IOException, TimeoutException {

        Channel channel = RabbitmqUtils.getChannel();

        System.out.println("Consumer2等待接受消息,处理时间长...");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            try {
                TimeUnit.SECONDS.sleep(15);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("时间:"+ LocalDateTime.now() + "Consumer2接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));

            //消息应答
            //long deliveryTag, boolean multiple
            //  消息的标记          是否批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("Consumer2取消消费消息:" + consumerTag);
        };

        channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
    }
}

测试:

正常情况下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

异常情况(Consumer2处理消息过程中宕机):

消息不会丢失,会转发给Consumer1进行处理

在这里插入图片描述

在这里插入图片描述

5.RabbitMQ持久化

为保证消息不丢失,将RabbitMQ队列和发送的消息进行持久化

/**
 * 队列、消息持久化,保证消息不丢失
 */
public class DurableProducer {

    private static final String QUEUE_NAME = "durable_queue";

    public static void main(String[] args) throws IOException, TimeoutException {

        Channel channel = RabbitmqUtils.getChannel();

        //队列持久化
        //第二个参数 durable置为true
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNext())
        {
            String message = scanner.nextLine();
            //消息持久化 MessageProperties.PERSISTENT_TEXT_PLAIN -> 将消息保存到磁盘
            channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
            System.out.println("发送消息:" + message);
        }
    }
}

查看web界面,Queues选项卡:
在这里插入图片描述

6.不公平分发

RabbitMQ默认使用轮询发送消息,在消费者处理速度不一致的情况下就不合适了,应该让效率高的消费者处

理更多的消息,能者多劳

对之前的两个消费者进行修改,加上Channel.basicQos(1)

public class ConsumerLongAck {
    private static final String QUEUE_NAME = "ack";

    public static void main(String[] args) throws IOException, TimeoutException {

        Channel channel = RabbitmqUtils.getChannel();

        System.out.println("Consumer2等待接受消息,处理时间长...");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            try {
                TimeUnit.SECONDS.sleep(15);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("时间:" + LocalDateTime.now() + "Consumer2接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));

            //消息应答
            //long deliveryTag, boolean multiple
            //  消息的标记          是否批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("Consumer2取消消费消息:" + consumerTag);
        };

        //设置消息不公平分发,能者多劳
        channel.basicQos(1);

        channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
    }
}
public class ConsumerAck {
    private static final String QUEUE_NAME = "ack";

    public static void main(String[] args) throws IOException, TimeoutException {

        Channel channel = RabbitmqUtils.getChannel();

        System.out.println("Consumer1等待接受消息,处理时间短...");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("时间:" + LocalDateTime.now() + "Consumer1接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));

            //消息应答
            //long deliveryTag, boolean multiple
            //  消息的标记          是否批量应答
            channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("Consumer1取消消费消息:" + consumerTag);
        };

        //设置prefetchCount = 1 -> 消息不公平分发,能者多劳
        //prefetchCount > 1 -> 表示预取值,将要收到的消息数(不一定)
        channel.basicQos(1);

        channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
    }
}

测试:

在这里插入图片描述

Consumer1接受8条消息

在这里插入图片描述

Consumer2接受2条消息

在这里插入图片描述

Channel.basicQos()中设置的值大于1时,表示该消费者将要接受的消息数量(不一定)

修改Consumer1:Channel.basicQos(2),设置将要接受2条消息

修改Consumer2:Channel.basicQos(3),设置将要接受3条消息

测试:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

五、交换机

生产者生产的消息不会直接发送到队列,而是发送到交换机,再由交换机根据路由将消息推入相应的队列

交换机的分类:

直接交换机、主题交换机、标题交换机、扇出交换机

之前我们发送消息时在交换机参数填入空字符串,使用的是默认交换机,它的类型是直接交换机路由等于

队列名称

channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));

在web界面中exchange标签页第一个:

在这里插入图片描述
在这里插入图片描述

1.Publish/Subscribe(发布订阅)

使用扇出交换机(fanout),将收到的所有消息广播到绑定的所有队列中

发布订阅结构图:

在这里插入图片描述

/**
 * 生产者
 */
public class Sender {

    private static final String EXCHANGE_NAME = "fanout_test";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitmqUtils.getChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

        Scanner scanner = new Scanner(System.in);

        String flag;

        do {
            System.out.print("请输入发送的消息内容:");
            String message = scanner.nextLine();

            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));

            System.out.println("时间:" + LocalDateTime.now() + "\t成功发送消息:\t" + message);

            System.out.print("是否结束发送消息(y/n):");
            flag = scanner.nextLine();
        } while (!flag.equalsIgnoreCase("y"));

        System.out.println("---------------发送消息结束--------------");
    }
}
/**
 * 消费者
 */
public class Receiver01 {

    private static final String EXCHANGE_NAME = "fanout_test";

    public static void main(String[] args) throws Exception {

        Channel channel = RabbitmqUtils.getChannel();

        //声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

        //生成一个临时队列, 当消费者断开连接时,队列自动删除
        String queueName = channel.queueDeclare().getQueue();

        //绑定交换机和队列 路由为""
        channel.queueBind(queueName, EXCHANGE_NAME, "");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Receiver01接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("Receiver01取消消息:" + consumerTag);
        };

        channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
        System.out.println("Receiver01等待接受消息...");
    }
}
/**
 * 消费者
 */
public class Receiver02 {

    private static final String EXCHANGE_NAME = "fanout_test";

    public static void main(String[] args) throws Exception {

        Channel channel = RabbitmqUtils.getChannel();

        //声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

        //生成一个临时队列, 当消费者断开连接时,队列自动删除
        String queueName = channel.queueDeclare().getQueue();

        //绑定交换机和队列 路由为""
        channel.queueBind(queueName, EXCHANGE_NAME, "");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Receiver02接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("Receiver02取消消息:" + consumerTag);
        };

        channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
        System.out.println("Receiver02等待接受消息...");
    }
}

测试:

交换机广播消息,两个消费者都接受到消息
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

临时队列:

队列名称随机,一旦断开连接队列将自动删除

String queueName = channel.queueDeclare().getQueue();

在这里插入图片描述

在这里插入图片描述

2.Routing(路由模式)

使用直接交换机(direct),会根据路由匹配,将消息推送到相应的队列

路由模式结构图:
在这里插入图片描述

/**
 * 生产者
 */
public class Sender {

    private static final String EXCHANGE_NAME = "direct_test";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitmqUtils.getChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        Scanner scanner = new Scanner(System.in);

        String flag;

        do {
            System.out.print("请输入路由:");
            String routing_key = scanner.nextLine();

            System.out.print("请输入消息:");
            String message = scanner.nextLine();

            //路由对应发送到的队列
            channel.basicPublish(EXCHANGE_NAME, routing_key, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println("Sender发送消息:" + message);

            System.out.print("是否结束发送消息(y/n):");
            flag = scanner.nextLine();
        } while (!flag.equalsIgnoreCase("y"));
    }
}
/**
 * 消费者
 */
public class Receiver01 {

    private static final String EXCHANGE_NAME = "direct_test";

    private static final String QUEUE_NAME = "console";

    private static final String ROUTING_KEY1 = "info";

    private static final String ROUTING_KEY2 = "warning";

    public static void main(String[] args) throws Exception {

        Channel channel = RabbitmqUtils.getChannel();

        //声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        channel.queueDeclare(QUEUE_NAME, false, false,false,null);

        //绑定交换机和队列
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY1);
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY2);

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Receiver01接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("Receiver01取消消息:" + consumerTag);
        };

        channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
        System.out.println("Receiver01等待接受消息...");
    }
}
/**
 * 消费者
 */
public class Receiver02 {

    private static final String EXCHANGE_NAME = "direct_test";

    private static final String QUEUE_NAME = "disk";

    private static final String ROUTING_KEY = "error";

    public static void main(String[] args) throws Exception {

        Channel channel = RabbitmqUtils.getChannel();

        //声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        channel.queueDeclare(QUEUE_NAME, false, false,false,null);

        //绑定交换机和队列 路由为""
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Receiver02接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("Receiver02取消消息:" + consumerTag);
        };

        channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
        System.out.println("Receiver02等待接受消息...");
    }
}

测试:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.Topics(主题模式)

使用主题交换机(topic),路由必须是一个单词列表以点做分割,根据路由进行匹配推送消息到队列

路由中可以使用替换符,最多255个字节:

*(星号):可以代替一个单词

#(井号):可以代替零个或多个单词

主题模式结构图:
在这里插入图片描述

/**
 * 生产者
 */
public class Sender {

    private static final String EXCHANGE_NAME = "topic_test";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitmqUtils.getChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        Scanner scanner = new Scanner(System.in);

        String flag;

        do {
            System.out.print("请输入路由:");
            String routing_key = scanner.nextLine();

            System.out.print("请输入消息:");
            String message = scanner.nextLine();

            //路由对应发送到的队列
            channel.basicPublish(EXCHANGE_NAME, routing_key, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println("Sender发送消息:" + message);

            System.out.print("是否结束发送消息(y/n):");
            flag = scanner.nextLine();
        } while (!flag.equalsIgnoreCase("y"));

        System.out.println("---------------发送消息结束--------------");
    }
}
/**
 * 消费者
 */
public class Receiver01 {

    private static final String EXCHANGE_NAME = "topic_test";

    private static final String QUEUE_NAME = "q1";

    private static final String ROUTING_KEY = "*.orange.*";

    public static void main(String[] args) throws Exception {

        Channel channel = RabbitmqUtils.getChannel();

        //声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        channel.queueDeclare(QUEUE_NAME, false, false,false,null);

        //绑定交换机和队列
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Receiver01接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("Receiver01取消消息:" + consumerTag);
        };

        channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
        System.out.println("Receiver01等待接受消息...");
    }
}
/**
 * 消费者
 */
public class Receiver02 {

    private static final String EXCHANGE_NAME = "topic_test";

    private static final String QUEUE_NAME = "q2";

    private static final String ROUTING_KEY1 = "*.*.rabbit";

    private static final String ROUTING_KEY2 = "lazy.#";

    public static void main(String[] args) throws Exception {

        Channel channel = RabbitmqUtils.getChannel();

        //声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        channel.queueDeclare(QUEUE_NAME, false, false,false,null);

        //绑定交换机和队列 路由为""
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY1);

        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY2);

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Receiver02接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("Receiver02取消消息:" + consumerTag);
        };

        channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
        System.out.println("Receiver02等待接受消息...");
    }
}

测试:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.死信队列

由于某些原因(消费者消费消息发生异常)导致队列中的消息无法被消费,使用死信队列可以保证消息不丢失

在以下情况,消息将变成死信,将放入死信队列中:

  • 消息TTL过期
  • 队列满了,无法再添加消息
  • 消息被拒绝(basic.nack或者basic.reject,且requeue = false)

死信队列结构图:

在这里插入图片描述

/**
 * 生产者
 */
public class Producer {

    private static final String NORMAL_EXCHANGE_NAME = "normal_exchange";

    private static final String NORMAL_QUEUE_ROUTING_KEY = "zhangsan";

    public static void main(String[] args) throws Exception {

        Channel channel = RabbitmqUtils.getChannel();

        //设置 发送消息的过期时间 ttl
        //AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
        //        .expiration("10000")
        //        .build();

        for (int i = 0; i < 10; i++) {
            String message = "info" + i;
            //channel.basicPublish(NORMAL_EXCHANGE_NAME, NORMAL_QUEUE_ROUTING_KEY, properties, message.getBytes(StandardCharsets.UTF_8));
            channel.basicPublish(NORMAL_EXCHANGE_NAME, NORMAL_QUEUE_ROUTING_KEY, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println("发送消息:" + message);
        }
    }
}
/**
 * 消费者
 */
public class Consumer01 {

    private static final String NORMAL_EXCHANGE_NAME = "normal_exchange";

    private static final String DEAD_EXCHANGE_NAME = "dead_exchange";

    private static final String NORMAL_QUEUE_NAME = "normal_queue";

    private static final String NORMAL_QUEUE_ROUTING_KEY = "zhangsan";

    private static final String DEAD_QUEUE_NAME = "dead_queue";

    private static final String DEAD_QUEUE_ROUTING_KEY = "lisi";

    public static void main(String[] args) throws Exception {

        Channel channel = RabbitmqUtils.getChannel();

        //声明普通交换机和死信交换机
        channel.exchangeDeclare(NORMAL_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        //声明普通队列和死信队列
        //消息变成死信后转发到死信交换机
        HashMap<String, Object> argument = new HashMap<>();
        //指定死信交换机和路由
        argument.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
        argument.put("x-dead-letter-routing-key", DEAD_QUEUE_ROUTING_KEY);
        //指定队列的最大容量
        //argument.put("x-max-length", 6);

        channel.queueDeclare(NORMAL_QUEUE_NAME, false, false, false, argument);
        channel.queueDeclare(DEAD_QUEUE_NAME, false, false, false, null);

        //绑定交换机和队列
        channel.queueBind(NORMAL_QUEUE_NAME, NORMAL_EXCHANGE_NAME, NORMAL_QUEUE_ROUTING_KEY);
        channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_QUEUE_ROUTING_KEY);

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String msg = new String(message.getBody(), StandardCharsets.UTF_8);
            //channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
            //System.out.println("Consumer01接受消息:" + msg);
            if (msg.equals("info2")) {
                //拒绝info2消息
                channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
                System.out.println("消息" + msg + "被拒绝!!!");
            } else {
                //确认其他消息
                channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
                System.out.println("Consumer01接受消息:" + msg);
            }
        };

        channel.basicConsume(NORMAL_QUEUE_NAME, false, deliverCallback, System.out::println);
        System.out.println("Consumer01等待接受消息...");
    }
}
/**
 * 消费者
 */
public class Consumer02 {

    private static final String DEAD_QUEUE_NAME = "dead_queue";

    public static void main(String[] args) throws Exception {

        Channel channel = RabbitmqUtils.getChannel();

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Consumer02接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
        };

        channel.basicConsume(DEAD_QUEUE_NAME, true, deliverCallback, System.out::println);
        System.out.println("Consumer02等待接受消息...");
    }
}

测试:

1.消息被拒绝进入死信队列
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.消息过期

在这里插入图片描述

在这里插入图片描述

3.队列满了

剩余的4条消息放入死信队列

在这里插入图片描述

在这里插入图片描述

六、整合springboot

导入依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit-test</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>com.vaadin.external.google</groupId>
            <artifactId>android-json</artifactId>
        </exclusion>
    </exclusions>
</dependency>

如果是云服务器需要修改安全组规则,确认端口5672和15672都开启

编写application.yml配置文件:

spring:
  rabbitmq:
    host: ip地址
    port: 5672
    username: 用户名
    password: 密码
1.TTL(延迟队列)

延时队列就是用来存放需要在指定时间处理元素的队列

延时队列结构图:

在这里插入图片描述

延时队列配置类(配置交换机和队列信息):

/**
 * 延时队列配置文件类
 */
@Configuration
public class TTLQueueConfig {

    //普通交换机名称
    private static final String NORMAL_EXCHANGE_NAME = "X";

    //死信交换机名称
    private static final String DEAD_EXCHANGE_NAME = "Y";

    //普通队列名称
    private static final String NORMAL_QUEUE_A = "QA";
    private static final String NORMAL_QUEUE_A_ROUTING_KEY = "XA";

    private static final String NORMAL_QUEUE_B = "QB";
    private static final String NORMAL_QUEUE_B_ROUTING_KEY = "XB";

    //死信队列名称
    private static final String DEAD_QUEUE_NAME = "QD";

    //路由
    private static final String DEAD_QUEUE_ROUTING_KEY = "YD";

    //声明交换机
    @Bean("exchange_X")
    public DirectExchange exchange_x(){
        return new DirectExchange(NORMAL_EXCHANGE_NAME);
    }

    @Bean("exchange_Y")
    public DirectExchange exchange_y(){
        return new DirectExchange(DEAD_EXCHANGE_NAME);
    }

    @Bean
    public Binding queue_ABindExchange_X(@Qualifier("queue_A") Queue queue_A,
                                        @Qualifier("exchange_X") Exchange exchange_X)
    {
        return BindingBuilder.bind(queue_A).to(exchange_X).with(NORMAL_QUEUE_A_ROUTING_KEY).noargs();
    }

    @Bean
    public Binding queue_BBindExchange_X(@Qualifier("queue_B") Queue queue_B,
                                         @Qualifier("exchange_X") Exchange exchange_X)
    {
        return BindingBuilder.bind(queue_B).to(exchange_X).with(NORMAL_QUEUE_B_ROUTING_KEY).noargs();
    }

    @Bean
    public Binding queue_DBindExchange_Y(@Qualifier("queue_D") Queue queue_D,
                                         @Qualifier("exchange_Y") Exchange exchange_Y)
    {
        return BindingBuilder.bind(queue_D).to(exchange_Y).with(DEAD_QUEUE_ROUTING_KEY).noargs();
    }

    @Bean("queue_A")
    public Queue queue_A(){
        Map<String, Object> argument = new HashMap<>(3);
        //设置死信交换机
        argument.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
        //设置死信路由
        argument.put("x-dead-letter-routing-key", DEAD_QUEUE_ROUTING_KEY);
        //设置TTL 10s
        argument.put("x-message-ttl", 10000);
        return QueueBuilder.durable(NORMAL_QUEUE_A).withArguments(argument).build();
    }

    @Bean("queue_B")
    public Queue queue_B(){
        Map<String, Object> argument = new HashMap<>(3);
        //设置死信交换机
        argument.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
        //设置死信路由
        argument.put("x-dead-letter-routing-key", DEAD_QUEUE_ROUTING_KEY);
        //设置TTL 40s
        argument.put("x-message-ttl", 40000);
        return QueueBuilder.durable(NORMAL_QUEUE_B).withArguments(argument).build();
    }

    @Bean("queue_D")
    public Queue queue_D(){
        return QueueBuilder.durable(DEAD_QUEUE_NAME).build();
    }

}
/**
* 生产者
*/
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMessageController {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @RequestMapping("/message/{message}")
    public void sendMessage(@PathVariable("message") String message){
        log.info("时间{} -发送消息:{}", LocalDateTime.now(), message);

        rabbitTemplate.convertAndSend("X", "XA", message);
        rabbitTemplate.convertAndSend("X", "XB", message);
    }
}
/**
* 消费者
*/
@Component
@Slf4j
public class DeadLetterQueueListener {

    @RabbitListener(queues = {"QD"})
    public void receive(Message message, Channel channel) throws Exception {
        String msg = new String(message.getBody());
        log.info("时间:{} -收到消息:{}", LocalDateTime.now(), msg);
    }
}

测试:

访问浏览器:http://localhost:8081/ttl/message/123

在这里插入图片描述

以上两个队列都完成了延时效果,但是延时的时间都是固定的无法更改,我们需要增加一个延时队列,延时

的时间随机,由自己指定

结构图:

在这里插入图片描述

配置类添加队列QC:

private static final String NORMAL_QUEUE_C = "QC";
private static final String NORMAL_QUEUE_C_ROUTING_KEY = "XC";

@Bean
public Binding queue_CBindExchange_X(@Qualifier("queue_C") Queue queue_C,
                                     @Qualifier("exchange_X") Exchange exchange_X)
{
    return BindingBuilder.bind(queue_C).to(exchange_X).with(NORMAL_QUEUE_C_ROUTING_KEY).noargs();
}

/**
* 此处不写死队列QC的TTL
*/
@Bean("queue_C")
public Queue queue_C(){
    Map<String, Object> argument = new HashMap<>(2);
    //设置死信交换机
    argument.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
    //设置死信路由
    argument.put("x-dead-letter-routing-key", DEAD_QUEUE_ROUTING_KEY);
    return QueueBuilder.durable(NORMAL_QUEUE_C).withArguments(argument).build();
}
/**
* 生产者
*/
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMessageController {

    @Autowired

    @RequestMapping("/message/{message}/{ttl}")
    public void sendExpiredMessage(@PathVariable("message") String message,
                                   @PathVariable("ttl") String ttl){
        log.info("时间:{} -发送消息:{} -ttl:{}ms", LocalDateTime.now(), message, ttl);

        rabbitTemplate.convertAndSend("X", "XC", message, msg -> {
            //由生产者设置消息的过期时间
            msg.getMessageProperties().setExpiration(ttl);
            return msg;
        });
    }
}

测试:

访问浏览器:http://localhost:8081/ttl/message/@/1000 http://localhost:8081/ttl/message/@/2000
在这里插入图片描述

但是,死信队列存在问题,如果第一个消息延时较长,第二个消息延时较短,那么第二个消息需要等待第一

个消息执行完才开始执行,而不会先得到执行

访问浏览器:http://localhost:8081/ttl/message/@/20000 http://localhost:8081/ttl/message/!/1000

在这里插入图片描述

解决方法:

下载RabbitMQ rabbitmq_delayed_message_exchange插件:

https://www.rabbitmq.com/#community-plugins.html=

将插件上传到Linux系统中,然后移动到RabbitMQ安装目录中plugins下面

/usr/lib/rabbitmq/lib/rabbitmq_server-xxx/plugins

应用该插件:

rabbitmq-plugins enable rabbitmq_delayed_message_exchange-3.9.0.ez

重新启动RabbitMq,观察web界面,在exchanges标签页,添加交换机类型中存在x-delayed-message则安

装成功

在这里插入图片描述

基于插件延时队列结构图:

在这里插入图片描述

@Configuration
public class DelayedQueueConfig {

    private static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";

    private static final String DELAYED_QUEUE_NAME = "delayed.queue";

    private static final String DELAYED_QUEUE_ROUTING_KEY = "delayed.routing.key";

    @Bean
    public Binding delayedQueueToExchange(@Qualifier("delayedExchange") CustomExchange delayedExchange,
                                          @Qualifier("delayedQueue") Queue delayedQueue){
        return BindingBuilder.bind(delayedQueue).to(delayedExchange).with(DELAYED_QUEUE_ROUTING_KEY).noargs();
    }

    @Bean
    public CustomExchange delayedExchange(){

        Map<String, Object> argument = new HashMap<>();
        //配置类型为 直接交换机
        argument.put("x-delayed-type", "direct");
        //String name, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments
        //交换机名称       交换机类型    是否持久化            是否自动删除          配置信息
        return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false, argument);
    }

    @Bean
    public Queue delayedQueue(){
        return QueueBuilder.durable(DELAYED_QUEUE_NAME).build();
    }
}
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMessageController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping("/delayMessage/{message}/{delayTime}")
    public void sendDelayMessage(@PathVariable("message") String message,
                                   @PathVariable("delayTime") Integer delayTime){
        log.info("时间:{} -发送消息:{} -延迟时间:{}ms", LocalDateTime.now(), message, delayTime);

        rabbitTemplate.convertAndSend("delayed.exchange", "delayed.routing.key", message, msg -> {
            //设置消息的延迟时长
            msg.getMessageProperties().setDelay(delayTime);
            return msg;
        });
    }
}
@Component
@Slf4j
public class DelayQueueListener {

    @RabbitListener(queues = {"delayed.queue"})
    public void receive(Message message, Channel channel)
    {
        String msg = new String(message.getBody());
        log.info("时间:{} -收到消息:{}", LocalDateTime.now(), msg);
    }
}

测试:

访问浏览器:http://localhost:8081/ttl/delayMessage/@/20000 http://localhost:8081/ttl/delayMessage/!/2000

在这里插入图片描述

延时较短的消息先得到执行

2.Publisher Confirms(发布确认)

首先队列和消息都需要进行持久化

原理:

生产者将信道设置为confirm模式,此时该信道上发布的消息都会被指派一个唯一的id,一旦消息被消费者消

费,交换机会发送一个确认给生产者,确保消息不丢失

开启发布确认:

channel.confirmSelect();

单个发布确认:

同步阻塞,当消息被确认之后才能发送下一条消息,导致消息速度慢

/**
 * 发布确认- 单个确认模式
 */
public class SinglePublishConfirm {

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitmqUtils.getChannel();

        String queueName = "single";

        channel.queueDeclare(queueName, true, false, false, null);

        //开启发布确认
        channel.confirmSelect();

        long start = System.currentTimeMillis();

        for (int i = 0; i < 100; i++) {
            channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, String.valueOf(i).getBytes(StandardCharsets.UTF_8));

            //单个消息发送后就确认
            boolean flag = channel.waitForConfirms();

            if(flag)
            {
                System.out.println("消息发送成功!");
            }
        }

        long end = System.currentTimeMillis();

        System.out.println("单个发布确认,发送100条消息,用时:" + (end - start) + "ms");
        //单个发布确认,发送100条消息,用时:5232ms
    }
}

批量发布确认:

效率比单个发布确认高,但是如果出现问题时,无法确定哪条消息没有被确认

public class BatchPublishConfirm {

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitmqUtils.getChannel();

        String queueName = "batch";

        channel.queueDeclare(queueName, true, false, false, null);

        //开启发布确认
        channel.confirmSelect();

        long start = System.currentTimeMillis();

        for (int i = 0; i < 100; i++) {
            channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, String.valueOf(i).getBytes(StandardCharsets.UTF_8));

            if(i % 10 == 0)
            {
                //每发送10条确认一次
                boolean flag = channel.waitForConfirms();

                if (flag) {
                    System.out.println("10条消息发送成功!");
                }

            }
        }

        long end = System.currentTimeMillis();

        System.out.println("批量发布确认,发送100条消息,用时:" + (end - start) + "ms");
        //批量发布确认,发送100条消息,用时:669ms
    }
}

异步发布确认(推荐使用):

效率最高,可以知道是哪一条消息未确认

public class AsyncPublishConfirm {

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitmqUtils.getChannel();

        String queueName = "async";

        channel.queueDeclare(queueName, true, false, false, null);

        //开启发布确认
        channel.confirmSelect();

        //创建可并发哈希表,存储发送的消息,删除已确认的消息
        ConcurrentSkipListMap<Long, String> map = new ConcurrentSkipListMap<>();

        long start = System.currentTimeMillis();

        //确认监听器
        //ConfirmCallback ackCallback, ConfirmCallback nackCallback
        //     消息确认成功回调                 消息确认失败回调
        ConfirmCallback ackCallback = (deliveryTag, multiple) -> {
            System.out.println("确认的消息:" + deliveryTag);
            //删除哈希表中已确认的消息
            //批量删除
            if (multiple) {
                ConcurrentNavigableMap<Long, String> confirmed = map.headMap(deliveryTag);
                confirmed.clear();
            } else {
                //单个删除
                map.remove(deliveryTag);
            }

        };

        ConfirmCallback nackCallback = (deliveryTag, multiple) -> {
            String message = map.get(deliveryTag);
            System.out.println("未确认的消息标记:" + deliveryTag + ", 内容:" + message);
        };

        //添加监听器 监听消息是否确认成功
        channel.addConfirmListener(ackCallback, nackCallback);

        for (int i = 0; i < 100; i++) {
            String message = String.valueOf(i);
            channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));

            //存储发送的消息
            map.put(channel.getNextPublishSeqNo(), message);
        }

        long end = System.currentTimeMillis();

        System.out.println("异步发布确认,发送100条消息,用时:" + (end - start) + "ms");
        //异步发布确认,发送100条消息,用时:27ms
    }
}

ConfirmCallback接口确认交换机接受到生产者发送的消息调用回调,ReturnsCallback接口在队列接受不

到消息时,将消息回退给生产者

在这里插入图片描述

修改application.yml

publisher-confirm-type: CORRELATED  # 发布消息成功到交换机后会触发回调
publisher-returns: true # 表示交换机消息发送失败会将消息回退给生产者
public static enum ConfirmType {
    SIMPLE, // 会触发回调方法,并且在发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待broker节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDie方法如果返回false则会关闭channel,则接下来无法发送消息到broker
    CORRELATED, // 发布消息成功到交换机会触发回调方法 
    NONE; // 默认值,禁止发布确认模式

    private ConfirmType() {
    }
}
@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        //将重写后的确认回调和回退消息回调注入rabbitTemplate
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnsCallback(this);
    }

    /**
     * 交换机对生产者的消息产生应答
     * @param correlationData 回调消息的id和信息,需要生产者发送
     * @param b               交换机是否收到消息  true
     * @param s               失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        String id = correlationData != null ? correlationData.getId() : "";
        if (b) {
            log.info("交换机收到id为{}的消息", id);
        } else {
            log.info("交换机未收到id为{}的消息,原因:{}", id, s);
        }
    }

    /**
     *
     * @param returnedMessage 回退信息
     * Message message  //消息内容
     * int replyCode //相应码
     * String replyText //相应结果
     * String exchange // 交换机名称
     * String routingKey // 路由
     */
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        log.info("消息{}被交换机{}退回,路由:{},响应码:{},原因是:{}",
                                returnedMessage.getMessage(),
                                returnedMessage.getExchange(),
                                returnedMessage.getRoutingKey(),
                                returnedMessage.getReplyCode(),
                                returnedMessage.getReplyText()
        );
    }
}
/**
 * 发布确认 配置类
 */
@Configuration
public class ConfirmConfig {

    private static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";

    private static final String CONFIRM_QUEUE_NAME = "confirm.queue";

    private static final String CONFIRM_QUEUE_ROUTING_KEY = "key1";

    @Bean
    public FanoutExchange backupExchange(){
        return ExchangeBuilder.fanoutExchange(BACKUP_EXCHANGE_NAME).build();
    }

    @Bean
    public Queue confirmQueue(){
        return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
    }
    
    @Bean
    public Binding confirmQueueToConfirmExchange(@Qualifier("confirmExchange") Exchange confirmExchange,
                                                @Qualifier("confirmQueue") Queue confirmQueue){
        return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_QUEUE_ROUTING_KEY).noargs();
    }
}
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMessageController {

    @Autowired
    private RabbitTemplate rabbitTemplate;
	
    @RequestMapping("/confirm/sendMessage/{message}")
    public void sendConfirmMessage(@PathVariable("message") String message){
        log.info("时间:{} -发送消息:{}", LocalDateTime.now(), message);

        //rabbitTemplate.convertAndSend("confirm.exchange", "key1", message);
        //测试 交换机应答和无法投递消息进行回退
        rabbitTemplate.convertAndSend("confirm.exchange", "key2", message, new CorrelationData("1"));
    }
}
@Component
@Slf4j
public class Consumer {

    @RabbitListener(queues = {"confirm.queue"})
    public void receive(Message message, Channel channel){
        String msg = new String(message.getBody());
        log.info("时间:{} -收到消息:{}", LocalDateTime.now(), msg);
    }
}

测试:

在这里插入图片描述

3.备份交换机

无法投递的消息将发送给备份交换机

结构图:

在这里插入图片描述

@Configuration
public class ConfirmConfig {

    private static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";

    private static final String CONFIRM_QUEUE_NAME = "confirm.queue";

    private static final String CONFIRM_QUEUE_ROUTING_KEY = "key1";

    //备份交换机
    private static final String BACKUP_EXCHANGE_NAME = "backup.exchange";

    private static final String BACKUP_QUEUE_NAME = "backup.queue";

    private static final String WARNING_QUEUE_NAME = "warning.queue";

    @Bean
    public DirectExchange confirmExchange(){
        //指定备份交换机 alternate-exchange
        return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME).withArgument("alternate-exchange", BACKUP_EXCHANGE_NAME).build();
    }

    @Bean
    public FanoutExchange backupExchange(){
        return ExchangeBuilder.fanoutExchange(BACKUP_EXCHANGE_NAME).build();
    }

    @Bean
    public Queue confirmQueue(){
        return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
    }

    @Bean
    public Queue backupQueue(){
        return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();
    }

    @Bean
    public Queue warningQueue(){
        return QueueBuilder.durable(WARNING_QUEUE_NAME).build();
    }

    @Bean
    public Binding confirmQueueToConfirmExchange(@Qualifier("confirmExchange") Exchange confirmExchange,
                                                @Qualifier("confirmQueue") Queue confirmQueue){
        return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_QUEUE_ROUTING_KEY).noargs();
    }

    @Bean
    public Binding backupQueueToBackupExchange(@Qualifier("backupExchange") Exchange backupExchange,
                                                @Qualifier("backupQueue") Queue backupQueue){
        return BindingBuilder.bind(backupQueue).to(backupExchange).with("").noargs();
    }

    @Bean
    public Binding warningQueueToBackupExchange(@Qualifier("backupExchange") Exchange backupExchange,
                                                @Qualifier("warningQueue") Queue warningQueue){
        return BindingBuilder.bind(warningQueue).to(backupExchange).with("").noargs();
    }
}
@Slf4j
@Component
public class WarningListener {

    @RabbitListener(queues = {"warning.queue"})
    public void receive(Message message, Channel channel){
        String msg = new String(message.getBody());
        log.info("报警发现非法路由:{},发送消息:{}",
                message.getMessageProperties().getReceivedRoutingKey(),
                msg);
    }
}

测试:

访问浏览器:http://localhost:8081/ttl/confirm/sendMessage/123

在这里插入图片描述

注意:

同时配置publisher-returns回退消息和备份交换机时,消息不会回退给消费者,备份交换机的优先级更高

4.惰性队列

一般消息时保存在内存中,惰性队列中的消息将会保存在磁盘中,消息被消费的效率更低,但是占用内存小

声明惰性队列,在声明队列时指定参数:

HashMap<String, Object> argument = new HashMap<>();
argument.put("x-queue-mode", "lazy");
channel.queueDeclare(队列名称, false, false, false, argument);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值