RabbitMQ

1 学习目标

2 简介

总结:
1.是基于AMQP协议;
2.高并发(是一个容量的概念,服务器可以接受的最大任务数量)
3.高性能(是一个速度的概念,单位时间你服务器可以处理的任务数)
4.高可用(是一个持久的概念,单位时间内服务器可以正常工作的时间)
5.强大的社区支持 6.支持插件 7.支持多语言(Python、java、.net、Ruby、JMS、C、PHP、actionScript、XMPP等,支持ajax)

3 安装

由于是用Erlang编写的服务器端,故在安装时要先安装Erlang; 

Erlang的安装:

 第一种方法: 
 (1)可以通过[Erlang下载地址](https://cloudsmith.io/~rabbitmq/repos/rabbitmq-erlang/packages/?q=distribution%3Ael+AND+distribution%3A7 "Erlang下载地址")下载rpm包 
 (2)然后通过yum -y install 下载名 进行安装 
 (3)安装完成后通过 erl 命令检查是否安装成功 
 第二种方法: 
 (1)安装依赖环境 yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel unixODBC-devel 
 (2)下载Erlang 
 wget http://erlang.org/download/otp_src_23.3.tar.gz 
 (3)解压 tar -xvzf otp_src_23.3.tar.gz 
  (4)进入解压后的Erlang目录 cd otp_src_23.3 
  (5) 构建 ./otp_build autoconf 
      如果出现 ./otp_build: line 319: autoconf: command not found ,需要yum install -y autoconf 
  (6)配置安装 ./configure make make install 
  (7)配置环境变量 vim /etc/profile 
  (8)末尾添加 export ERLANG_HOME=/usr/local/lib/erlang export PATH=$PATH:$ERLANG_HOME/bin 
  (9)重新加载环境变量 source /etc/profile 建议用第二种,第一种好像没有配置成功! 

Socat的安装

 (1)下载Socat 
 wget http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-5.el7.lux.x86_64.rpm
  (2)解压安装
  rpm -ivh socat-1.7.3.2-5.el7.lux.x86_64.rpm

RabbitMQ的安装

 第一种方法:
(1)通过[RabbitMQ下载地址](https://github.com/rabbitmq/rabbitmq-server/releases "RabbitMQ下载地址")下载rpm包 
(2)输入命令: yum -y install 下载名 
  如果报错为: --skip-broken 选项来解决该问题 您可以尝试执行:rpm -Va --nofiles --nodigest 解决方案时:可以查看链接的文案[安装RabbitMQ并升级erlang解决Requires: erlang >= 23.2](https://blog.csdn.net/yinjl123456/article/details/117714325 "安装RabbitMQ并升级erlang解决Requires: erlang >= 23.2") 
  第二种方法:
  (1)通过 
  wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.24/rabbitmq-server-generic-unix-3.7.24.tar.xz  下载安装压缩包
  (2)解压压缩包:
  tar -xf rabbitmq-server-generic-unix-3.7.24.tar.xz
  (3)编辑文件: vim /etc/profile
  (4)在文件最后面加入:
  # 在最下面添加
  RABBIT_HOME=/usr/local/rabbitmq
  PATH=$RABBIT_HOME/sbin:$PATH
  export PATH
  (5)更新文件配置: source /etc/profile

RbabitMQ安装检查

输入命令:[root@localhost ~]# systemctl start rabbitmq-server.service #启动 
输入命令:[root@localhost ~]# systemctl status rabbitmq-server.service #查看启动日志

RbabitMQ可视化插件的安装

输入命令:[root@localhost ~]# rabbitmq-plugins enable rabbitmq_management #运行可视化插件 
如果报错为: {:query, :rabbit@centos7, {:badrpc, :timeout}}
通过查看链接的文案[修改主机名](https://blog.csdn.net/u010689849/article/details/115274256 "修改主机名")方法进行解决。

4 RabbitMQ专业术语

5 简单模式队列

在这部分的使用指南中,我们要用java写两个程序;一个是生产者,他发送一个消息,另一个是消费者,他接收消息,并且把消息打印出来。我们将会忽略一些java API的细节,而是将注意力主要放在我们将要做的这件事上,这件事就是发送一个“Hello Word”消息。
在下面的图中,“P”代表生产者,而“C”代表消费者,中间的就是一个Queue,一个消息缓存区。
image

5.1创建项目

image
image

5.2添加依赖

 <!--     rabbitmq依赖-->
 <dependency>
   <groupId>com.rabbitmq</groupId>
   <artifactId>amqp-client</artifactId>
   <version>5.6.0</version>
 </dependency>

5.3代码实现

参考官方连接:https://www.rabbitmq.com/tutorials/tutorial-one-java.html
如需在连接时加入连接参数,代码如下:

    factory.setHost("192.168.10.131");   //连接地址
    factory.setUsername("lh");           //连接用户名
    factory.setVirtualHost("/lh");       //设置文件路径
    factory.setPassword("lh");           //连接用户密码
    factory.setPort(5672);               //连接端口号

6 Work queues-工作模式队列

参考官方连接:https://www.rabbitmq.com/tutorials/tutorial-two-java.html
当生产的数量远远大于消费的数量时,采用工作模式队列。

6.1轮询模式

第一种情况:轮询模式工作,一个消费者一条数据轮着来直到拿完,平均分摊
生产者代码 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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
    try (
	//                连接工厂创建连接
            Connection connection = factory.newConnection();
			//                 创建信道
            Channel channel = connection.createChannel()) {
			//            绑定队列:第一个参数:队列名,第二个参数:是否持久化,
			//            第三个参数:排他队列:该队列仅对首次声明它的连接可见,不同连接之间是看不到其他连接创建的排他队列,连接关闭之后,排他队列也会自动删除,不管持久化是不是true,自动删除是不是为false,不同连接之间的排他队列名是不相同的要不然会报错,同一连接的不同通道是可以同时访问同一个连接创建的排他队列
			//            第四个参数:自动删除:如果该队列没有任何订阅的消费者的话,该队列会自动删除,这种队列适用于临时队列
			//            第五个参数:额外参数
        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);
        }
    }
}}

