rabbitmq学习笔记

前言

​ 本文为自己在学习rabbitmq时的学习笔记,欢迎大家一起学习交流。

1.相关配置

1.1安装

rabbitmq历史版本下载:https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.8.5
erlang历史版本下载:https://github.com/rabbitmq/erlang-rpm/tags?after=v23.2.7

相关命令

具体的安装教程大家可以上网百度,有很多,这里只是简单列出一些常用命令(以centOS7系统为例)

yun -y install 对应的erlang安装包              安装erlang
yum -y install 对应的rabbitmq安装包			 安装rabbitmq
systemctl start rabbitmq-server.service      开启服务
systemctl status rabbitmq-server.service   	 查看状态
systemctl restart rabbitmq-server.service    重启服务
rabbitmq-plungins enable rabbitmq_management 安装UI插件

安装UI插件后可在浏览器输入:服务器ip:15672 例如:192.168.2.24:15672来访问rabbitmq的管理页面(默认用户名和密码都为guest)

如果访问失败的话需要防火墙问题
1、检查服务器是否开启了防火墙。可以通过以下命令查看:

sudo systemctl status firewalld

2、如果服务器开启了防火墙,则需要添加防火墙规则,允许外部访问 15672 端口。如下所示:

开放15672端口(管理页面占用端口)
sudo firewall-cmd --zone=public --add-port=15672/tcp --permanent
开放5672端口(后续通过代码连接是占用端口)
sudo firewall-cmd --zone=public --add-port=5672/tcp --permanent
sudo firewall-cmd --reload

2.官方案例

官方给出的java代码:rabbitmq-tutorials/java at main · rabbitmq/rabbitmq-tutorials (github.com)

1.简单队列(“Hello Wrold!”)

官方文档:RabbitMQ tutorial - “Hello world!” — RabbitMQ

Producer -> Queue -> Consuming: send and receive messages from a named queue.

优点

​ 简单

缺点

​ 如果生产者无法及时消费消费者所产生的数据,会导致内存崩溃

代码

Send.java

package com.xxxx.simple.send;

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

import java.nio.charset.StandardCharsets;

/**
 * 简单队列-消息生产者
 */
public class Send {

    // 定义队列名称
    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("你的ip地址");
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);

        try (
                // 连接工厂创建连接
                Connection connection = factory.newConnection();
                // 创建信道
                Channel channel = connection.createChannel()) {
            /*
              声明队列
              第一个参数queue:队列名称第
              二个参数durable:是否持久化
              第三个参数Exclusive:排他队列,如果一个队列被声明为排他队列,
              该队列仅对首次声明它的连接可见,并在连接断开时自动删除。
              这里需要注意三点:
                   1.排他队列是基于连接可见的,同一连接的不同通道是可以同时访问同一个连接创建的排他队列的。
                   2.“首次",如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同。
                   3.即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的。这种队列适用于只限于一个客户端发送读取消息的应用场景。
              第四个参数Auto-delete:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于|临时队列。
             */
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            String message = "Hello World!";
            // 发送消息
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}

Recv.java

package com.xxxx.simple.recv;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;

/**
 * 简单队列-消息消费者
 */
public class Recv {

    // 定义队列名称
    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();
        // 绑定队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        /*
            监听队列消费消息
            队列名称
            自动回执:当消费者受到消息的时候会告诉队列:我受到消息了
            剩下的无所谓
         */
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}

2.工作队列(Work queues)

官方文档:RabbitMQ tutorial - Work Queues — RabbitMQ

Producer -> Queue -> Consuming: Work Queue used to distribute time-consuming tasks among multiple workers.

工作队列-轮询

建立多个消费者,每个消费者依次访问

优点

​ 用来解决生产者的生产能力远远大于消费者的消费能力的时候,通过多加几个消费者的方式来实现

缺点
  1. 因为每个消费者的消费水平可能是不一样的,所以可能会导致消费快的消费者等待消费慢的消费者
  2. 整体的消费处理时间仍然是比较长的代码
代码

Send.java

package com.xxxx.work.send;

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

import java.nio.charset.StandardCharsets;

/**
 * 工作队列-轮询-消息生产者
 */
public class Send {

    // 定义队列名称
    private final static String QUEUE_NAME = "work_rr";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
       	factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);

        try (
                // 连接工厂创建连接
                Connection connection = factory.newConnection();
                // 创建信道
                Channel channel = connection.createChannel()) {
            /*
              声明队列
              第一个参数queue:队列名称第
              二个参数durable:是否持久化
              第三个参数Exclusive:排他队列,如果一个队列被声明为排他队列,
              该队列仅对首次声明它的连接可见,并在连接断开时自动删除。
              这里需要注意三点:
                   1.排他队列是基于连接可见的,同一连接的不同通道是可以同时访问同一个连接创建的排他队列的。
                   2.“首次",如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同。
                   3.即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的。这种队列适用于只限于一个客户端发送读取消息的应用场景。
              第四个参数Auto-delete:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于|临时队列。
             */
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            //模拟工作队列可以发送很多条消息的情况
            for (int i = 0; i < 20; i++) {
                String message = "Hello World!"+i;
                // 发送消息
                channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
                System.out.println(" [x] Sent '" + message + "'" + i);
            }
        }
    }
}

