RabbtiMQ

目录

基本概念

四大核心概念

工作原理

hello world

生产者

消费者 

Work Queues

轮询分发消息

消息应答

自动应答

手动应答

持久化

不公平分发

预取值

发布确认

 三种发布确认

处理异步未确认消息

交换机

临时队列

Direct

Fanout

Topics

死信队列

队列延迟

消息设置TTL

队列设置TTL

整合SpringBoot

队列TTL

延时队列插件

发布确认高级

回退消息


基本概念

消息指的是两个应用间传递的数据。数据的类型有很多种形式,可能只包含文本字符串,也可能包含嵌入对象。

“消息队列(Message Queue)”是在消息的传输过程中保存消息的容器。在消息队列中,通常有生产者和消费者两个角色。生产者只负责发送数据到消息队列,谁从消息队列中取出数据处理,他不管。消费者只负责从消息队列中取出数据处理,他不管这是谁发送的数据。

RabbitMQ 是一个消息中间件:它接受并转发消息。Message Queue,本质是一个消息队列,是一种跨进程的通信机制,用于上下流传递消息

RabbitMQ 三大作用

  • 解耦。如图所示。假设有系统B、C、D都需要系统A的数据,于是系统A调用三个方法发送数据到B、C、D。这时,系统D不需要了,那就需要在系统A把相关的代码删掉。假设这时有个新的系统E需要数据,这时系统A又要增加调用系统E的代码。为了降低这种强耦合,就可以使用MQ,系统A只需要把数据发送到MQ,其他系统如果需要数据,则从MQ中获取即可

  • 异步。如图所示。一个客户端请求发送进来,系统A会调用系统B、C、D三个系统,同步请求的话,响应时间就是系统A、B、C、D的总和,也就是800ms。如果使用MQ,系统A发送数据到MQ,然后就可以返回响应给客户端,不需要再等待系统B、C、D的响应,可以大大地提高性能。对于一些非必要的业务,比如发送短信,发送邮件等等,就可以采用MQ。

  • 削峰。如图所示。这其实是MQ一个很重要的应用。假设系统A在某一段时间请求数暴增,有5000个请求发送过来,系统A这时就会发送5000条SQL进入MySQL进行执行,MySQL对于如此庞大的请求当然处理不过来,MySQL就会崩溃,导致系统瘫痪。如果使用MQ,系统A不再是直接发送SQL到数据库,而是把数据发送到MQ,MQ短时间积压数据是可以接受的,然后由消费者每次拉取2000条进行处理,防止在请求峰值时期大量的请求直接发送到MySQL导致系统崩溃

特点

RabbitMQ是一款使用Erlang语言开发的,实现AMQP(高级消息队列协议)的开源消息中间件。首先要知道一些RabbitMQ的特点,官网可查:

  • 可靠性。支持持久化,传输确认,发布确认等保证了MQ的可靠性。
  • 灵活的分发消息策略。这应该是RabbitMQ的一大特点。在消息进入MQ前由Exchange(交换机)进行路由消息。分发消息策略有:简单模式、工作队列模式、发布订阅模式、路由模式、通配符模式。
  • 支持集群。多台RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
  • 多种协议。RabbitMQ支持多种消息队列协议,比如 STOMP、MQTT 等等。
  • 支持多种语言客户端。RabbitMQ几乎支持所有常用编程语言,包括 Java、.NET、Ruby 等等。
  • 可视化管理界面。RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker。
  • 插件机制。RabbitMQ提供了许多插件,可以通过插件进行扩展,也可以编写自己的插件。

关键的类和接口

  • Channel:代表一个AMQP Channel,提供了大部分操作
  • Connection:代表AMQP Connection
  • ConnectionFactory:构建Connection实例
  • Consumer:消息的消费者
  • DefaultConsumer:Consumer的基类
  • BasicProperties:消息配置
  • BasicProperties.Builder:构建 BasicProperties

操作协议可通过 Channel接口获得,Connection是用来创建 Channels,注册和关闭 Connection 不再需要,Connection的实例化是通过 ConnectionFactory。Connection 和 Channel是核心的API类,代表一个AMQP的 Connection 和 Channel

四大核心概念

生产者:产生数据发送消息的程序是生产者

交换机:交换机是 RabbitMQ 非常重要的一个部件,一方面它接收来自生产者的消息,另一方面它将消息推送到队列中。交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是推送到多个队列,亦或者是把消息丢弃,这个得有交换机类型决定

队列:队列是 RabbitMQ 内部使用的一种数据结构,尽管消息流经 RabbitMQ 和应用程序,但它们只能存储在队列中。队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲区。

消费者:消费与接收具有相似的含义。消费者大多时候是一个等待接收消息的程序。

工作原理

Broker:接收和分发消息的应用,RabbitMQ Server 就是 Message Broker

Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个 vhost,每个用户在自己的 vhost 创建 exchange/queue 等

Connection:publisher/consumer 和 broker 之间的 TCP 连接Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCPConnection 的开销将是巨大的,效率也较低。 Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个 thread 创建单独的 channel 进行通讯,AMQP method 包含了 channel id 帮助客户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的Connection 极大减少了操作系统建立 TCP connection 的开销

Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到 queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout(multicast)Queue:消息最终被送到这里等待 consumer 取走

Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key,Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据

hello world

导入依赖

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
</dependency

生产者

public class Producer {

    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建一个连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setUsername("guest");
        factory.setPassword("guest");
        // 创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();

        /**
         * 生成一个对垒
         * 1.队列名称
         * 2.队列消息是否持久化,默认存储在内存中
         * 3.队列是否只供一个消费者消费,即是否共享
         * 4.是否自动删除,最后一个消费者断开连接后,该队列是否自动删除
         * 5.其它参数
         */
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        String msg = "rabbit mq test msg 13:48";