消费者01代码:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
//工作队列--轮询--消息消费者
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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
	//        连接工厂创建连接
    Connection connection = factory.newConnection();
	//        创建信道
    Channel channel = connection.createChannel();
	//            绑定队列:第一个参数:队列名,第二个参数:是否持久化,
	//            第三个参数:排他队列:该队列仅对首次声明它的连接可见,不同连接之间是看不到其他连接创建的排他队列,连接关闭之后,排他队列也会自动删除,不管持久化是不是true,自动删除是不是为false,不同连接之间的排他队列名是不相同的要不然会报错,同一连接的不同通道是可以同时访问同一个连接创建的排他队列
	//            第四个参数:自动删除:如果该队列没有任何订阅的消费者的话,该队列会自动删除,这种队列适用于临时队列
	//            第五个参数:额外参数
    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(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String message = new String(delivery.getBody(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");
		//            当channel.basicConsume这个方法中的第二个参数值设置为false(手动确认)时,需要加上下面这行代码
		//            这个方法的第二个参数:是否确认多条   false:哪怕收到多条消息,也是一条一条的确认   true:多条确认
        channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
    };
	//        监听队列消费消息  第一个参数:队列名称   第二个参数:自动回值:当我的消费者收到消息后,会告诉队列我收到消息了:false表示手动确认
    channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
}}

消费者02代码:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
//工作队列--轮询--消息消费者
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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
	//        连接工厂创建连接
    Connection connection = factory.newConnection();
	//        创建信道
    Channel channel = connection.createChannel();
	//            绑定队列:第一个参数:队列名,第二个参数:是否持久化,
	//            第三个参数:排他队列:该队列仅对首次声明它的连接可见,不同连接之间是看不到其他连接创建的排他队列,连接关闭之后,排他队列也会自动删除,不管持久化是不是true,自动删除是不是为false,不同连接之间的排他队列名是不相同的要不然会报错,同一连接的不同通道是可以同时访问同一个连接创建的排他队列
	//            第四个参数:自动删除:如果该队列没有任何订阅的消费者的话,该队列会自动删除,这种队列适用于临时队列
	//            第五个参数:额外参数
    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(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String message = new String(delivery.getBody(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");
		//            当channel.basicConsume这个方法中的第二个参数值设置为false(手动确认)时,需要加上下面这行代码
		//            这个方法的第二个参数:是否确认多条   false:哪怕收到多条消息,也是一条一条的确认   true:多条确认
        channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
    };
	//        监听队列消费消息  第一个参数:队列名称   第二个参数:自动回值:当我的消费者收到消息后,会告诉队列我收到消息了:false表示手动确认
    channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
}}