Recv01.java

package com.xxxx.work.recv;

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

import java.nio.charset.StandardCharsets;

/**
 * 工作队列-轮询-消息消费者01
 */
public class Recv01 {

    // 定义队列名称
    private final static String QUEUE_NAME = "work_rr";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
       	factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();
        // 绑定队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            //模拟消费耗时
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
            /*
                手动确认
                multiple:是否确认多条
             */
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
        };
        /*
            监听队列消费消息
            队列名称
            自动回执:当消费者受到消息的时候会告诉队列:我受到消息了
            剩下的无所谓
         */
        channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
    }
}

Recv02.java

package com.xxxx.work.recv;

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

import java.nio.charset.StandardCharsets;

/**
 * 工作队列-轮询-消息消费者02
 */
public class Recv02 {

    // 定义队列名称
    private final static String QUEUE_NAME = "work_rr";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();
        // 绑定队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            //模拟消费耗时
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
            /*
                手动确认
                multiple:是否确认多条
             */
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
        };
        /*
            监听队列消费消息
            队列名称
            自动回执:当消费者受到消息的时候会告诉队列:我受到消息了
            剩下的无所谓
         */
        channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
    }
}
运行效果

在这里插入图片描述

工作队列-公平

​ 注意:这里的公平并不是传统意义上的公平,而是让消费快的消费者去帮消费慢的消费者消费,可以理解为能者多劳

优点
  1. 因为能者多劳,所有不会造成资源浪费了
  2. 整体的消费时间会变短
代码

Send.java

package com.xxxx.work.fair.send;

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

import java.nio.charset.StandardCharsets;

/**
 * 工作队列-公平-消息生产者
 */
public class Send {

    // 定义队列名称
    private final static String QUEUE_NAME = "work_fair";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);

        try (
                // 连接工厂创建连接
                Connection connection = factory.newConnection();
                // 创建信道
                Channel channel = connection.createChannel()) {
            /*
              声明队列
              第一个参数queue:队列名称第
              二个参数durable:是否持久化
              第三个参数Exclusive:排他队列,如果一个队列被声明为排他队列,
              该队列仅对首次声明它的连接可见,并在连接断开时自动删除。
              这里需要注意三点:
                   1.排他队列是基于连接可见的,同一连接的不同通道是可以同时访问同一个连接创建的排他队列的。
                   2.“首次",如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同。
                   3.即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的。这种队列适用于只限于一个客户端发送读取消息的应用场景。
              第四个参数Auto-delete:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于|临时队列。
             */
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            //模拟工作队列可以发送很多条消息的情况
            for (int i = 0; i < 20; i++) {
                String message = "Hello World!"+i;
                // 发送消息
                channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
                System.out.println(" [x] Sent '" + message + "'" + i);
            }
        }
    }
}

Recv01.java

package com.xxxx.work.fair.recv;

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

import java.nio.charset.StandardCharsets;

/**
 * 工作队列-公平-消息消费者01
 */
public class Recv01 {

    // 定义队列名称
    private final static String QUEUE_NAME = "work_fair";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();
        // 绑定队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
        //限制消费者每次只能接收一条消息,处理完才能接收下一条消息
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            //模拟消费耗时(消费快的)
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
            /*
                手动确认
                multiple:是否确认多条
             */
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
        };
        /*
            监听队列消费消息
            队列名称
            自动回执:当消费者受到消息的时候会告诉队列:我受到消息了
            剩下的无所谓
         */
        channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
    }
}

Recv02.java

package com.xxxx.work.fair.recv;

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

import java.nio.charset.StandardCharsets;

/**
 * 工作队列-公平-消息消费者02
 */
public class Recv02 {

    // 定义队列名称
    private final static String QUEUE_NAME = "work_fair";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();
        // 绑定队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
        //限制消费者每次只能接收一条消息,处理完才能接收下一条消息
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            //模拟消费耗时(消费慢的)
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
            /*
                手动确认
                multiple:是否确认多条
             */
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
        };
        /*
            监听队列消费消息
            队列名称
            自动回执:当消费者受到消息的时候会告诉队列:我受到消息了
            剩下的无所谓
         */
        channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
    }
}

用法:在消费者队列中加入这一条代码

//限制消费者每次只能接收一条消息,处理完才能接收下一条消息
int prefetchCount = 1;
channel.basicQos(prefetchCount);
运行效果

在这里插入图片描述

3.发布与订阅(Pubilst/Subscribe)