        /**
         * 发送一个消费
         * 1.发送到那个交换机
         * 2.路由的key是什么,即本次队列的名称
         * 3.其它参数信息
         * 4.发送消息的消息体
         */
        channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());

        System.out.println("send success");
    }
}

运行程序可以从rabbitmq后台页面上看到

消费者 

public class Consumer {

    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setUsername("guest");
        factory.setPassword("guest");
        // 创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();

        // 接收消息
        DeliverCallback deliverCallback = (consumerTag, msg) -> {
            System.out.println("接收到的信息:" + new String(msg.getBody()));
        };

        // 取消消息回调
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息消费被中断");
        };


        /**
         * 消费者接收消息
         * 1.消费那个队列
         * 2. 消费之后是否自动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
         */
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
    }
}

Work Queues

工作队列又称任务队列,避免立即执行资源密集型任务,即一个生产者加多个消费者,一个消息只能被消费一次,不能处理多次

轮询分发消息

生产者

public class Producer {

    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMqUtil.getChannel();
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        String msg = "rabbit mq msg 13:48 -> ";
        for (int i = 0; i < 10; i++) {
            channel.basicPublish("", QUEUE_NAME, null, (msg + i).getBytes());
        }
        System.out.println("消息发送完毕");
    }
}

消费者

public class WorkFirst {

    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMqUtil.getChannel();
        DeliverCallback deliverCallback = (consumerTag, msg) -> {
            System.out.println("接收到的信息:" + new String(msg.getBody()));

        };

        // 取消消息回调
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println(consumerTag + "消息被取消消费接口回调逻辑");
        };
        System.out.println("C1 waiting");
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
    }
}
public class WorkSecond {

    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMqUtil.getChannel();
        DeliverCallback deliverCallback = (consumerTag, msg) -> {
            System.out.println("接收到的信息:" + new String(msg.getBody()));
        };

        // 取消消息回调
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println(consumerTag + "消息被取消消费接口回调逻辑");
        };
        System.out.println("C2 waiting");
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
    }
}

结果

消息应答

为了保证消息在发送过程中不丢失,rabbitmq 引入消息应答机制,

消息应答:消费者在接收到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了。

自动应答

        消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡,因为这种模式如果消息在接收到之前,消费者那边出现连接或者 channel 关闭,那么消息就丢失了,当然另一方面这种模式消费者那边可以传递过载的消息,没有对传递的消息数量进行限制,当然这样有可能使得消费者这边由于接收太多还来不及处理的消息,导致这些消息的积压,最终使得内存耗尽,最终这些消费者线程被操作系统杀死,所以这种模式仅适用在消费者可以高效并以某种速率能够处理这些消息的情况下使用。

消息应答的方法

  • Channel.basicAck(用于肯定确认)
    • RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了
  • Channel.basicNack(用于否定确认)
  • Channel.basicReject(用于否定确认)
    • 与 Channel.basicNack 相比少一个参数,不处理该消息了直接拒绝,可以将其丢弃

Multiple 的解释

手动应答的好处是可以批量应答并且减少网络拥堵

multiple 的 true 和 false 代表不同意思

  • true 代表批量应答 channel 上未应答的消息 比如说 channel 上有传送 tag 的消息 5,6,7,8 当前 tag 是8 那么此时 5-8 的这些还未应答的消息都会被确认收到消息应答
  • false 同上面相比 只会应答 tag=8 的消息 5,6,7 这三个消息依然不会被确认收到消息应答

消息自动重新入队

        如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或 TCP 连接丢失),导致消息未发送 ACK 确认,RabbitMQ 将了解到消息未完全处理,并将对其重新排队。如果此时其他消费者可以处理,它将很快将其重新分发给另一个消费者。这样,即使某个消费者偶尔死亡,也可以确保不会丢失任何消息

手动应答

默认消息采用的是自动应答,所以我们要想实现消息消费过程中不丢失,需要把自动应答改为手动应答

生产者

/**
 * 消息在自动应答时不丢失,放回队列重新消费
 * @Author: chen yang
 * @Date: 2022/7/31 14:56
 */
public class Task {

    public static final String TASK_QUEUE_ACK = "ack_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMqUtil.getChannel();
        channel.queueDeclare(TASK_QUEUE_ACK, false, false, false, null);

        String msg = "TASK_QUEUE_ACK -> ";
        for (int i = 0; i < 2; i++) {
            channel.basicPublish("", TASK_QUEUE_ACK, null, (msg + i).getBytes());
        }
        System.out.println("消息发送完毕");
    }
}
public class WorkSleepFast {

    public static final String QUEUE_NAME = "ack_queue";

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

        Channel channel = RabbitMqUtil.getChannel();
        System.out.println("C1 waiting");
        DeliverCallback deliverCallback = (consumerTag, msg) -> {
            ThreadUtil.sleep(2);
            System.out.println("接收到的信息:" + new String(msg.getBody()));

            /*
             * 手动应答
             * 1.应答的tag
             * 2.是否批量应答
             */
            channel.basicAck(msg.getEnvelope().getDeliveryTag(), false);
        };

        // 取消消息回调
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println(consumerTag + "消息被取消消费接口回调逻辑");
        };

        channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
    }
}
public class WorkSleepLow {

    public static final String QUEUE_NAME = "ack_queue";

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

        Channel channel = RabbitMqUtil.getChannel();
        System.out.println("C2 waiting");
        DeliverCallback deliverCallback = (consumerTag, msg) -> {
            ThreadUtil.sleep(30);
            System.out.println("接收到的信息:" + new String(msg.getBody()));

            /*
             * 手动应答
             * 1.应答的tag
             * 2.是否批量应答
             */
            channel.basicAck(msg.getEnvelope().getDeliveryTag(), false);
        };