01消费者拿到的数据全是偶数数据,02消费者拿到的数据全是奇数数据。

6.2公平模式---能者多劳

 在消费者类中加入下面两行代码就行:
  int prefetchCount = 1;
  channel.basicQos(prefetchCount);
 加入到建立了信道且队列名之后,即下面这行代码之后: 
 channel.queueDeclare(QUEUE_NAME, false, false, false, null);

7 Publish/Subscribe-消息的发布与订阅模式

参考官方连接:https://www.rabbitmq.com/tutorials/tutorial-three-java.html
当同一个消息需要发给多个消费者时,就需要用到发布与订阅模式又叫广播模式。
生产者类:

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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    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 + "'");
    }
}}

消费者01类:

import com.rabbitmq.client.*;
//发布/订阅--消息消费者
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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
  //        连接工厂创建连接
    Connection connection = factory.newConnection();
  //        创建信道
    Channel channel = connection.createChannel();
    //绑定交换机
    channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
    // 获取队列(排他队列):交换机生成的队列为排他队列
    String queue = channel.queueDeclare().getQueue();
    // 绑定交换机和队列
    channel.queueBind(queue,EXCHANGE_NAME,"");
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

    DeliverCallback deliverCallback = (consumerTag, delivery) -> {
        String message = new String(delivery.getBody(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");
  };
  //        监听队列消费消息  第一个参数:队列名称   第二个参数:自动回值:当我的消费者收到消息后,会告诉队列我收到消息了:false表示手动确认
    channel.basicConsume(queue, true, deliverCallback, consumerTag -> { });
}}

消费者02类:

import com.rabbitmq.client.*;

//发布/订阅--消息消费者
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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
 //        连接工厂创建连接
    Connection connection = factory.newConnection();
  //        创建信道
    Channel channel = connection.createChannel();
    //绑定交换机
    channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
    // 获取队列(排他队列):交换机生成的队列为排他队列
    String queue = channel.queueDeclare().getQueue();
    // 绑定交换机和队列
    channel.queueBind(queue,EXCHANGE_NAME,"");
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

    DeliverCallback deliverCallback = (consumerTag, delivery) -> {
        String message = new String(delivery.getBody(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");
    }; 
	//        监听队列消费消息  第一个参数:队列名称   第二个参数:自动回值:当我的消费者收到消息后,会告诉队列我收到消息了:false表示手动确认
    channel.basicConsume(queue, true, deliverCallback, consumerTag -> { });
}}

运行结果为:两个消费者都收到了生产者生产的“Hello World!”同一个消息。

8 Routing-路由模式队列

当如果你想加入会员制那种,就是普通类型的消息所有消费者都可以接收,但是会员类型的消息只有部分消费者可以收到消息,那就用到路由模式队列。
参考官方实例:https://www.rabbitmq.com/tutorials/tutorial-four-java.html
生产者实现类:

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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    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 = "waining";
			//            发送消息:第一个参数:交换机 第二个参数:队列的名称 第三个队列:对于的额外参数  第四个参数:消息的实体
            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 + "'");
    }
}}

消费者01实现类:

import com.rabbitmq.client.*;
//路由队列--消息消费者
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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
	//        连接工厂创建连接
    Connection connection = factory.newConnection();
	//        创建信道
    Channel channel = connection.createChannel();
    //绑定交换机
    channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
    // 获取队列(排他队列):交换机生成的队列为排他队列
    String queue = channel.queueDeclare().getQueue();
    //路由Key
    String errorRoutingKey = "error";
    // 绑定交换机和队列
    channel.queueBind(queue,EXCHANGE_NAME,errorRoutingKey);
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

    DeliverCallback deliverCallback = (consumerTag, delivery) -> {
        String message = new String(delivery.getBody(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");
		};
		//        监听队列消费消息  第一个参数:队列名称   第二个参数:自动回值:当我的消费者收到消息后,会告诉队列我收到消息了:false表示手动确认
    channel.basicConsume(queue, true, deliverCallback, consumerTag -> { });
}}

消费者02实现类:

 import com.rabbitmq.client.*;
 //路由队列--消息消费者
 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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
	//        连接工厂创建连接
    Connection connection = factory.newConnection();
	//        创建信道
    Channel channel = connection.createChannel();
    //绑定交换机
    channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
    // 获取队列(排他队列):交换机生成的队列为排他队列
    String queue = channel.queueDeclare().getQueue();
    //路由Key
    String infoRoutingKey = "info";
    String errorRoutingKey = "error";
    String warningRoutingKey = "waining";
    // 绑定交换机和队列
    channel.queueBind(queue,EXCHANGE_NAME,infoRoutingKey);
    channel.queueBind(queue,EXCHANGE_NAME,errorRoutingKey);
    channel.queueBind(queue,EXCHANGE_NAME,warningRoutingKey);
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

    DeliverCallback deliverCallback = (consumerTag, delivery) -> {
        String message = new String(delivery.getBody(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");
    };
	//        监听队列消费消息  第一个参数:队列名称   第二个参数:自动回值:当我的消费者收到消息后,会告诉队列我收到消息了:false表示手动确认
    channel.basicConsume(queue, true, deliverCallback, consumerTag -> { });
}
}

运行结果为:消费者01只接收到了普通消息;消费者02接收到了普通、错误、警告消息。

9 Topics-主题模式队列

image
当使用路由队列模式时,如果路由key过多,就繁琐,不好取数据,不方便了,此时就要用到主题模式队列。
参考官方文档实例:https://www.rabbitmq.com/tutorials/tutorial-five-java.html
其中 * 通配符只能匹配一个 例如: .orange. 与 "quick.orange.rabbit" 和 "quick.orange.fox" 匹配,但是不与quick.orange.male.rabbit它匹配,因为后面跟着两个..名
其中 # 通配符匹配零个或者多个 例如: "lazy.#". 与 "lazy.brown.fox" 和"lazy.orange.male.rabbit"和 "lazy" 都可以匹配。

生产者实现代码:

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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    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 + "'");
    }
}}

消费者01实现代码:

import com.rabbitmq.client.*;
//主题队列--消息消费者
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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
    //        连接工厂创建连接
    Connection connection = factory.newConnection();
    //        创建信道
    Channel channel = connection.createChannel();
    //绑定交换机
    channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
    // 获取队列(排他队列):交换机生成的队列为排他队列
    String queue = channel.queueDeclare().getQueue();
    //路由Key
    String RoutingKey = "#.message.#";
    // 绑定交换机和队列
    channel.queueBind(queue,EXCHANGE_NAME,RoutingKey);
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

    DeliverCallback deliverCallback = (consumerTag, delivery) -> {
        String message = new String(delivery.getBody(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");

};
// 监听队列消费消息 第一个参数:队列名称 第二个参数:自动回值:当我的消费者收到消息后,会告诉队列我收到消息了:false表示手动确认
channel.basicConsume(queue, true, deliverCallback, consumerTag -> { });
}}
消费者02实现代码:

import com.rabbitmq.client.*;
//主题队列--消息消费者
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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
    //        连接工厂创建连接
    Connection connection = factory.newConnection();
    //        创建信道
    Channel channel = connection.createChannel();
    //绑定交换机
    channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
    // 获取队列(排他队列):交换机生成的队列为排他队列
    String queue = channel.queueDeclare().getQueue();
    //路由Key
    String RoutingKey = "*.rabbit.*";
    // 绑定交换机和队列
    channel.queueBind(queue,EXCHANGE_NAME,RoutingKey);
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

    DeliverCallback deliverCallback = (consumerTag, delivery) -> {
        String message = new String(delivery.getBody(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");
    };
    //        监听队列消费消息  第一个参数:队列名称   第二个参数:自动回值:当我的消费者收到消息后,会告诉队列我收到消息了:false表示手动确认
    channel.basicConsume(queue, true, deliverCallback, consumerTag -> { });
}}