​ 并不是工作队列中有什么缺点,而是无法满足一些需求(生产者发送一条消息,需要这条消息被多个消费者同时接收到,比如微信公众号),因此引入发布于订阅。

官方文档:RabbitMQ tutorial - Publish/Subscribe — RabbitMQ
在这里插入图片描述

​ 如上图所示,它在生产者与队列之间增加了X(交换机),生产者将消息发送到交换机,交换机去绑定不同的队列并把消息发给这些队列,每一个队列后面都有不同的消费者去接收这些消息,这样就实现了不同的消费者去接收同一条消息的场景。

代码

交换机为广播模式(FANOUT)

Send.java

package com.xxxx.fanout.send;

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

import java.nio.charset.StandardCharsets;

/**
 * 发布/订阅-消息生产者
 */
public class Send {

    //定义交换机名称
    //(广播模式)
    private final static String EXCHANGE_NAME = "exchange_fanout";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);

        try (
                // 连接工厂创建连接
                Connection connection = factory.newConnection();
                // 创建信道
                Channel channel = connection.createChannel()) {
            //绑定交换机(广播模式)
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
            String message = "Hello World!";
            // 发送消息
            channel.basicPublish(EXCHANGE_NAME,"", null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}

Recv01.java

package com.xxxx.fanout.recv;

import com.rabbitmq.client.*;

import java.nio.charset.StandardCharsets;

/**
 * 工发布/订阅-消息消费者01
 */
public class Recv01 {

    // 定义队列名称
    private final static String EXCHANGE_NAME = "exchange_fanout";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();

        // 绑定交换机(广播模式)
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
        // 获取队列(排他队列(为了不造成资源的浪费,这里的队列获取的都是排他队列))
        String queueName = channel.queueDeclare().getQueue();
        // 绑定队列和交换机
        channel.queueBind(queueName, EXCHANGE_NAME, "");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        // 监听队列消费消息
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
        });
    }
}

Recv02.java

package com.xxxx.fanout.recv;

import com.rabbitmq.client.*;

import java.nio.charset.StandardCharsets;

/**
 * 工发布/订阅-消息消费者02
 */
public class Recv02 {

    // 定义队列名称
    private final static String EXCHANGE_NAME = "exchange_fanout";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();

        // 绑定交换机(广播模式)
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
        // 获取队列(排他队列(为了不造成资源的浪费,这里的队列获取的都是排他队列))
        String queueName = channel.queueDeclare().getQueue();
        // 绑定队列和交换机
        channel.queueBind(queueName, EXCHANGE_NAME, "");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        // 监听队列消费消息
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
        });
    }
}

运行效果

首先可以发现管理控制台中多了一个交换机,同时里面多了两个队列
在这里插入图片描述

同时,还可以看到生产者发送了一条消息后,两个消费者都收到了

在这里插入图片描述

4.路由模式(Routing)

官方文档:RabbitMQ tutorial - Routing — Ra bbitMQ

​ 路由模式同样能够使生产者发送相同的消息给消费者,但是他可以使不同群体的消费者收到不同的群体消息,类似于公众号的vip文章。

在这里插入图片描述

优点

​ 定制度更高了,想给谁发给谁发,可以满足类似于vip文章的需求,甚至也可以实现发布/订阅的模式。

缺点

​ 当项目足够庞大的时候,可能会有一千个、一万个key,不方便维护(可以通过下一个模式(主机模式)来解决)。

代码

交换机为直连模式(direct)

Send.java

package com.xxxx.direct.send;

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

import java.nio.charset.StandardCharsets;

/**
 * 路由队列-消息生产者
 */
public class Send {

    //定义交换机名称
    //(路由模式)
    private final static String EXCHANGE_NAME = "exchange_direct";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);

        try (
                // 连接工厂创建连接
                Connection connection = factory.newConnection();
                // 创建信道
                Channel channel = connection.createChannel()) {
            // 绑定交换机(路由模式)
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            // 准备对应的信息
            String infoMessage = "普通信息!";
            String errorMessage = "错误信息!";
            String warningMessage = "警告信息!";
            // 准备对应的路由
            String infoRoutingKey = "info";
            String errorRoutingKey = "error";
            String warningRoutingKey = "warning";
            // 发送消息
            channel.basicPublish(EXCHANGE_NAME, infoRoutingKey, null, infoMessage.getBytes(StandardCharsets.UTF_8));
            channel.basicPublish(EXCHANGE_NAME, errorRoutingKey, null, errorMessage.getBytes(StandardCharsets.UTF_8));
            channel.basicPublish(EXCHANGE_NAME, warningRoutingKey, null, warningMessage.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + infoMessage + "'");
            System.out.println(" [x] Sent '" + errorMessage + "'");
            System.out.println(" [x] Sent '" + warningMessage + "'");
        }
    }
}

Recv01.java

package com.xxxx.direct.recv;

import com.rabbitmq.client.*;

import java.nio.charset.StandardCharsets;