        // 取消消息回调
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println(consumerTag + "消息被取消消费接口回调逻辑");
        };
        channel.basicConsume(QUEUE_NAME, Boolean.FALSE, deliverCallback, cancelCallback);
    }
}

当运行程序时,C1会在运行两秒之后输出结果,而C2则需要30S的时间,当C2在运行的过程中宕机时,则会由 C1来重新执行该任务

持久化

默认情况下 RabbitMQ 退出或由于某种原因崩溃时,它忽视队列和消息,除非告知它不要这样做。确保消息不会丢失需要做两件事:我们需要将队列和消息都标记为持久化。

持久化分类

  • 队列实现持久化
  • 消息实现持久化

生产者

public class Task {

    public static final String TASK_QUEUE_ACK = "ack_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMqUtil.getChannel();

        // 队列持久化
        boolean durable = true;
        channel.queueDeclare(TASK_QUEUE_ACK, durable, false, false, null);

        String msg = "TASK_QUEUE_ACK -> ";
        for (int i = 0; i < 2; i++) {
            // 设置生产者发送消息持久化,即将消息保存到磁盘中
            channel.basicPublish("", TASK_QUEUE_ACK, MessageProperties.PERSISTENT_TEXT_PLAIN, (msg + i).getBytes());
        }
        System.out.println("消息发送完毕");
    }
}

         将消息标记为持久化并不能完全保证不会丢失消息。尽管它告诉 RabbitMQ 将消息保存到磁盘,但是这里依然存在当消息刚准备存储在磁盘的时候 但是还没有存储完,消息还在缓存的一个间隔点。此时并没有真正写入磁盘。持久性保证并不强,但是对于我们的简单任务队列而言,这已经绰绰有余了。

不公平分发

我们可以设置参数 `channel.basicQos(1)`

public class WorkSleepFast {

    public static final String QUEUE_NAME = "ack_queue";

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

        Channel channel = RabbitMqUtil.getChannel();
        System.out.println("C1 waiting");
        DeliverCallback deliverCallback = (consumerTag, msg) -> {
            ThreadUtil.sleep(2);
            System.out.println("接收到的信息:" + new String(msg.getBody()));
            channel.basicAck(msg.getEnvelope().getDeliveryTag(), false);
        };
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println(consumerTag + "消息被取消消费接口回调逻辑");
        };
        // 设置不公平分发
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
    }
}
public class WorkSleepLow {

    public static final String QUEUE_NAME = "ack_queue";

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

        Channel channel = RabbitMqUtil.getChannel();
        System.out.println("C2 waiting");
        DeliverCallback deliverCallback = (consumerTag, msg) -> {
            ThreadUtil.sleep(30);
            System.out.println("接收到的信息:" + new String(msg.getBody()));
            channel.basicAck(msg.getEnvelope().getDeliveryTag(), false);
        };

        // 取消消息回调
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println(consumerTag + "消息被取消消费接口回调逻辑");
        };
        // 设置不公平分发
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        channel.basicConsume(QUEUE_NAME, Boolean.FALSE, deliverCallback, cancelCallback);
    }
}

预取值

        本身消息的发送就是异步发送的,所以在任何时候,channel 上肯定不止只有一个消息另外来自消费者的手动确认本质上也是异步的。因此这里就存在一个未确认的消息缓冲区,因此希望开发人员能限制此缓冲区的大小,以避免缓冲区里面无限制的未确认消息问题。这个时候就可以通过使用 basic.qos 方法设置“预取计数”值来完成的。该值定义通道上允许的未确认消息的最大数量。一旦数量达到配置的数量,RabbitMQ 将停止在通道上传递更多消息,除非至少有一个未处理的消息被确认,

        通常,增加预取将提高向消费者传递消息的速度。虽然自动应答传输消息速率是最佳的,但是,在这种情况下已传递但尚未处理的消息的数量也会增加,从而增加了消费者的 RAM 消耗(随机存取存储器)应该小心使用具有无限预处理的自动确认模式或手动确认模式,消费者消费了大量的消息如果没有确认的话,会导致消费者连接节点的内存消耗变大,所以找到合适的预取值是一个反复试验的过程。

public class WorkSleepLow {

    public static final String QUEUE_NAME = "ack_queue";

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

        Channel channel = RabbitMqUtil.getChannel();
        System.out.println("C2 waiting");
        DeliverCallback deliverCallback = (consumerTag, msg) -> {
            ThreadUtil.sleep(15);
            System.out.println("接收到的信息:" + new String(msg.getBody()));
            channel.basicAck(msg.getEnvelope().getDeliveryTag(), false);
        };

        // 取消消息回调
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println(consumerTag + "消息被取消消费接口回调逻辑");
        };
        // 预取值是5
        int prefetchCount = 5;
        channel.basicQos(prefetchCount);
        channel.basicConsume(QUEUE_NAME, Boolean.FALSE, deliverCallback, cancelCallback);
    }
}
public class WorkSleepFast {

    public static final String QUEUE_NAME = "ack_queue";

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

        Channel channel = RabbitMqUtil.getChannel();
        System.out.println("C1 waiting");
        DeliverCallback deliverCallback = (consumerTag, msg) -> {
            ThreadUtil.sleep(2);
            System.out.println("接收到的信息:" + new String(msg.getBody()));
            channel.basicAck(msg.getEnvelope().getDeliveryTag(), false);
        };

        // 取消消息回调
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println(consumerTag + "消息被取消消费接口回调逻辑");
        };
        // 预取值是4
        int prefetchCount = 4;
        channel.basicQos(prefetchCount);
        channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
    }
}

发布确认