运行结果为:消费者01接收到的消息为普通、警告信息;消费者02接收道德消息为错误信息。

10 RPC-远程过程调用模式队列

参考官方文档:https://www.rabbitmq.com/tutorials/tutorial-six-java.html
客户端代码:

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.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;
//RPC模式队列--客户端
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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    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 e) {
        e.printStackTrace();
    }
}
// 请求服务端
public String call(String message) throws IOException, InterruptedException {
    //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 BlockingQueue<String> response = new ArrayBlockingQueue<>(1);
    // 接收服务器返回结果
    String ctag = channel.basicConsume(replyQueueName, true, (consumerTag, delivery) -> {
        if (delivery.getProperties().getCorrelationId().equals(corrId)) {
            // 将给定的元素在给定的时间内设置到线程队列中,如果设置成功返回true,否则返回false
            response.offer(new String(delivery.getBody(), "UTF-8"));
        }
    }, consumerTag -> {
    });
    // 从线程队列中获取值,如果线程队列中没有值,线程会一直阻塞,直到线程队列中有值,并且取得该值
    String result = response.take();
    // 从消息队列中丢弃该值
    channel.basicCancel(ctag);
    return result;
}
// 关闭连接
public void close() throws IOException {
    connection.close();
}}

服务端实现类:

  import com.rabbitmq.client.*;
  //RPC模式队列--服务端
  public class RPCServer {
// 队列名称
private static final String RPC_QUEUE_NAME = "rpc_queue";
//计算斐波那契数列
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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);

    try (
            //通过工厂创建连接
            Connection connection = factory.newConnection();
            // 获取通道
            Channel channel = connection.createChannel()) {
            // 声明队列
            channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
            channel.queuePurge(RPC_QUEUE_NAME);
            /*
            * 限制RabbitMQ只发不超过一条的消息给同一个消费者。
            * 当消息处理完毕后,有了反馈,才会进行第二次发送
            * */
            channel.basicQos(1);

            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.toString());
            } finally {
                //将处理结果发送值replyTo队列同时携带correlationId属性
                channel.basicPublish("", delivery.getProperties().getReplyTo(), replyProps, response.getBytes("UTF-8"));
                //手动回执消息
                channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                // RabbitMq consumer worker thread notifies the RPC server owner thread
                synchronized (monitor) {
                    monitor.notify();
                }
            }
        };
        //监听队列
        /*channel.basicConsume中的第二个参数:
        *false:代表手动确认消息
        * true:代表自动确认消息
        **/
        channel.basicConsume(RPC_QUEUE_NAME, false, deliverCallback, (consumerTag -> { }));
        // Wait and be prepared to consume the message from RPC client.
       //线程等待并准备接收来自RPC客户端的消息
        while (true) {
            synchronized (monitor) {
                try {
                    monitor.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}}

11 RabbitMQ消息的事物机制

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

RabbitMQ为我们提供了两种方法:
1.通过AMQP事物机制实现,这也是AMQP协议层面提供的解决方案;
2.通过将channel设置成confirm模式来实现

11.1AMQP事物机制控制

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

send.java:

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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
    Connection connection = null;
    Channel channel = null;
    try  {
        //                连接工厂创建连接
        connection = factory.newConnection();
        //                 创建信道
        channel = connection.createChannel();
        //开启事务
        channel.txSelect();
        //            绑定队列:第一个参数:队列名,第二个参数:是否持久化,
        //            第三个参数:排他队列:该队列仅对首次声明它的连接可见,不同连接之间是看不到其他连接创建的排他队列,连接关闭之后,排他队列也会自动删除,不管持久化是不是true,自动删除是不是为false,不同连接之间的排他队列名是不相同的要不然会报错,同一连接的不同通道是可以同时访问同一个连接创建的排他队列
        //            第四个参数:自动删除:如果该队列没有任何订阅的消费者的话,该队列会自动删除,这种队列适用于临时队列
        //            第五个参数:额外参数
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        String message = "Hello World!";
        //            发送消息:第一个参数:交换机 第二个参数:队列的名称  第三个参数:队列的额外参数  第四个参数:消息的实体
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
        //提交事务
        channel.txCommit();
        System.out.println(" [x] Sent '" + message + "'");
    }catch (Exception e){
       e.printStackTrace();
       //回滚事务
       channel.txRollback();
    }
}}