/**
 * 路由队列-消息消费者01
 */
public class Recv01 {

    // 定义队列名称
    private final static String EXCHANGE_NAME = "exchange_direct";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();

        // 绑定交换机(路由模式)
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        // 获取队列(排他队列(为了不造成资源的浪费,这里的队列获取的都是排他队列))
        String queueName = channel.queueDeclare().getQueue();
        // 绑定队列和交换机(让它只能收错误信息)
        String errorRoutingKey = "error";
        channel.queueBind(queueName, EXCHANGE_NAME, errorRoutingKey);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        // 监听队列消费消息
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
        });
    }
}

Recv02.java

package com.xxxx.direct.recv;

import com.rabbitmq.client.*;

import java.nio.charset.StandardCharsets;

/**
 * 路由队列-消息消费者02
 */
public class Recv02 {

    // 定义队列名称
    private final static String EXCHANGE_NAME = "exchange_direct";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();

        // 绑定交换机(路由模式)
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        // 获取队列(排他队列(为了不造成资源的浪费,这里的队列获取的都是排他队列))
        String queueName = channel.queueDeclare().getQueue();
        // 绑定队列和交换机(让它绑定三个路由,使它能收三种信息)
        String infoRoutingKey = "info";
        String errorRoutingKey = "error";
        String warningRoutingKey = "warning";
        channel.queueBind(queueName, EXCHANGE_NAME, infoRoutingKey);
        channel.queueBind(queueName, EXCHANGE_NAME, errorRoutingKey);
        channel.queueBind(queueName, EXCHANGE_NAME, warningRoutingKey);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag , delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        // 监听队列消费消息
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
        });
    }
}

运行效果

可以看到,Revc01只能收到错误信息,Recv02可以收到三种信息
在这里插入图片描述

5.主题队列(Topics)

官方文档:RabbitMQ tutorial - Topics — RabbitMQ

​ 它是实际工作中用的最多的一种工作模式,通过主题队列,可以实现前面4种工作模式。它是通过添加通配符去模糊匹配的方式来管理key,尽量减少路由key的编写。

  • ==*==代表可以匹配的唯一单词
    • ==#==代表可以匹配0个或者多个单词
    • 注意:是队列和交换机绑定的时候才能使用通配符,发送消息的时候需要给详细具体的路由名称,不能用通配符,否则会导致错乱

在这里插入图片描述
​ 如上图所示,如果发送"quick.orange.rabbit"这个消息,则可以满足1 2(由上至下),最后走Q1 Q2队列,如果发送"lazy.orange.elephant"这个消息,则可以满足1 3,最后走Q1 Q2两个队列,如果发送"lazy.brown.fox"这个消息,则可以满足3,最后走Q2这一个队列。

代码

交换机为topic模式

Send.java

package com.xxxx.topic.send;

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

import java.nio.charset.StandardCharsets;

/**
 * 主题队列-消息生产者
 */
public class Send {

    //定义交换机名称
    //(路由模式)
    private final static String EXCHANGE_NAME = "exchange_topic";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);

        try (
                // 连接工厂创建连接
                Connection connection = factory.newConnection();
                // 创建信道
                Channel channel = connection.createChannel()) {
            // 绑定交换机(路由模式)
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
            // 准备对应的信息
            String infoMessage = "普通信息!";
            String errorMessage = "错误信息!";
            String warningMessage = "警告信息!";
            // 准备对应的路由
            String infoRoutingKey = "info.message.orange";
            String errorRoutingKey = "error.rabbit.lazy";
            String warningRoutingKey = "orange.warning.message";
            // 发送消息
            channel.basicPublish(EXCHANGE_NAME, infoRoutingKey, null, infoMessage.getBytes(StandardCharsets.UTF_8));
            channel.basicPublish(EXCHANGE_NAME, errorRoutingKey, null, errorMessage.getBytes(StandardCharsets.UTF_8));
            channel.basicPublish(EXCHANGE_NAME, warningRoutingKey, null, warningMessage.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + infoMessage + "'");
            System.out.println(" [x] Sent '" + errorMessage + "'");
            System.out.println(" [x] Sent '" + warningMessage + "'");
        }
    }
}

Recv01.java

package com.xxxx.topic.recv;

import com.rabbitmq.client.*;

import java.nio.charset.StandardCharsets;

/**
 * 主题队列-消息消费者01
 */
public class Recv01 {

    // 定义队列名称
    private final static String EXCHANGE_NAME = "exchange_topic";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();

        // 绑定交换机(主题队列)
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        // 获取队列(排他队列(为了不造成资源的浪费,这里的队列获取的都是排他队列))
        String queueName = channel.queueDeclare().getQueue();
        // 绑定队列和交换机(让它只能收错误信息)
        String RoutingKey = "#.message.#";
        channel.queueBind(queueName, EXCHANGE_NAME, RoutingKey);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        // 监听队列消费消息
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
        });
    }
}