生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID(从 1 开始),一旦消息被投递到所有匹配的队列之后,broker 就会发送一个确认给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker 回传给生产者的确认消息中 delivery-tag 域包含了确认消息的序列号,此外 broker 也可以设置basic.ack 的multiple 域,表示到这个序列号之前的所有消息都已经得到了处理

要求队列持久化

要求消息持久化

消息保存到磁盘上才算真正的持久化,所以需要发布确认

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

 三种发布确认

  • 单步确认发布
  • 批量确认发布
  • 异步确认发布
public class ConfirmMessage {

    public static final int MESSAGE_COUNT = 1000;

    public static void main(String[] args) throws Exception {
        // 单个确认     314 ms
        ConfirmMessage.publishMessageIndividually();

        // 批量确认     173 ms
        ConfirmMessage.publishMessageBatch();

        // 异步发布     24 ms
        ConfirmMessage.publishMessageAsync();
    }

    /**
     * 单独确认发布
     * @throws Exception
     */
    public static void  publishMessageIndividually() throws Exception {
        Channel channel = RabbitMqUtil.getChannel();
        channel.confirmSelect();
        
        String queryName = UUID.randomUUID().toString();
        channel.queueDeclare(queryName, true, false, false, null);
        
        long begin = System.currentTimeMillis();
        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String msg = i + "";
            channel.basicPublish("", queryName, null, msg.getBytes());
            // 单个消息发布确认
            boolean flag = channel.waitForConfirms();
            if (flag){
                System.out.println("消息发送成功 " + i);
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("发布 " + MESSAGE_COUNT + "个单独确认消息,耗时:" +(end - begin) + " ms");
    }

    /**
     * 批量确认发布
     * @throws Exception
     */
    public static void publishMessageBatch() throws Exception {
        Channel channel = RabbitMqUtil.getChannel();
        channel.confirmSelect();

        String queryName = UUID.randomUUID().toString();
        channel.queueDeclare(queryName, true, false, false, null);

        long begin = System.currentTimeMillis();

        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String msg = i + "";
            channel.basicPublish("", queryName, null, msg.getBytes());

            if (i % 100 == 0){
                channel.waitForConfirms();
            }
        }
        channel.waitForConfirms();
        long end = System.currentTimeMillis();
        System.out.println("发布 " + MESSAGE_COUNT + "个批量确认消息,耗时:" +(end - begin) + " ms");
    }

    /**
     * 异步确认发布
     */
    public static void publishMessageAsync() throws Exception {
        Channel channel = RabbitMqUtil.getChannel();
        channel.confirmSelect();

        String queryName = UUID.randomUUID().toString();
        channel.queueDeclare(queryName, true, false, false, null);

        long begin = System.currentTimeMillis();

        // 消息确认回调函数
        ConfirmCallback ackCallback = (deliverTag, multiple) -> {
            System.out.println("确认的消息:" + deliverTag);
        };

        // 消息失败回调函数
        // 1.消息的标记
        // 2.是否为批量确认
        ConfirmCallback nakCallback = (deliverTag, multiple) -> {
            System.out.println("未确认的消息:" + deliverTag);
        };

        // 准备一个监听器,监听那些消息成功,那些消息失败
        channel.addConfirmListener(ackCallback, nakCallback);

        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String msg = i + "";
            channel.basicPublish("", queryName, null, msg.getBytes());
        }
        long end = System.currentTimeMillis();
        System.out.println("发布 " + MESSAGE_COUNT + "个异步确认消息,耗时:" +(end - begin) + " ms");
    }
}

处理异步未确认消息

        最好的解决的解决方案就是把未确认的消息放到一个基于内存的能被发布线程访问的队列,比如说用 ConcurrentLinkedQueue 这个队列在 confirm callbacks 与发布线程之间进行消息的传递。

public static void publishMessageAsync() throws Exception {
    Channel channel = RabbitMqUtil.getChannel();
    channel.confirmSelect();

    ConcurrentSkipListMap<Long, String> outstanding = new ConcurrentSkipListMap<>();

    String queryName = UUID.randomUUID().toString();
    channel.queueDeclare(queryName, true, false, false, null);

    long begin = System.currentTimeMillis();

    // 消息确认回调函数
    ConfirmCallback ackCallback = (deliverTag, multiple) -> {
        System.out.println("确认的消息:" + deliverTag);
        ConcurrentNavigableMap<Long, String> confirmed = outstanding.headMap(deliverTag);
        if (multiple){
            confirmed.clear();
        }else {
            confirmed.remove(deliverTag);
        }
    };
    ConfirmCallback nakCallback = (deliverTag, multiple) -> {
        System.out.println("未确认的消息:" + deliverTag);
    };
    // 准备一个监听器,监听那些消息成功,那些消息失败
    channel.addConfirmListener(ackCallback, nakCallback);

    for (int i = 0; i < MESSAGE_COUNT; i++) {
        String msg = i + "";
        // 记录所有要发送的消息
        outstanding.put(channel.getNextPublishSeqNo(), msg);
        channel.basicPublish("", queryName, null, msg.getBytes());
    }
    long end = System.currentTimeMillis();
    System.out.println("发布 " + MESSAGE_COUNT + "个异步确认消息,耗时:" +(end - begin) + " ms");
}

交换机

        生产者生产的消息不会直接发送到队列,实际上,通常生产者都不知道这些消息传递到哪个队列中,生产者只能将消息发送到交换机中。交换机有两个作用,一是接收来自生产者的消息,另一方面是将它们推入队列中,消息能路由发送到队列是由 routingKey 绑定 key 指定的,如果它存在的话,否则使用无名或默认交换机。

交换机的类型

  • direct
  • topic
  • headers
  • fanout

临时队列

public static void main(String[] args) throws IOException {
    Channel channel = RabbitMqUtil.getChannel();
    String queueName = channel.queueDeclare().getQueue();
    channel.basicPublish("", queueName, null, "message".getBytes());
}