Recv.java1:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
//事务--消息消费者
public class Recv {
//    定义队列
private final static String QUEUE_NAME = "tx";

public static void main(String[] argv) throws Exception {
//        创建连接工厂
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
    //        连接工厂创建连接
    Connection connection = factory.newConnection();
    //        创建信道
    Channel channel = connection.createChannel();
    //            绑定队列:第一个参数:队列名,第二个参数:是否持久化,
    //            第三个参数:排他队列:该队列仅对首次声明它的连接可见,不同连接之间是看不到其他连接创建的排他队列,连接关闭之后,排他队列也会自动删除,不管持久化是不是true,自动删除是不是为false,不同连接之间的排他队列名是不相同的要不然会报错,同一连接的不同通道是可以同时访问同一个连接创建的排他队列
    //            第四个参数:自动删除:如果该队列没有任何订阅的消费者的话,该队列会自动删除,这种队列适用于临时队列
    //            第五个参数:额外参数
    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(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");
    };
    //        监听队列消费消息
    channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}

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

12 confirm确认模式

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

12.1Confirm确认模式原理

生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么去而信息会将信息写入磁盘之后发出,broker回传给生产者的确认消息中deliver.tag域包含了确认信息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。
confirm模式最大的好处在于它是异步的(AMQP事务机制是同步的,故会降低性能),一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条信息,当信息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理nack消息。
在channel被设置成confirm模式之后,所有被public的后续消息都将被confirm(即ack)或者nack一次。但是没有对消息被confirm的快慢做任何保证,并且同一条消息不会即被confirm又被nack。
注意:两种事物控制形式不能同时开启!

12.2COnfirm确认机制代码实现

实现生产者confirm机制有三种方式:
1.普通confirm模式:没发送一条消息后,调用waitForConfirms()方法,等待服务器端confirm。实际上是一种串行confirm了(同步confirm)。
2.批量confirm模式:没发送一批消息后,调用waitForfirmsorDie()方法,等待服务器端confirm(同步confirm)。
3.异步confirm模式:提供一个回调方法,服务端confirm了一条或者多条消息后Client端会回调这个方法。

12.3普通/批量confirm模式--同步Confirm

生产者实现代码:

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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
    Connection connection = null;
    Channel channel = null;
    try  {
        //                连接工厂创建连接
        connection = factory.newConnection();
        //                 创建信道
        channel = connection.createChannel();
        //开启确认模式
        channel.confirmSelect();
        //            绑定队列:第一个参数:队列名,第二个参数:是否持久化,
        //            第三个参数:排他队列:该队列仅对首次声明它的连接可见,不同连接之间是看不到其他连接创建的排他队列,连接关闭之后,排他队列也会自动删除,不管持久化是不是true,自动删除是不是为false,不同连接之间的排他队列名是不相同的要不然会报错,同一连接的不同通道是可以同时访问同一个连接创建的排他队列
        //            第四个参数:自动删除:如果该队列没有任何订阅的消费者的话,该队列会自动删除,这种队列适用于临时队列
        //            第五个参数:额外参数
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        String message = "Hello World!";
        //假装开启异常情况
        //int i=1/0;
        //            发送消息:第一个参数:交换机 第二个参数:队列的名称  第三个参数:队列的额外参数  第四个参数:消息的实体
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
        // 普通confirm模式
		/*            if(channel.waitForConfirms()){
            System.out.println("消息发送成功");
        }else{
            System.out.println("消息发送失败");
        }*/

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

消费者实现代码:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
//确认模式--同步--消息消费者
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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
    //        连接工厂创建连接
    Connection connection = factory.newConnection();
    //        创建信道
    Channel channel = connection.createChannel();
    //            绑定队列:第一个参数:队列名,第二个参数:是否持久化,
    //            第三个参数:排他队列:该队列仅对首次声明它的连接可见,不同连接之间是看不到其他连接创建的排他队列,连接关闭之后,排他队列也会自动删除,不管持久化是不是true,自动删除是不是为false,不同连接之间的排他队列名是不相同的要不然会报错,同一连接的不同通道是可以同时访问同一个连接创建的排他队列
    //            第四个参数:自动删除:如果该队列没有任何订阅的消费者的话,该队列会自动删除,这种队列适用于临时队列
    //            第五个参数:额外参数
    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(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");
    };
    //        监听队列消费消息
    channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}

12.4异步confirm

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

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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    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();
        //            绑定队列:第一个参数:队列名,第二个参数:是否持久化,
        //            第三个参数:排他队列:该队列仅对首次声明它的连接可见,不同连接之间是看不到其他连接创建的排他队列,连接关闭之后,排他队列也会自动删除,不管持久化是不是true,自动删除是不是为false,不同连接之间的排他队列名是不相同的要不然会报错,同一连接的不同通道是可以同时访问同一个连接创建的排他队列
        //            第四个参数:自动删除:如果该队列没有任何订阅的消费者的话,该队列会自动删除,这种队列适用于临时队列
        //            第五个参数:额外参数
        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);
                    confirmSet.remove(deliveryTag);
                }
            }
            // 未确认
            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                if(multiple){
                    System.out.println("handleAck--failed-->multiple-->" + deliveryTag);
                    // 清除前deliveryTag 项标识id
                    confirmSet.headSet(deliveryTag+1L).clear();
                }else{
                    System.out.println("handleAck--failed-->single"+deliveryTag);
                    confirmSet.remove(deliveryTag);
                }
            }
        });
        //循环发送消息演示消息确认
        while(true){
            //创建消息
            String message = "Hello Word!";
            //获取unconfirm的消息序号deliveryTag
            Long seqNo = channel.getNextPublishSeqNo();
            //            发送消息:第一个参数:交换机 第二个参数:队列的名称  第三个参数:队列的额外参数  第四个参数:消息的实体
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            //将消息序号deliveryTag添加至SortedSet
            confirmSet.add(seqNo);
        }
    }catch (Exception e){
       e.printStackTrace();
    } finally {
        try {

            //关闭通道
            if (null != channel && channel.isOpen()) {
                channel.close();
            }
            //关闭连接
            if (null != connection && connection.isOpen()) {
                connection.close();
            }
        }catch (TimeoutException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
}

消费者实现类:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
//确认模式--异步--消息消费者
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("192.168.10.131");
    factory.setUsername("lh");
    factory.setVirtualHost("/lh");
    factory.setPassword("lh");
    factory.setPort(5672);
    //        连接工厂创建连接
    Connection connection = factory.newConnection();
    //        创建信道
    Channel channel = connection.createChannel();
    //            绑定队列:第一个参数:队列名,第二个参数:是否持久化,
    //            第三个参数:排他队列:该队列仅对首次声明它的连接可见,不同连接之间是看不到其他连接创建的排他队列,连接关闭之后,排他队列也会自动删除,不管持久化是不是true,自动删除是不是为false,不同连接之间的排他队列名是不相同的要不然会报错,同一连接的不同通道是可以同时访问同一个连接创建的排他队列
    //            第四个参数:自动删除:如果该队列没有任何订阅的消费者的话,该队列会自动删除,这种队列适用于临时队列
    //            第五个参数:额外参数
    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(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");
    };
    //        监听队列消费消息
    channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}

13 SpringAMQP-Spring集成RabbitMQ

posted @ 2021-08-23 16:19  卢-知识海洋  阅读(95)  评论(0)    收藏  举报