Recv02.java

package com.xxxx.topic.recv;

import com.rabbitmq.client.*;

import java.nio.charset.StandardCharsets;

/**
 * 主题队列-消息消费者02
 */
public class Recv02 {

    // 定义队列名称
    private final static String EXCHANGE_NAME = "exchange_topic";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();

        // 绑定交换机(路由模式)
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        // 获取队列(排他队列(为了不造成资源的浪费,这里的队列获取的都是排他队列))
        String queueName = channel.queueDeclare().getQueue();
        // 绑定队列和交换机(让它绑定三个路由,使它能收三种信息)
        String RoutingKey = "*.rabbit.*";
        channel.queueBind(queueName, EXCHANGE_NAME, RoutingKey);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag , delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        // 监听队列消费消息
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
        });
    }
}

运行效果

在这里插入图片描述

​ 如上图所示,Recv01能收到两条消息,Recv02,能收到一条消息

6.RPC模式(Remote procedure call)

官方文档:RabbitMQ tutorial - Remote procedure call (RPC) — RabbitMQ

​ 了解即可,很少会用

Summary illustration, which is described in the following bullet points.

​ 如上图所示,客户端一开始发送一个消息给rpc_queue队列,我们服务器类似于消费者去接收并且监听这个消息。注意:此时客户端发送的消息中有两个参数,reply_to的作用是:服务器处理完消息后,也作为生产者,将消息放到队列中,此时的消息就是放到reply_to所指定的队列里;而correlation_id的作用是:可能会有多个客户端都指定服务器发送到同一队列中,此时客户端就可以根据correlation_id去取消息。 类似于网购,你从网上下单,告诉平台要什么商品,然后平台根据你的收件地址,将商品发送给你,此时,比如在学校的话,很多人都会写学校这个地址,那么就可以根据手机号来确定这个快递是你的。

注意:在RPC模式中,不论是服务器,还是客户端,它们都是两种角色,即消费者和生产者。

代码

Send.java

package com.xxxx.rpc.server;

import com.rabbitmq.client.*;

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

/**
 * RPC模式队列-服务端
 * 注意:一定要先启动server
 */
public class RPCServer {

    // 队列名称
    private static final String RPC_QUEUE_NAME = "rpc_queue";

    /**
     * 计算斐波那契数列
     *
     * @param n
     * @return
     */
    private static int fib(int n) {
        if (n == 0) return 0;
        if (n == 1) return 1;
        return fib(n - 1) + fib(n - 2);
    }

    public static void main(String[] argv) throws Exception {
        //创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);

        try {
            // 通过工厂连接
            final Connection connection = factory.newConnection();
            // 获取信道
            final Channel channel = connection.createChannel();
            // 声明队列
            channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
            channel.queuePurge(RPC_QUEUE_NAME);

            /*
                限制RabbitMQ执法不超过1条的消息给同一个消费者。
                当消息处理完毕后,有了反馈,才会进行第二次发送。
             */
            int prefetchCount = 1;
            channel.basicQos(prefetchCount);

            System.out.println(" [x] Awaiting RPC requests");

            Object monitor = new Object();
            // 获取消息
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                // 获取replyTo队列和correlationId请求标识
                AMQP.BasicProperties replyProps = new AMQP.BasicProperties
                        .Builder()
                        .correlationId(delivery.getProperties().getCorrelationId())
                        .build();

                String response = "";
                try {
                    // 接收客户端消息
                    String message = new String(delivery.getBody(), "UTF-8");
                    int n = Integer.parseInt(message);

                    System.out.println(" [.] fib(" + message + ")");
                    // 服务端根据业务需求处理
                    response += fib(n);
                } catch (RuntimeException e) {
                    System.out.println(" [.] " + e);
                } finally {
                    // 将处理结果发送至replyTo队列同时携带correlationId属性
                    channel.basicPublish("", delivery.getProperties().getReplyTo(), replyProps, response.getBytes("UTF-8"));
                    // 手动回执消息
                    channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);

                    // RabbitMq消费者工作线程通知RPC服务器其他所有线程运行
                    synchronized (monitor) {
                        monitor.notify();
                    }
                }
            };

            // 监听队列
            /*
                autoAck = true 代表自动确认消息
                autoAck = false 代表手动确认消息
             */
            boolean autoAck = false;
            channel.basicConsume(RPC_QUEUE_NAME, autoAck, deliverCallback, (consumerTag -> {
            }));
            // 线程等待并准备接收来自RPC客户端的消息
            while (true) {
                synchronized (monitor) {
                    try {
                        monitor.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
    }
}

RPCClient.java

package com.xxxx.rpc.client;

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

import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.*;

/**
 * RPC模式队列-客户端
 * 注意:一定要先启动server
 */
public class RPCClient implements AutoCloseable {

    private Connection connection;
    private Channel channel;
    // 队列名称
    private String requestQueueName = "rpc_queue";

    // 初始化连接
    public RPCClient() throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);

        connection = factory.newConnection();
        channel = connection.createChannel();
    }