Direct

        消息只到它绑定的 routingKey 队列中去,如果 exchange 的类型是 direct,但是它绑定了多个队列的 key 都相同,那么在这种情况下就和 fanout 类似了

public class DirectProducer {

    public static final String EXCHANGE_NAME = "directExchangeName";

    public static void main(String[] args) throws IOException {
        Channel channel = RabbitMqUtil.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        HashMap<String, String> map = new HashMap<>();
        map.put("info", "这是普通的 info 信息");
        map.put("debugger", "这是调试 debugger 信息");
        map.put("error", "这是 错误 error 信息");

        for (Map.Entry<String, String> bindingKeyEntry : map.entrySet()){
            String bindingKey = bindingKeyEntry.getKey();
            String message = bindingKeyEntry.getValue();
            // exchangeName, routingKey, properties, msg
            channel.basicPublish(EXCHANGE_NAME, bindingKey, null, message.getBytes(StandardCharsets.UTF_8));
        }
    }
}

class DirectReceiverOne{
    public static void main(String[] args) throws IOException {
        Channel channel = RabbitMqUtil.getChannel();
        // exchangeName    exchangeType    durable
        channel.exchangeDeclare(DirectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT, false);
        String queueName = "mary";
        // queueName, durable, share, auto-delete, parms
        channel.queueDeclare(queueName, false, false, false, null);
        // queueName, exchangeName, routingKey
        channel.queueBind(queueName, DirectProducer.EXCHANGE_NAME, "info");
        System.out.println("卢本伟一号等待接收消息:");
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            System.out.println("卢本伟一号收到消息:" + new String(delivery.getBody(), StandardCharsets.UTF_8));
        };
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});
    }
}

class DirectReceiverTwo{
    public static void main(String[] args) throws IOException {
        Channel channel = RabbitMqUtil.getChannel();
        channel.exchangeDeclare(DirectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT, false);
        String queueName = "disk";
        channel.queueDeclare(queueName, false, false, false, null);
        channel.queueBind(queueName, DirectProducer.EXCHANGE_NAME, "error");
        System.out.println("卢本伟二号等待接收消息:");
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            System.out.println("卢本伟二号收到消息:" + new String(delivery.getBody(), StandardCharsets.UTF_8));
        };
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});
    }
}

Fanout

它将会将接收的消息广播到它所有的队列中

/**
 * 广播消息 发送者
 * @Author: chen yang
 * @Date: 2022/8/6 11:30
 */
public class FanoutProducer {
    public static final String EXCHANGE_NAME = "fanoutExchange";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtil.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
        for (int i = 0; i < 10; i++) {
            String msg = "message -> " + i;
            channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes(StandardCharsets.UTF_8));
        }
        System.out.println("生产者消息发送完成!");
    }
}

class FanoutReceiverOne{
    public static void main(String[] args) throws IOException {
        Channel channel = RabbitMqUtil.getChannel();
        channel.exchangeDeclare(FanoutProducer.EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

        String queueName = channel.queueDeclare().getQueue();
        channel.queueBind(queueName, FanoutProducer.EXCHANGE_NAME, "");
        System.out.println("1等待接收消息ing");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            System.out.println("1收到消息:" + new String(delivery.getBody(), StandardCharsets.UTF_8));
        };
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});
    }
}

class FanoutReceiverTwo{
    public static void main(String[] args) throws IOException {
        Channel channel = RabbitMqUtil.getChannel();
        channel.exchangeDeclare(FanoutProducer.EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

        String queueName = channel.queueDeclare().getQueue();
        channel.queueBind(queueName, FanoutProducer.EXCHANGE_NAME, "");
        System.out.println("2等待接收消息ing");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            System.out.println("2收到消息:" + new String(delivery.getBody(), StandardCharsets.UTF_8));
        };
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});
    }
}

Topics

按照通配符匹配 routingKey,routingKey 必须是一个单词,以点号分隔开,单词列表最多不能超过 255 个字节 `rabbit.mq.nyse` 这是三个单词,

  • *:可以匹配一个单词
  • #:可以匹配零个或者多个单词
/**
 * 主题交换机
 * @Author: chen yang
 * @Date: 2022/8/6 17:31
 */
public class TopicProducer {

    public static final String EXCHANGE_NAME = "topicExchangeName";

    public static void main(String[] args) throws IOException {
        Channel channel = RabbitMqUtil.getChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        HashMap<String, String> bindingKeyMap = new HashMap<>();
        bindingKeyMap.put("quick.orange.rabbit","被队列 Q1Q2 接收到"); 
        bindingKeyMap.put("lazy.orange.elephant","被队列 Q1Q2 接收到");
        bindingKeyMap.put("quick.orange.fox","被队列 Q1 接收到");
        bindingKeyMap.put("lazy.brown.fox","被队列 Q2 接收到");
        bindingKeyMap.put("lazy.pink.rabbit","虽然满足两个绑定但只被队列 Q2 接收一次");
        bindingKeyMap.put("quick.brown.fox","不匹配任何绑定不会被任何队列接收到会被丢弃");
        bindingKeyMap.put("quick.orange.male.rabbit","是四个单词不匹配任何绑定会被丢弃");
        bindingKeyMap.put("lazy.orange.male.rabbit","是四个单词但匹配 Q2");

        for (Map.Entry<String, String> bindingMap : bindingKeyMap.entrySet()){
            String bindingKey = bindingMap.getKey();
            String bindingValue = bindingMap.getValue();
            channel.basicPublish(EXCHANGE_NAME, bindingKey, null, bindingValue.getBytes(StandardCharsets.UTF_8));
        }
        System.out.println("message send success");
    }
}