    public static void main(String[] argv) {
        try (RPCClient fibonacciRpc = new RPCClient()) {
            for (int i = 0; i < 32; i++) {
                String i_str = Integer.toString(i);
                System.out.println(" [x] Requesting fib(" + i_str + ")");

                // 官网的例子(斐波那契序列)
                // 请求服务端
                String response = fibonacciRpc.call(i_str);
                System.out.println(" [.] Got '" + response + "'");
            }
        } catch (IOException | TimeoutException | InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    // 请求服务端
    public String call(String message) throws IOException, InterruptedException, ExecutionException {
        // correlationId请求表示ID
        final String corrId = UUID.randomUUID().toString();

        // 获取队列名称
        String replyQueueName = channel.queueDeclare().getQueue();

        // 设置replyTo队列和correlationId请求标识
        AMQP.BasicProperties props = new AMQP.BasicProperties
                .Builder()
                .correlationId(corrId)
                .replyTo(replyQueueName)
                .build();

        // 发送消息至队列
        channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8"));

        // 设置线程等待,每次只接收一个响应结果
        final CompletableFuture<String> response = new CompletableFuture<>();

        // 接收服务器返回结果
        String ctag = channel.basicConsume(replyQueueName, true, (consumerTag, delivery) -> {
            if (delivery.getProperties().getCorrelationId().equals(corrId)) {
                // 将给定的元素在给定的时间内设置到线程队列中,如果设置成功返回true,否则返回false
                response.complete(new String(delivery.getBody(), "UTF-8"));
            }
        }, consumerTag -> {
        });

        // 从线程队列中获取值,如果线程队列中没有值,线程会一直阻塞,直到线程队列中有值,并且取得该值
        String result = response.get();
        // 从消息队列中丢弃该值
        channel.basicCancel(ctag);
        return result;
    }

    // 关闭连接
    public void close() throws IOException {
        connection.close();
    }
}

运行效果

在这里插入图片描述

3.RabbitMQ消息的事务机制

了解即可,很少回去用事务,因为会降低它的性能。

​ 在使用RabbitMQ的时候,我们可以通过消息持久化操作来解决因为服务器的异常奔溃导致的消息丢失,除此之外我们还会遇到一个问题,当消息的发布者在将消息发送出去之后,消息到底有没有正确到达broker代理服务器(也就是生产者不知道消息是否到打队列)呢?如果不进行特殊配置的话,默认情况下发布操作是不会返回任何信息给生产者的,也就是默认情况下我们的生产者是不知道消息有没有正确到达broker的,如果在消息到达broker之前已经丢失的话,持久化操作也解决不了这个问题,因为消息根本就没到达代理服务器,你怎么进行持久化,那么这个问题该怎么解决呢?

​ RabbitMQ为我们提供了两种方式:

  • 通过AMQP事务机制实现,这也是AMQP协议层面提供的解决方案;
  • 通过将channel设置成confirm模式来实现;

3.1AMQP事务机制控制

​ RabbitMQ中与事务机制有关的方法有三个:txSelect( ) , txCommit()以及txRollback(), *txSelect()*用于将当前channel设置成transaction模式,,*txCommit()*用于提交事务,*txRollback()用于回滚事务在通过txSelect()开启事务之后,我们便可以发布消息给broker代理服务器了,如果txCommit()提交成功了,则消息一定到达了broker了,如果在txCommit()执行之前broker异常崩溃或者由于其他原因抛出异常,这个时候我们便可以捕获异常通过txRollback()*回滚事务。

代码

Send.java

package com.xxxx.tx.send;

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

import java.nio.charset.StandardCharsets;

/**
 * 事务-消息生产者
 */
public class Send {

    // 定义队列名称
    private final static String QUEUE_NAME = "tx";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        Connection connection = null;
        Channel channel = null;
        try {
            // 连接工厂创建连接
            connection = factory.newConnection();
            // 创建信道
            channel = connection.createChannel();
            // 开启事务
            channel.txSelect();
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            String message = "Hello World!";
            // 发送消息
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            // 手动添加异常
            int i=1/0;
            // 提交事务
            channel.txCommit();
            System.out.println(" [x] Sent '" + message + "'");
        } catch (Exception e) {
            e.printStackTrace();
            // 回滚事务
            channel.txRollback();
        }
    }
}

Recv.java

package com.xxxx.tx.recv;

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

import java.nio.charset.StandardCharsets;

/**
 * 事务-消息消费者
 */
public class Recv {

    // 定义队列名称
    private final static String QUEUE_NAME = "tx";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();
        // 绑定队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        // 监听队列消费消息
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}
运行效果

可以看到,当有异常时,队列中并没有新增消息,而是被事务回滚了

在这里插入图片描述

总结

​ 事务确实能够解决producer与broker之间消息确认的问题,只有消息成功被broker接受,事务提交才能成功,否则我们便可以在捕获异常进行事务回滚操作同时进行消息重发,但是使用事务机制的话会降低RabbitMQ的性能,那么有没有更好的方法既能保障producer知道消息已经正确送到,又能基本上不带来性能上的损失呢?从AMQP协议的层面看是没有更好的方法,但是RabbitMQ提供了一个更好的方案,即将channel信道设置成confrm模式。

4.confirm确认模式

这个很重要!

​ 通过AMQP协议层面为我们提供了事务机制解决了这个问题,但是采用事务机制实现会降低RabbitMQ的消息吞吐量,此时处理AMQP协议层面能够实现消息事物控制外,我们还有第二种方式即:Confirm模式。

4.1confirm确认模式原理

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

​ confirm模式最大的好处在于他是异步的(事务是同步的),一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理
i该nack消息。

​ 在channel被设置成confirm模式之后,所有被publish的后续消息都将被confirm (即 ack)或者被nack一次。但是没有对消息被confirm的快慢做任何保证,并且同一条消息不会既被confirm又被nack。
注意:两种事物控制形式不能同时开启!

4.2confirm确认机制代码实现

实现生产者confirm机制有三种方式:

  • 普通confirm模式:每发送一条消息后,调用waitForConfirms()万法,等待服务器端Cconfirm。实际上是一种串行confirm了(也是同步的方式)。
  • 批量confirm模式:每发送一批消息后,调用waitForConfirmsOrDie()方法,等待服务器端confirm,这个也是串行(也是同步的)。
  • 异步confirm模式:提供一个回调方法,服务端confirm了一条或者多条消息后Client端会回调这个方法。
4.2.1同步confirm

Send.java

package com.xxxx.confirm.sync.send;

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

import java.nio.charset.StandardCharsets;

/**
 * 信道确认模式-同步-消息生产者
 */
public class Send {

    // 定义队列名称
    private final static String QUEUE_NAME = "confirm_sync";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        Connection connection = null;
        Channel channel = null;
        try {
            // 连接工厂创建连接
            connection = factory.newConnection();
            // 创建信道
            channel = connection.createChannel();
            // 开启确认模式
            channel.confirmSelect();
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            String message = "Hello World!";
            // 发送消息
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            // 手动添加异常
            //int i=1/0;

            // 普通confirm模式(由于是在本地模拟,所以很难模拟出发送失败的情况,要在实际应用中,如网络波动时,可能会遇到发送失败)
            //if(channel.waitForConfirms()){
            //    System.out.println("消息发送成功!");
            //}

            //批量confirm模式,一批一批的发,只要有一条未确认,直接抛异常
            channel.waitForConfirmsOrDie();
            System.out.println("消息发送成功!");
            System.out.println(" [x] Sent '" + message + "'");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Recv.java

package com.xxxx.confirm.sync.recv;

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

import java.nio.charset.StandardCharsets;

/**
 * 信道确认模式-同步-消息消费者
 */
public class Recv {

    // 定义队列名称
    private final static String QUEUE_NAME = "confirm_sync";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();
        // 绑定队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        // 监听队列消费消息
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}

​ 以上代码可以看出,使用同步的方式需要等所有的消息发送成功以后才会执行后面代码,只要有一个消息未被确认就会抛出IO异常。解决办法可以使用异步确认。

4.2.2异步confirm

​ 异步confirm模式的编程实现最复杂,Channel对象提供的ConfirmListener()回调方法只包含deliveryTag (当前Chanel发出的消息序号),我们需要自己为每一个Channel维护一个unconfirm的消息序号集合,每publish一条数据,集合中元素加1,每回调一次 handleAck方法,unconfirm集合删掉相应的一条*(multiple=false)或多条(multiple=true)记录。从程序运行效率上看,这个unconfirm*集合最好采用有序集合SortedSet存储结构。实际上, *waitForConfirms()*方法也是通过SortedSet维护消息序号的

Send.java

package com.xxxx.confirm.async.send;

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

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.TimeoutException;

/**
 * 信道确认模式-异步-消息生产者
 */
public class Send {

    // 定义队列名称
    private final static String QUEUE_NAME = "confirm_async";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);

        Connection connection = null;
        Channel channel = null;
        try {
            // 维护信息发送回执deliveryTag
            final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());

            // 连接工厂创建连接
            connection = factory.newConnection();
            // 创建信道
            channel = connection.createChannel();
            // 开启确认模式
            channel.confirmSelect();
            // 声明队列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            /// 添加channel 监听
            channel.addConfirmListener(new ConfirmListener() {
                // 已确认
                @Override
                public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                    // multiple=true已确认多条  =false已确认单条
                    if (multiple) {
                        System.out.println("handleAck--success-->multiple" + deliveryTag);
                        //多条,清除前 deliveryTag项标识id
                        confirmSet.headSet(deliveryTag + 1L).clear();
                    } else {
                        System.out.println("handleAck-success-->single" + deliveryTag);
                        //单条,直接通过remove清除
                        confirmSet.remove(deliveryTag);
                    }
                }

                //未确认
                @Override
                public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                    // multiple=true未确认多条  =false未确认单条
                    if (multiple) {
                        System.out.println("handleAck--failed-->multiple" + deliveryTag);
                        //多条,清除前 deliveryTag项标识id
                        confirmSet.headSet(deliveryTag + 1L).clear();
                    } else {
                        System.out.println("handleAck-failed-->single" + deliveryTag);
                        //单条,直接通过remove清除
                        confirmSet.remove(deliveryTag);
                    }
                }
            });