class TopicReceiverOne{
    public static void main(String[] args) throws IOException {
        Channel channel = RabbitMqUtil.getChannel();
        channel.exchangeDeclare(TopicProducer.EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        String queueName = "Q1";
        channel.queueDeclare(queueName, false, false, false, null);
        channel.queueBind(queueName, TopicProducer.EXCHANGE_NAME, "*.orange.*");
        System.out.println("Q1 waiting");
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String msg = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(queueName + " --> " + delivery.getEnvelope().getRoutingKey() + " --> " + msg);
        };
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});
    }
}

class TopicReceiverTwo{
    public static void main(String[] args) throws IOException {
        Channel channel = RabbitMqUtil.getChannel();
        channel.exchangeDeclare(TopicProducer.EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        String queueName = "Q2";
        channel.queueDeclare(queueName, false, false, false, null);
        channel.queueBind(queueName, TopicProducer.EXCHANGE_NAME, "*.*.rabbit");
        channel.queueBind(queueName, TopicProducer.EXCHANGE_NAME, "lazy.#");
        System.out.println("Q2 waiting");
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String msg = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(queueName + " --> " + delivery.getEnvelope().getRoutingKey() + " --> " + msg);
        };
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});
    }
}

死信队列

由于特定的原因导致 queue 中某些消息无法被消费,如果这些消息没有被后续处理,就变成了死信

死信的原因

  • 消息TTL过期
  • 队列满了
  • 消息被拒绝
/**
 * @Author: chen yang
 * @Date: 2022/8/6 18:32
 */
public class DeadProducer {
    public static final String NORMAL_CHANGE = "normal_change";
    public static void main(String[] args) throws IOException {
        Channel channel = RabbitMqUtil.getChannel();
        channel.exchangeDeclare(NORMAL_CHANGE, BuiltinExchangeType.DIRECT);
        for (int i = 0; i < 10; i++) {
            String msg = "info -> " + i;
            // 设置TTL
//            channel.basicPublish(NORMAL_CHANGE, "Q1", new AMQP.BasicProperties().builder()
//                    .expiration("10000").build(), msg.getBytes(StandardCharsets.UTF_8));
            channel.basicPublish(NORMAL_CHANGE, "Q1", null, msg.getBytes(StandardCharsets.UTF_8));
        }
        System.out.println("生产者消息发送完成");
    }
}

class DeadConsumer{

    public static final String DEAD_EXCHANGE = "dead_change";
    public static final String DEAD_ROUTING_KEY = "Q2";
    public static final String DEAD_QUEUE_NAME = "deadQueue";

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

        channel.exchangeDeclare(DeadProducer.NORMAL_CHANGE, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);


        channel.queueDeclare(DEAD_QUEUE_NAME, false, false, false, null);
        channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE, DEAD_ROUTING_KEY);

        HashMap<String, Object> params = new HashMap<>();
        params.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        params.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY);
        // 设置队列长度
//        params.put("x-max-length", 6);

        String normalQueueName = "normalQueue";
        channel.queueDeclare(normalQueueName, false, false, false, params);
        channel.queueBind(normalQueueName, DeadProducer.NORMAL_CHANGE, "Q1");

        System.out.println("waiting...");
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String msg = new String(delivery.getBody(), StandardCharsets.UTF_8);
            if ("info -> 7".equals(msg)){
                System.out.println("Consumer reject this message:" + msg);
                // 设置拒绝消息
                channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false);
            }else {
                System.out.println(normalQueueName + " --> " +  msg);
                // false:不批量应答
                channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
            }
        };
        boolean autoAck = false;
        channel.basicConsume(normalQueueName, autoAck, deliverCallback, consumerTag -> {});
    }
}

class DeadReceiver{
    public static void main(String[] args) throws IOException {
        Channel channel = RabbitMqUtil.getChannel();
        channel.exchangeDeclare(DeadConsumer.DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
        channel.queueDeclare(DeadConsumer.DEAD_QUEUE_NAME, false, false, false, null);
        channel.queueBind(DeadConsumer.DEAD_QUEUE_NAME, DeadConsumer.DEAD_EXCHANGE, DeadConsumer.DEAD_ROUTING_KEY);
        System.out.println("dead channel waiting...");
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String msg = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(DeadConsumer.DEAD_QUEUE_NAME + " --> " +  msg);
        };
        channel.basicConsume(DeadConsumer.DEAD_QUEUE_NAME, true, deliverCallback, consumerTag -> {});
    }
}

队列延迟

队列内部有序,延时队列就是来存放在指定时间被处理的元素队列。例如:订单十分钟后自动取消、用户退款48小时自动处理

TTL

  • 队列TTL:消息进入该队列在设定时间内未被消费则会成为死信
  • 消息TTL:可以为每条消息设置一个TTL,如果同时配置了队列TTL和消息TTL,则将会使用较小的那个值

消息设置TTL

channel.basicPublish(NORMAL_CHANGE, "Q1", new AMQP.BasicProperties().builder().expiration("10000").build(), msg.getBytes(StandardCharsets.UTF_8));

队列设置TTL

params.put("x-message-ttl", 5000);
channel.queueDeclare(normalQueueName, false, false, false, params);

区别:如果设置了队列TTL属性,那么消息一旦过期,就会被队列抛弃(如果配置了死信队列则会被丢到死信队列中),如果是设置了消息TTL,即使消息过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者之前判定的,如果队列存在严重的消息积压情况,那么已过期的消息也许可以存活较长时间。

如果不设置TTL,则表示消息永远不会过期,如果将消息设置为0,则表示存放此时可以直接投递到消费者,否则该消息会被直接丢弃

整合SpringBoot

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest

队列TTL

创建两个队列,TTL分别为10s和40s,在创建一个交换机和死信交换机以及一个死信队列。

/**
 * @Author: chen yang
 * @Date: 2022/8/7 14:52
 */
@Configuration
public class QueueTtlConfig {
    /**
     * exchange
     */
    public static final String NORMAL_EXCHANGE = "NORMAL-EXCHANGE";
    public static final String DEAD_EXCHANGE = "DEAD-EXCHANGE";
    /**
     * queue
     */
    public static final String NORMAL_A_QUEUE = "NORMAL-A-QUEUE";
    public static final String NORMAL_B_QUEUE = "NORMAL-B-QUEUE";
    public static final String DEAD_QUEUE = "DEAD-QUEUE";

    /**
     * routingKey
     */
    public static final String NORMAL_A_ROUTING_KEY = "NORMAL-EXCHANGE-A-ROUTING-KEY";
    public static final String NORMAL_B_ROUTING_KEY = "NORMAL-EXCHANGE-B-ROUTING-KEY";
    public static final String DEAD_ROUTING_KEY = "DEAD-EXCHANGE-ROUTING-KEY";

    public static final Integer ONE_THOUSAND = 1000;

    /**
     * 创建交换机
     * @return
     */
    @Bean("normalExchange")
    public DirectExchange normalExchange(){
        return new DirectExchange(NORMAL_EXCHANGE);
    }

    @Bean("deadExchange")
    public DirectExchange deadExchange(){
        return new DirectExchange(DEAD_EXCHANGE);
    }

    /**
     * 创建普通队列A并绑定交换机
     * @return
     */
    @Bean("normalAQueue")
    public Queue normalQueueA(){
        HashMap<String, Object> args = new HashMap<>(3);
        args.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        args.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY);
        args.put("x-message-ttl", 10 * ONE_THOUSAND);
        return QueueBuilder.durable(NORMAL_A_QUEUE).withArguments(args).build();
    }

    @Bean
    public Binding queueABindingNormalExchange(@Qualifier("normalAQueue") Queue queue,
                                               @Qualifier("normalExchange") DirectExchange directExchange){
        return BindingBuilder.bind(queue).to(directExchange).with(NORMAL_A_ROUTING_KEY);
    }

    /**
     * 创建普通队列B并绑定交换机
     * @return
     */
    @Bean("normalQueueB")
    public Queue normalQueueB(){
        HashMap<String, Object> args = new HashMap<>(3);
        args.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        args.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY);
        args.put("x-message-ttl", 30 * ONE_THOUSAND);
        return QueueBuilder.durable(NORMAL_B_QUEUE).withArguments(args).build();
    }

    @Bean
    public Binding queueBBindingNormalExchange(@Qualifier("normalQueueB") Queue queue,
                                               @Qualifier("normalExchange") DirectExchange directExchange){
        return BindingBuilder.bind(queue).to(directExchange).with(NORMAL_B_ROUTING_KEY);
    }

    /**
     * 创建死信队列即绑定死信交换机
     * @return
     */
    @Bean("deadQueue")
    public Queue deadQueue(){
        return new Queue(DEAD_QUEUE);
    }

    @Bean
    public Binding deadQueueBindingDeadExchange(@Qualifier("deadQueue") Queue queue,
                                                @Qualifier("deadExchange") DirectExchange directExchange){
        return BindingBuilder.bind(queue).to(directExchange).with(DEAD_ROUTING_KEY);
    }
}
@GetMapping("/sendMsg/{message}")
public void sendMsg(@PathVariable String message){
    log.info("当前时间:{},发送一条信息给两个 TTL 队列:{}", LocalDateTime.now(), message);
    rabbitTemplate.convertAndSend(QueueTtlConfig.NORMAL_EXCHANGE, QueueTtlConfig.NORMAL_A_ROUTING_KEY, "消息来自 ttl 为 10S 的队列:" + message);
    rabbitTemplate.convertAndSend(QueueTtlConfig.NORMAL_EXCHANGE, QueueTtlConfig.NORMAL_B_ROUTING_KEY, "消息来自 ttl 为 30S 的队列:" + message);
}
@Component
@Slf4j
public class DeadQueueConsumer {

    @RabbitListener(queues = QueueTtlConfig.DEAD_QUEUE)
    public void receiveD(Message message, Channel channel) throws IOException
    {String msg = new String(message.getBody());
        log.info("当前时间:{},收到死信队列信息{}", LocalDateTime.now(), msg);
    }
}

问题:每增加一个新的时间需求就需要新增一个队列

解决办法:TTL由生产者设置

public static final String NORMAL_C_QUEUE = "NORMAL-C-QUEUE";
public static final String NORMAL_C_ROUTING_KEY = "NORMAL-EXCHANGE-C-ROUTING-KEY";

/**
 * 创建普通队列C并绑定交换机
 * @return
 */
@Bean("normalQueueC")
public Queue normalQueueC(){
    HashMap<String, Object> args = new HashMap<>(2);
    args.put("x-dead-letter-exchange", DEAD_EXCHANGE);
    args.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY);
    return QueueBuilder.durable(NORMAL_C_QUEUE).withArguments(args).build();
}

@Bean
public Binding queueCBindingNormalExchange(@Qualifier("normalQueueC") Queue queue,
                                           @Qualifier("normalExchange") DirectExchange directExchange){
    return BindingBuilder.bind(queue).to(directExchange).with(NORMAL_C_ROUTING_KEY);
}
@GetMapping("sendExpirationMsg/{message}/{ttlTime}")
public void sendMsg(@PathVariable String message,@PathVariable String ttlTime) {
    rabbitTemplate.convertAndSend(QueueTtlConfig.NORMAL_EXCHANGE, QueueTtlConfig.NORMAL_C_ROUTING_KEY, message, correlationData -> {
        correlationData.getMessageProperties().setExpiration(ttlTime);
        return correlationData;
    });
    log.info("当前时间:{},发送一条时长{}毫秒 TTL 信息给队列 C:{}", LocalDateTime.now(),ttlTime, message);
}