            // 循环发送消息演示消息确认
            while (true) {
                // 创建消息
                String message = "Hello World!";
                // 获取unconfirm的消息序号deliveryTag
                long seqNo = channel.getNextPublishSeqNo();
                // 发送消息
                channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
                // 将消息序号deliveryTag添加至SortedSet
                confirmSet.add(seqNo);
            }
        } catch (IOException | TimeoutException e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭通道
                if (null != channel && channel.isOpen()) {
                    channel.close();
                }
                // 关闭连接
                if (null != connection && connection.isOpen()) {
                    connection.close();
                }
            } catch (IOException | TimeoutException e) {
                e.printStackTrace();
            }
        }
    }
}

Recv.java

package com.xxxx.confirm.async.recv;

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

import java.nio.charset.StandardCharsets;

/**
 * 信道确认模式-异步-消息消费者
 */
public class Recv {

    // 定义队列名称
    private final static String QUEUE_NAME = "confirm_async";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");   //这里输入你的ip地址
        factory.setUsername("guest");
        factory.setVirtualHost("/");
        factory.setPassword("guest");
        factory.setPort(5672);
        // 连接工厂创建连接
        Connection connection = factory.newConnection();
        // 创建信道
        Channel channel = connection.createChannel();
        // 绑定队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        // 监听队列消费消息
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}

3.Spring集成RabbitMQ

3.1父项目依赖

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.xxxx</groupId>
  <artifactId>rabbotmq-springAMQP</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>


  <modules>
    <module>amqp-send</module>
  </modules>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.7.RELEASE</version>
    <relativePath/>
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

</project>

3.2编写生产者

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xxxx</groupId>
    <artifactId>amqp-send</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>com.xxxx</groupId>
        <artifactId>rabbotmq-springAMQP</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId></dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId></dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId></dependency>
    </dependencies>
</project>

application.yml

spring:
  rabbitmq:
    host: 192.168.244.129
    port: 5672
    username: yeb
    password: rabbitmq@789
    virtual-host: /yeb
server:
  port: 8081

SendApplication.java (springboot项目启动类)

package com.xxxx.send;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SendApplication {
    public static void main(String[] args) {
        SpringApplication.run(SendApplication.class, args);
    }
}

RabbitmqConfig.java

package com.xxxx.send.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    // 声明队列
    @Bean
    public Queue queue(){
        return new Queue("amqp_queue");
    }

    // 声明交换机
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange("amqp_exchange");
    }

    // 绑定交换机和路由
    @Bean
    public Binding binding(){
        return BindingBuilder.bind(queue()).to(topicExchange()).with("*.amqp.#");
    }
}

RabbitMQTest

package com.xxxx.send.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    // 声明队列
    @Bean
    public Queue queue(){
        return new Queue("amqp_queue");
    }

    // 声明交换机
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange("amqp_exchange");
    }

    // 绑定交换机和路由
    @Bean
    public Binding binding(){
        return BindingBuilder.bind(queue()).to(topicExchange()).with("*.amqp.#");
    }
}

3.3编写消费者

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xxxx</groupId>
    <artifactId>amqp-recv</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>com.xxxx</groupId>
        <artifactId>rabbotmq-springAMQP</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
</project>

application.yml

spring:
  rabbitmq:
    host: 172.0.0.1    
    port: 5672
    username: guest
    password: guest
    virtual-host: /
server:
  port: 8082

RecvApplication.java (springboot项目启动类)

package com.xxxx.recv;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RecvApplication {
    public static void main(String[] args) {
        SpringApplication.run(RecvApplication.class, args);
    }
}

RecvInit.java

package com.xxxx.recv;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
// 监听队列
@RabbitListener(queues = "amqp_queue")
public class RecvInit {

    // 监听之后获取消息的处理方法
    @RabbitHandler
    public void testRecv(String message) {
        System.out.println("接收到消息:" + message);
    }
}

参考资料

https://www.bilibili.com/video/BV1Ai4y1P7Tk/?p=254&vd_source=e3d63e6ee036b9116767a15060a371a3

https://www.rabbitmq.com/

图文来源网络,如有侵权请联系删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值