问题:队列为先进先出,Rabbitmq只会检查第一个消息是否过期,如果消息过期则丢到死信队列,但是如果第一个消息延时很长,而第一个消息延时很短,但是并不会先执行第二个任务。 

延时队列插件

官网:Community Plugins — RabbitMQ

下载`rabbitmq_delayed_message_exchange` 插件,然后解压放置到 RabbitMQ 的安装目录下的 plgins 目录,执行下面命令让该插件生效

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

 

@Configuration
public class DelayedQueueConfig {

    public static final String DELAYED_QUEUE_NAME = "delayed.queue";
    public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";
    public static final String DELAYED_ROUTING_KEY = "delayed.routingkey";

    @Bean
    public Queue delayedQueue() {
        return new Queue(DELAYED_QUEUE_NAME);
    }

    @Bean
    public CustomExchange delayedExchange() {
        Map<String, Object> args = new HashMap<>();
        //自定义交换机的类型
        args.put("x-delayed-type", "direct");
        return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false, args);
    }

    @Bean
    public Binding bindingDelayedQueue(@Qualifier("delayedQueue") Queue queue, @Qualifier("delayedExchange") CustomExchange delayedExchange) {
        return BindingBuilder.bind(queue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();
    }
}
@GetMapping("sendDelayMsg/{message}/{delayTime}")
public void sendMsg(@PathVariable String message,@PathVariable Integer delayTime) {
    rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, message, correlationData -> {
                correlationData.getMessageProperties().setDelay(delayTime);
                return correlationData;
            });
    log.info(" 当前时间:{},发送一条延迟 {} 毫秒的信息给队列 delayed.queue:{}", LocalDateTime.now(),delayTime, message);

}
public static final String DELAYED_QUEUE_NAME = "delayed.queue";

@RabbitListener(queues = DELAYED_QUEUE_NAME)
public void receiveDelayedQueue(Message message){
    String msg = newString(message.getBody());
    log.info("当前时间:{},收到延时队列的消息:{}", new Date().toString(), msg);
}

发布确认高级

spring:
  rabbitmq:
    publisher-confirm-type: correlated
public class ConfirmConfig {

    public static final String CONFIRM_EXCHANGE = "confirm-exchange";

    public static final String CONFIRM_QUEUE = "confirm-queue";

    public static final String ROUTING_KEY = "confirm-routing-key";

    @Bean("confirmExchange")
    public DirectExchange  confirmExchange(){
        return new DirectExchange(CONFIRM_EXCHANGE);
    }

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

    @Bean
    public Binding confirmBinding(@Qualifier("confirmQueue") Queue queue,
                                  @Qualifier("confirmExchange") DirectExchange directExchange){
        return BindingBuilder.bind(queue).to(directExchange).with(ROUTING_KEY);
    }
}
@GetMapping("/sendMessage/{message}")
public void sendMessage(@PathVariable String message){
    CorrelationData data = new CorrelationData("1");
    rabbitTemplate.convertAndSend(QueueTtlConfig.NORMAL_EXCHANGE, QueueTtlConfig.NORMAL_C_ROUTING_KEY, message, data);
    log.info("发送的消息内容:" + message);
}
@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);
    }

    /**
     * 交换机不管是否收到消息的一个回调方法
     * CorrelationData  消息相关数据
     * ack  交换机是否收到消息
     * cause  失败的原因
     */

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData == null ? "" : correlationData.getId();
        if (ack){
            log.info("交换机已经收到 id 为:{}的消息",id);
        }else {
            log.info("交换机还未收到 id 为:{}消息,由于原因:{}",id,cause);
        }
    }
}

回退消息

spring:
  rabbitmq:
    publisher-confirm-type: correlated
    publisher-returns: true
@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setReturnCallback(this);
    }

    /**
     * 交换机不管是否收到消息的一个回调方法
     * CorrelationData  消息相关数据
     * ack  交换机是否收到消息
     * cause  失败的原因
     */

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData == null ? "" : correlationData.getId();
        if (ack){
            log.info("交换机已经收到 id 为:{}的消息",id);
        }else {
            log.info("交换机还未收到 id 为:{}消息,由于原因:{}",id,cause);
        }
    }

    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        log.info("消息:{}被服务器退回,退回原因:{}, 交换机是:{}, 路由 key:{}",
                new String(returnedMessage.getMessage().getBody()),
                returnedMessage.getReplyText(), returnedMessage.getExchange(), returnedMessage.getRoutingKey());
    }

    @GetMapping("sendMessage")
    public void sendMessage(String message){
        //让消息绑定一个 id 值
        CorrelationData correlationData1 = new CorrelationData(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE,ConfirmConfig.ROUTING_KEY,message+" key1",correlationData1);
        log.info("发送消息 id 为:{}内容为{}",correlationData1.getId(),message+" key1");
        CorrelationData correlationData2 = new CorrelationData(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE,"key2",message+" key2",correlationData2);
        log.info("发送消息 id 为:{}内容为{}",correlationData2.getId(),message+" key2");
    }
}
@GetMapping("/sendMessage/{message}")
public void sendMessage(@PathVariable String message){
    CorrelationData data1 = new CorrelationData("1");
    rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE, ConfirmConfig.ROUTING_KEY, message+ "key1", data1);

    CorrelationData data2 = new CorrelationData("2");
    String routingKey2 = "key2";
    rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE, routingKey2, message + " key2", data2);
    log.info("发送的消息内容:" + message);
}

猜你喜欢

转载自blog.csdn.net/weixin_46058921/article/details/126858612