目录
- 一、Rabbitmq说明
- 二、模式解读
- 三、难点解读
-
- 1、try—with—resource语法为什么可以在生产者端使用,而不能在消费者端使用
- 2、连接和通道的关系
- 3、队列持久化的作用
- 4、队列不应该自动删除的原因
- 5、为什么在生产者和消费者代码中都声明队列?
- 6、默认交换机会和所有队列建立默认连接吗?
- 7、生产者端如何进行消息持久化
- 8、消费者端自动应答消息方式、手动应答消息的两种方式
- 9、消费者端设置通道中最大消息数量(RabbitMQ服务端往通道中发送的消息数量)的作用
- 10、什么情况下消息会重新入列,也就是什么时候不会接收到消费者发送过来的ack消息确认?
- 11、消费者始终不确认消息被消费,阐述RabbitMQ服务端的处理策略
- 12、生产者可以直接指定接收消息的队列吗?
- 13、为什么生产者端发送消息时一定要指定路由?
- 14、消费者端接收到了消息,但是RabbitMQ控制台中的消息依然存在,原因是什么
- 15、交换机的几种类型
- 16、我们把消息发送到交换机上,但是没有任何队列和交换机相连,那么消息将何去何从?
- 17、创建随机名称的临时队列有什意义?
- 18、basicQos()方法如何使用
- 19、basicReject()和basicNack()方法的区别
一、Rabbitmq说明
1、官网
首页: https://www.rabbitmq.com
示例: https://www.rabbitmq.com/getstarted.html
2、下载地址
https://www.rabbitmq.com/download.html
3、概念
- RabbitMQ是一个部署最广泛的开源消息中间件(消息代理)——来自官网首页
- 无论在本地还是在云端,RabbitMQ是一个轻量级、容易部署的,支持多种消息协议,可以部署在分布式系统之中,满足大规模、高可用的要求
- 异步消息:支持多种消息协议,消息队列、消息确认、灵活的队列路由、多种消息类型
- 开发者体验:支持多种部署方式,比如Docker等;拥有多种语言客户端,不受开发语言的限制,可以进行跨语言系统之间的消息传递
- 分布式部署:可以部署RabbitMQ集群来获取高可用性、高吞吐量的优势
- 认证授权:支持身份认证,权限授予
- 管理监控:可以使用命令行或者UI界面来管理或者监控RabbitMQ
- 支持多协议:RabbitMQ支持多种协议,其中AMQP(Advanced Message Queuing Protocol:即高级消息队列协议)只是其中一种,AMQP它是一个开放的、通用的消息传递协议。
4、RabbitMQ比喻、行话
4.1、RabbitMQ的类比
RabbitMQ是一个消息中间件,用来接收和转发消息。我们来类比一下,你可以把它想象成一个邮局,当你把一封邮件放在一个邮箱之中,你确信这封邮件最终能发送到收件人手中。在这个类比中,RabbitMQ是一个邮箱、一个邮局、一个快递员
4.2、RabbitMQ中的行话
- 生产者:生产意味着发送,发送消息的程序就是生产者
- 队列:队列相当于类比中的邮箱,消息在RabbitMQ和应用程序之间流通,不过中间存储在队列中,队列相当于一个消息缓冲区
- 消费者:消费者是一个等待接收消息的程序
三者关系:生产者、队列、消费者不用在同一台主机上,当然也可以在同一台主机上
二、模式解读
1、说明
1.1、快速开始官方Demo
https://www.rabbitmq.com/getstarted.html
1.2、SpringBoot版本
以下Demo案例对应RabbitMQ学习文档(入门篇(Demo使用SpringBoot编写))中的Demo代码
2、Hello World!(简单队列)
2.1、画图说明
注意:P是生产者,中间红色矩形是队列,C是消费者
2.2、概念分析和使用场景
概念分析:
最简单的消息发送接收模式;一个生产者,一个消费者来进行消息产生和消费
使用场景:
用户在使用我们系统时将产生多种用户行为(浏览、点赞、收藏、转发、评论),然后有一家公司需要对用户的行为进行分析,这种模式就适合这种情况
2.3、示例代码
2.3.1、pom依赖
<!--version仅供参考,依需设置-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.14.2</version>
</dependency>
官网说明:(The RabbitMQ Java client is also in the central Maven repository, with the groupId com.rabbitmq and the artifactId amqp-client.)
解释:RabbitMQ的Java客户端在Maven中央仓库中可以找到,其中groupId是com.rabbitmq,artifactId是amqb-clien,version可以去Maven仓库中查找
2.3.2、生产者代码
public class Provider {
// 队列名称
private static final String QUEUE_NAME = "hello_wold";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
/**
* 使用 try-with-resource 语法,try中代码块执行结束之后将会关闭connection和channel,
* 原因是:Connection和Channel都实现了AutoCloseable接口,所以close()方法会主动执行
*/
try (
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
) {
/**
* 创建队列
* 说明:一保证消息发送之前,队列已经存在了
* 参数1:队列名称
* 参数2:是否持久化
* 队列持久化之后,即使RabbitMQ重启,队列依然存在
* 参数3:是否排他
* 设置排他之后,仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
* 参数4:附加参数
* 比如可以设置死信队列等
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 消息内容
String message = "Hello World!";
/**
* 发送消息到队列
* 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
* 参数1:交换机名称
* ""是默认交换机的名称,其中默认交换机隐式绑定到每个队列,路由密钥等于队列名称,无法删除默认交换机
* 参数2:路由名称
* 由于默认交换机和队列之间的路由是队列名称,所以消息路由是队列名称
* 参数3:其他消息属性
* MessageProperties.PERSISTENT_TEXT_PLAIN:消息持久化,即消息将会存储到磁盘中,只要消息队列也是持久化的,即使RabbitMQ重启,消息依然存在于队列中
* 参数4,消息内容
* 消息内容是字节数组,所以我们需要使用getBytes()方法
*/
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3.2、消费者代码
public class Consumer {
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
// 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
try {
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
/**
* 创建队列
* 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
* 参数1:队列名称
* 参数2:是否持久化
* 队列持久化之后,即使RabbitMQ重启,队列依然存在
* 参数3:是否排他
* 设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
* 参数4:附加参数
* 比如可以设置死信队列等
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println(str);
};
/**
* 消费者异步监听队列消息
* 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
* 参数1:队列名称;
* 指定消费者连接的队列
* 参数2:是否自动确认;
* 如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
* 如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
* 参数3:消息传递时的回调对象
* 参数4:消费者被取消时的回调,暂时没有看懂啥意思
*/
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
3、Work queues(工作队列 / 任务队列)
3.1、画图说明
注意:P是生产者,中间红色矩形是队列,C是消费者
3.2、概念分析和使用场景
概念分析:
采用消费者竞争模式,用于耗时任务,符合能者多劳原则,在多个消费者之间分配任务,常用于执行资源密集型任务,也就是说消费比较多,消费比较耗时的情况
使用场景:
订单支付之后,需要物流系统进行处理,由于消息比较多,并且处理起来比较复杂,因此单个物流系统处理处理效率太低,所以我们需要使用多个服务器中的物流系统进行消息处理,这种情况就很适合使用
3.3、示例代码
3.3.1、pom依赖
<!--version仅供参考,依需设置-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.14.2</version>
</dependency>
官网说明:(The RabbitMQ Java client is also in the central Maven repository, with the groupId com.rabbitmq and the artifactId amqp-client.)
解释:RabbitMQ的Java客户端在Maven中央仓库中可以找到,其中groupId是com.rabbitmq,artifactId是amqb-clien,version可以去Maven仓库中查找
3.3.2、生产者代码
public class Provider {
// 队列名称
private static final String QUEUE_NAME = "work_queues";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
/**
* 使用 try-with-resource 语法,try中代码块执行结束之后将会关闭connection和channel,
* 原因是:Connection和Channel都实现了AutoCloseable接口,所以close()方法会主动执行
*/
try (
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
) {
/**
* 创建队列
* 说明:一保证消息发送之前,队列已经存在了
* 参数1:队列名称
* 参数2:是否持久化
* 队列持久化之后,即使RabbitMQ重启,队列依然存在
* 参数3:是否排他
* 设置排他之后,仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
* 参数4:附加参数
* 比如可以设置死信队列等
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 发送多个消息
for (int i = 0; i < 10; i++) {
// 消息内容
String message = String.format("Hello,我是%s号消息", i);
/**
* 发送消息到队列
* 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
* 参数1:交换机名称
* ""是默认交换机的名称,其中默认交换机隐式绑定到每个队列,路由密钥等于队列名称,无法删除默认交换机
* 参数2:路由名称
* 由于默认交换机和队列之间的路由是队列名称,所以消息路由是队列名称
* 参数3:其他消息属性
* MessageProperties.PERSISTENT_TEXT_PLAIN:消息持久化,即消息将会存储到磁盘中,只要消息队列也是持久化的,即使RabbitMQ重启,消息依然存在于队列中
* 参数4,消息内容
* 消息内容是字节数组,所以我们需要使用getBytes()方法
*/
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.3.3、消费者1代码
public class Consumer1 {
private static final String QUEUE_NAME = "work_queues";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
// 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
try {
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
// 通道中只能存在一条未确认的消息,在消费者进行消息确认之前,不要给消费者发送新消息,符合能者多劳思想
channel.basicQos(1);
/**
* 创建队列
* 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
* 参数1:队列名称
* 参数2:是否持久化
* 队列持久化之后,即使RabbitMQ重启,队列依然存在
* 参数3:是否排他
* 设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
* 参数4:附加参数
* 比如可以设置死信队列等
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new String(message.getBody(), StandardCharsets.UTF_8);
// 模拟消息处理过程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Consumer1接收到消息,消息内容:" + str);
/**
* 手动确认消息
* 参数1:用来确认消息的发送标签
* 参数2:是否确认当前通道中的所有消息
* false(建议):只确认当前标签对应的消息
* true:确认当前通道中的所有未确认消息,这样可能造成部分正在被消费者处理的消息被确认,如果这些消息处理失败了,那么这些消息就丢失了
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
/**
* 消费者异步监听队列消息
* 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
* 参数1:队列名称;
* 指定消费者连接的队列
* 参数2:是否自动确认;
* 如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
* 如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
* 参数3:消息传递时的回调对象
* 参数4:消费者被取消时的回调,暂时没有看懂啥意思
*/
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.3.4、消费者1代码
public class Consumer2 {
private static final String QUEUE_NAME = "work_queues";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
// 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
try {
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
// 通道中只能存在一条未确认的消息,在消费者进行消息确认之前,不要给消费者发送新消息,符合能者多劳思想
channel.basicQos(1);
/**
* 创建队列
* 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
* 参数1:队列名称
* 参数2:是否持久化
* 队列持久化之后,即使RabbitMQ重启,队列依然存在
* 参数3:是否排他
* 设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
* 参数4:附加参数
* 比如可以设置死信队列等
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new String(message.getBody(), StandardCharsets.UTF_8);
// 模拟消息处理过程
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Consumer2接收到消息,消息内容:" + str);
/**
* 手动确认消息
* 参数1:用来确认消息的发送标签
* 参数2:是否确认当前通道中的所有消息
* false(建议):只确认当前标签对应的消息
* true:确认当前通道中的所有未确认消息,这样可能造成部分正在被消费者处理的消息被确认,如果这些消息处理失败了,那么这些消息就丢失了
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
/**
* 消费者异步监听队列消息
* 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
* 参数1:队列名称;
* 指定消费者连接的队列
* 参数2:是否自动确认;
* 如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
* 如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
* 参数3:消息传递时的回调对象
* 参数4:消费者被取消时的回调,暂时没有看懂啥意思
*/
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
4、Publish/Subscribe(发布订阅模式)
4.1、画图说明
注意:P是生产者,X是交换机,中间红色矩形是队列,CN是消费者
4.2、概念分析和使用场景
概念分析:
交换机采用fanout扇出模式,可以将消息发送给所有与之相连的队列,并且和消息路由键、队列和交换机
使用场景:
用户在使用我们系统时将产生多种用户行为(浏览、点赞、收藏、转发、评论),然后有两家公司都需要对用户的行为进行分析,这种模式就适合这种情况
4.3、示例代码
4.3.1、pom依赖
<!--version仅供参考,依需设置-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.14.2</version>
</dependency>
官网说明:(The RabbitMQ Java client is also in the central Maven repository, with the groupId com.rabbitmq and the artifactId amqp-client.)
解释:RabbitMQ的Java客户端在Maven中央仓库中可以找到,其中groupId是com.rabbitmq,artifactId是amqb-clien,version可以去Maven仓库中查找
4.3.2、生产者代码
public class Provider {
// 交换机名称
private static final String EXCHANGE_NAME = "public_subscribe";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
/**
* 使用 try-with-resource 语法,try中代码块执行结束之后将会关闭connection和channel,
* 原因是:Connection和Channel都实现了AutoCloseable接口,所以close()方法会主动执行
*/
try (
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
) {
/**
* 创建交换机
* 参数1:交换机名称
* 参数2:交换机类型;fanout代表扇出模式,这种模式可以将消息发送到和该交换机绑定的所有队列
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
// 消息内容
String message = "Hello World!";
/**
* 发送消息到队列
* 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
* 参数1:交换机名称
* EXCHANGE_NAME是交换机名称
* 参数2:路由名称
* 当前交换机的类型是fanout,所以该交换机会将消息发送给所有连接到该交换机上的队列,因此路由将被忽略,所以写成空串即可
* 参数3:其他消息属性,可以设置消息持久化,目前没有设置
* 参数4,消息内容
* 消息内容是字节数组,所以我们需要使用getBytes()方法
*/
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.3.3、消费者1代码
public class Consumer1 {
// 交换机名称
private static final String EXCHANGE_NAME = "public_subscribe";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
// 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
try {
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
/**
* 创建交换机
* 参数1:交换机名称
* 参数2:交换机类型;fanout代表扇出模式,这种模式可以将消息发送到和该交换机绑定的所有队列
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
/**
* 创建临时队列
* 作用: 创建一个具有非持久、独占、自动删除的队列
* 使用情况:对于旧的消息,我们并不关心,我们只关心当前的消息,
* 所以消费者每次连接到RabbitMQ时,都会创建一个新的随机名称队列,
* 正如上面所说,这个队列是一个具有非持久、独占、自动删除的队列,
* 一旦我们断开消费者的连接,队列就会被自动删除
*/
String queueName = channel.queueDeclare().getQueue();
/**
* 将交换机和队列绑定
* 参数1:队列名称
* 参数2:交换机名称
* 参数3:交换机和队列绑定的路由,由于本次交换机使用fanout扇出模式,只要我们和交换机绑定之后,交换机接收到的所有消息都会发送给队列,和路由键无关
*/
channel.queueBind(queueName, EXCHANGE_NAME, "");
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println(str);
};
/**
* 消费者异步监听队列消息
* 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
* 参数1:队列名称;
* 指定消费者连接的队列
* 参数2:是否自动确认;
* 如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
* 如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
* 参数3:消息传递时的回调对象
* 参数4:消费者被取消时的回调,暂时没有看懂啥意思
*/
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.3.4、消费者2代码
public class Consumer2 {
// 交换机名称
private static final String EXCHANGE_NAME = "public_subscribe";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
// 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
try {
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
/**
* 创建交换机
* 参数1:交换机名称
* 参数2:交换机类型;fanout代表扇出模式,这种模式可以将消息发送到和该交换机绑定的所有队列
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
/**
* 创建临时队列
* 作用: 创建一个具有非持久、独占、自动删除的队列
* 使用情况:对于旧的消息,我们并不关心,我们只关心当前的消息,
* 所以消费者每次连接到RabbitMQ时,都会创建一个新的随机名称队列,
* 正如上面所说,这个队列是一个具有非持久、独占、自动删除的队列,
* 一旦我们断开消费者的连接,队列就会被自动删除
*/
String queueName = channel.queueDeclare().getQueue();
/**
* 将交换机和队列绑定
* 参数1:队列名称
* 参数2:交换机名称
* 参数3:交换机和队列绑定的路由,由于本次交换机使用fanout扇出模式,只要我们和交换机绑定之后,交换机接收到的所有消息都会发送给队列,和路由键无关
*/
channel.queueBind(queueName, EXCHANGE_NAME, "");
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println(str);
};
/**
* 消费者异步监听队列消息
* 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
* 参数1:队列名称;
* 指定消费者连接的队列
* 参数2:是否自动确认;
* 如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
* 如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
* 参数3:消息传递时的回调对象
* 参数4:消费者被取消时的回调,暂时没有看懂啥意思
*/
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
5、Routing(路由模式)
5.1、画图说明
注意:P是生产者,X是交换机,QN是队列,CN是消费者
5.2、概念分析和使用场景
概念分析:
交换机采用direct模式,可以根据消息路由键进行有选择性的接收消息。首先一条消息有一个路由键,然后交换机和队列也是通过路由键进行绑定,当两个路由键完全相等的时候,交换机就会把消息发送给这个队列,其中队列和交换机可以使用多个路由键进行绑定,所以可以通过消息路由键来控制消息将会发送到哪个队列
使用场景:
日志存在多种等级,不同的等级处理方式不同,其中error级别的消息不仅要存储到磁盘,还需要在控制台展示,而info、warn等级别的消息只需要在控制台展示就可以了,这就可以使用路由模式
5.3、示例代码
5.3.1、pom依赖
<!--version仅供参考,依需设置-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.14.2</version>
</dependency>
官网说明:(The RabbitMQ Java client is also in the central Maven repository, with the groupId com.rabbitmq and the artifactId amqp-client.)
解释:RabbitMQ的Java客户端在Maven中央仓库中可以找到,其中groupId是com.rabbitmq,artifactId是amqb-clien,version可以去Maven仓库中查找
5.3.2、生产者代码
public class Provider {
// 交换机名称
private static final String EXCHANGE_NAME = "routing";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
/**
* 使用 try-with-resource 语法,try中代码块执行结束之后将会关闭connection和channel,
* 原因是:Connection和Channel都实现了AutoCloseable接口,所以close()方法会主动执行
*/
try (
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
) {
/**
* 创建交换机
* 参数1:交换机名称
* 参数2:交换机类型;direct代表直接模式,这种模式根据消息的路由键进行消息转发,其中消息携带路由键,而队列和交换机也通过路由键进行绑定,当两个路由键完全一致的时候,交换机就会把消息发送给这个队列
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 消息1内容
String message = "error message";
/**
* 发送消息到队列
* 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
* 参数1:交换机名称
* EXCHANGE_NAME是交换机名称
* 参数2:路由名称
* 参数3:其他消息属性,可以设置消息持久化,目前没有设置
* 参数4,消息内容
* 消息内容是字节数组,所以我们需要使用getBytes()方法
*/
channel.basicPublish(EXCHANGE_NAME, "error", null, message.getBytes(StandardCharsets.UTF_8));
// 消息2内容
message = "info message";
channel.basicPublish(EXCHANGE_NAME, "info", null, message.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.3.3、消费者1代码
public class Consumer1 {
// 交换机名称
private static final String EXCHANGE_NAME = "routing";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
// 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
try {
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
/**
* 创建交换机
* 参数1:交换机名称
* 参数2:交换机类型;direct代表直接模式,这种模式根据消息的路由键进行消息转发,其中消息携带路由键,而队列和交换机也通过路由键进行绑定,当两个路由键完全一致的时候,交换机就会把消息发送给这个队列
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
/**
* 创建临时队列
* 作用: 创建一个具有非持久、独占、自动删除的队列
* 使用情况:对于旧的消息,我们并不关心,我们只关心当前的消息,
* 所以消费者每次连接到RabbitMQ时,都会创建一个新的随机名称队列,
* 正如上面所说,这个队列是一个具有非持久、独占、自动删除的队列,
* 一旦我们断开消费者的连接,队列就会被自动删除
*/
String queueName = channel.queueDeclare().getQueue();
/**
* 将交换机和队列绑定,可以接受路由键为error或者info的消息
* 参数1:队列名称
* 参数2:交换机名称
* 参数3:交换机和队列绑定的路由键
*/
channel.queueBind(queueName, EXCHANGE_NAME, "error");
channel.queueBind(queueName, EXCHANGE_NAME, "info");
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println(str);
};
/**
* 消费者异步监听队列消息
* 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
* 参数1:队列名称;
* 指定消费者连接的队列
* 参数2:是否自动确认;
* 如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
* 如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
* 参数3:消息传递时的回调对象
* 参数4:消费者被取消时的回调,暂时没有看懂啥意思
*/
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.3.4、消费者2代码
public class Consumer2 {
// 交换机名称
private static final String EXCHANGE_NAME = "routing";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
// 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
try {
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
/**
* 创建交换机
* 参数1:交换机名称
* 参数2:交换机类型;direct代表直接模式,这种模式根据消息的路由键进行消息转发,其中消息携带路由键,而队列和交换机也通过路由键进行绑定,当两个路由键完全一致的时候,交换机就会把消息发送给这个队列
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
/**
* 创建临时队列
* 作用: 创建一个具有非持久、独占、自动删除的队列
* 使用情况:对于旧的消息,我们并不关心,我们只关心当前的消息,
* 所以消费者每次连接到RabbitMQ时,都会创建一个新的随机名称队列,
* 正如上面所说,这个队列是一个具有非持久、独占、自动删除的队列,
* 一旦我们断开消费者的连接,队列就会被自动删除
*/
String queueName = channel.queueDeclare().getQueue();
/**
* 将交换机和队列绑定,只能接受消息路由键为error的消息
* 参数1:队列名称
* 参数2:交换机名称
* 参数3:交换机和队列绑定的路由键
*/
channel.queueBind(queueName, EXCHANGE_NAME, "error");
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println(str);
};
/**
* 消费者异步监听队列消息
* 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
* 参数1:队列名称;
* 指定消费者连接的队列
* 参数2:是否自动确认;
* 如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
* 如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
* 参数3:消息传递时的回调对象
* 参数4:消费者被取消时的回调,暂时没有看懂啥意思
*/
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
6、Topics(话题模式)
6.1、画图说明
注意:P是生产者,X是交换机,QN是队列,CN是消费者
6.2、概念分析和使用场景
概念分析:
使用场景:
6.3、示例代码
6.3.1、pom依赖
<!--version仅供参考,依需设置-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.14.2</version>
</dependency>
官网说明:(The RabbitMQ Java client is also in the central Maven repository, with the groupId com.rabbitmq and the artifactId amqp-client.)
解释:RabbitMQ的Java客户端在Maven中央仓库中可以找到,其中groupId是com.rabbitmq,artifactId是amqb-clien,version可以去Maven仓库中查找
6.3.2、生产者代码
public class Provider {
// 交换机名称
private static final String EXCHANGE_NAME = "topics";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
/**
* 使用 try-with-resource 语法,try中代码块执行结束之后将会关闭connection和channel,
* 原因是:Connection和Channel都实现了AutoCloseable接口,所以close()方法会主动执行
*/
try (
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
) {
/**
* 创建交换机
* 参数1:交换机名称
* 参数2:交换机类型;topic代表主题模式,这种模式代表队列和交换机绑定的路由键中可以包含特殊字符,比如*(代替一个单词)和#(代替零个或多个单词)
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 消息1内容
String message = "快速的橘色兔子";
/**
* 发送消息到队列
* 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
* 参数1:交换机名称
* EXCHANGE_NAME是交换机名称
* 参数2:路由名称,交换机为topic模式情况下要求路由键必须是单词列表,并且以.分隔
* 参数3:其他消息属性,可以设置消息持久化,目前没有设置
* 参数4,消息内容
* 消息内容是字节数组,所以我们需要使用getBytes()方法
*/
channel.basicPublish(EXCHANGE_NAME, "quick.orange.rabbit", null, message.getBytes(StandardCharsets.UTF_8));
// 消息2内容
message = "快速的褐色狐狸";
channel.basicPublish(EXCHANGE_NAME, "quick.brown.fox", null, message.getBytes(StandardCharsets.UTF_8));
// 消息3内容
message = "慢吞吞的黑色熊猫";
channel.basicPublish(EXCHANGE_NAME, "lazy.black.panda", null, message.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}
}
}
6.3.3、消费者1代码
public class Consumer1 {
// 交换机名称
private static final String EXCHANGE_NAME = "topics";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
// 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
try {
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
/**
* 创建交换机
* 参数1:交换机名称
* 参数2:交换机类型;topic代表主题模式,这种模式代表队列和交换机绑定的路由键中可以包含特殊字符,比如*(代替一个单词)和#(代替零个或多个单词)
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
/**
* 创建临时队列
* 作用: 创建一个具有非持久、独占、自动删除的队列
* 使用情况:对于旧的消息,我们并不关心,我们只关心当前的消息,
* 所以消费者每次连接到RabbitMQ时,都会创建一个新的随机名称队列,
* 正如上面所说,这个队列是一个具有非持久、独占、自动删除的队列,
* 一旦我们断开消费者的连接,队列就会被自动删除
*/
String queueName = channel.queueDeclare().getQueue();
/**
* 将交换机和队列绑定,可以接受路由键为error或者info的消息
* 参数1:队列名称
* 参数2:交换机名称
* 参数3:交换机和队列绑定的路由键
*/
channel.queueBind(queueName, EXCHANGE_NAME, "quick.*.*");
channel.queueBind(queueName, EXCHANGE_NAME, "lazy.#");
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println(str);
};
/**
* 消费者异步监听队列消息
* 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
* 参数1:队列名称;
* 指定消费者连接的队列
* 参数2:是否自动确认;
* 如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
* 如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
* 参数3:消息传递时的回调对象
* 参数4:消费者被取消时的回调,暂时没有看懂啥意思
*/
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
6.3.4、消费者2代码
public class Consumer2 {
// 交换机名称
private static final String EXCHANGE_NAME = "topics";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost("localhost");
// 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
try {
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
/**
* 创建交换机
* 参数1:交换机名称
* 参数2:交换机类型;topic代表主题模式,这种模式代表队列和交换机绑定的路由键中可以包含特殊字符,比如*(代替一个单词)和#(代替零个或多个单词)
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
/**
* 创建临时队列
* 作用: 创建一个具有非持久、独占、自动删除的队列
* 使用情况:对于旧的消息,我们并不关心,我们只关心当前的消息,
* 所以消费者每次连接到RabbitMQ时,都会创建一个新的随机名称队列,
* 正如上面所说,这个队列是一个具有非持久、独占、自动删除的队列,
* 一旦我们断开消费者的连接,队列就会被自动删除
*/
String queueName = channel.queueDeclare().getQueue();
/**
* 将交换机和队列绑定,只能接受消息路由键为error的消息
* 参数1:队列名称
* 参数2:交换机名称
* 参数3:交换机和队列绑定的路由键
*/
channel.queueBind(queueName, EXCHANGE_NAME, "*.*.panda");
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println(str);
};
/**
* 消费者异步监听队列消息
* 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
* 参数1:队列名称;
* 指定消费者连接的队列
* 参数2:是否自动确认;
* 如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
* 如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
* 参数3:消息传递时的回调对象
* 参数4:消费者被取消时的回调,暂时没有看懂啥意思
*/
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
三、难点解读
1、try—with—resource语法为什么可以在生产者端使用,而不能在消费者端使用
生产者: try中代码执行完毕之后,自动关闭通道,避免在finally中写关闭通道的冗余代码;原因:Channel接口继承了AutoCloseable接口,所以try{}中代码执行完毕,channel.close()方法将会自动调用进行通道关闭
消费者: 消费者需要持续性异步监听通道中的消息,所以不能关闭通道,对应连接来说,连接可以复用,所以不用关闭,这对生产者也是一样的
2、连接和通道的关系
一个连接中可以有多个通道,通道用于消息发送或者接收,所以我们可以复用连接来节省资源
3、队列持久化的作用
队列持久化之后,即使RabbitMQ重启,队列依然存在,这仅仅是队列持久化,和消息持久化无关
4、队列不应该自动删除的原因
队列的创建和销毁都是需要耗费系统资源的,所以最好在创建之后就不在销毁,另外队列被删除之后,消费者也会报错,所以我们需要在创建队列的时候设置队列不自动删除,代码如下:
如果设置为false,那么在队列中没有消息的时候,队列将会被自动删除,这种情况我们是不允许出现的
5、为什么在生产者和消费者代码中都声明队列?
生产者: 消息发送之前队列一定要存在
生产者: 只有连接到对应队列才可以接收消息
建议: 生产者和消费者两处声明的队列代码一致,毕竟队列一旦创建结束就不会再次创建了,这样可以出现问题时不被该问题困扰
6、默认交换机会和所有队列建立默认连接吗?
在RabbitMQ控制台可以找到如下内容:
The default exchange is implicitly bound to every queue, with a routing key equal to the queue name. It is not possible to explicitly bind to, or unbind from the default exchange. It also cannot be deleted.
翻译过来的意思如下:
默认交换机隐式绑定到每个队列,路由密钥等于队列名称。无法显式绑定到默认exchange或从默认exchange解除绑定。它也不能被删除。
这句话的位置如下:
可以看出默认交换机的类型是direct
如果想把消息发送到默认交换机上,在生产者端用代码表示如下:
// 参数1:默认交换机名称
// 参数2::路由键名称,由于队列和默认交换机的绑定路由键是队列名称,所以我们发送本条消息的路由键就应该是队列名称,这样才能让消息从默认交换机到达队列
// 参数3:消息持久化参数
// 参数4:消息字节数组
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
具体流程如下:
如果我们想把消息发送到默认交换机上,然后让默认交换机把消息发送给队列,那么发送消息的路由键一定要和队列绑定交换机时的路由键一致,其中队列和默认交换机绑定的路由键就是队列名称,所以我们需要在发送者端设置消息路由键是队列名称
7、生产者端如何进行消息持久化
消息持久化的前提是队列持久化,创建队列代码如下:
解释:只有队列是持久化的,那么当RabbitMQ重启的时候,队列才会存在,而消息和队列绑定,只有队列存在的前提下,消息才有存在的可能性
需要在发送消息的时候进行持久化,消息持久化配置如下:
解释:这行代码是写在发送者端的,用来进行消息发送,消息发送到默认交换机,默认交换机把消息存储在队列中,这个消息是被持久化到磁盘中的
关于消息持久性的注意事项
将消息标记为持久性并不能完全保证消息不会丢失。虽然它告诉 RabbitMQ 将消息保存到磁盘,但是当 RabbitMQ 接受消息并且还没有保存它时,仍然有很短的时间窗口。此外,RabbitMQ 不会对每条消息都执行fsync(2) ——它可能只是保存到缓存中而不是真正写入磁盘。持久性保证并不强,但对于我们简单的任务队列来说已经绰绰有余了。如果您需要更强的保证,那么您可以使用 发布者确认
8、消费者端自动应答消息方式、手动应答消息的两种方式
8.1、自动应答消息
解释:当消费者接收到消息之后,直接会向RabbitMQ服务端发送应答消息,然后RabbitMQ服务端就会进行消息删除,假设在消息处理过程中出现问题,消息也不会进行重新分配,毕竟消息已经被删除了,代码如下:
8.2、手动应答消息之单条应答(推荐)
默认情况下一个通道中可以存在多条消息,当消费者端将该条消息处理完毕,我们可以进行该消息的手动应答,代码如下:
其中message.getEnvelope().getDeliveryTag()
是当前消息的标签,用于RabbitMQ服务端来确认消息是哪一个
8.3、手动应答消息之批量应答(不推荐)
上面提到默认情况下一个通道中可以存在多条消息,如果我们进行批量消息确认之后,当前通道中正在处理的消息出现了问题,那消息已经被确认了,因此消息不会在此分发给其他消费者进行处理,所以有可能造成消息丢失,虽然上面“手动应答消息之单条应答”会进行多次消息确认,但是这种资源浪费保证了消息可能完全消费,这是值得的
9、消费者端设置通道中最大消息数量(RabbitMQ服务端往通道中发送的消息数量)的作用
在Work Queues
模式下,将会有多个消费者来进行消息消费,但是消费者的能力不同,另外消息处理起来的难易程度也不同,所以我们设置RabbitMQ服务端往通道中发送的最大消息数量,以保证消息能够被合理的消费,这符合能者多劳、公平调度的思想,能更好的使用各个消费者的能力,代码如下:
当然这种设置也不是必须的,毕竟我们可以多接受几个消息,然后多线程来处理它们,之后单个确定消息被消费,这样可以充分利用消费者端的服务器性能
10、什么情况下消息会重新入列,也就是什么时候不会接收到消费者发送过来的ack消息确认?
- 消费者在没有发送 ack 的情况下死亡(其通道关闭、连接关闭或 TCP 连接丢失)
- 消费者处理消息时间超时(超时时间详解看:11、消费者始终不确认消息被消费,阐述RabbitMQ服务端的处理策略)
11、消费者始终不确认消息被消费,阐述RabbitMQ服务端的处理策略
消费者确认消息的超时时间是30分钟,如果在这个时间段内消息没有被确认,那RabbitMQ服务端就认为消费者执行消息已经失败,然后将消息分发给其他消费者,当然超时时间也是可以改变的,如果需要改变,请看Delivery Acknowledgement Timeout
12、生产者可以直接指定接收消息的队列吗?
不可以,无论是简单模式还是复杂模式,生产者都是将消息发送给了交换机(简单模式和工作队列模式使用默认交换机),然后交换机根据路由把消息发送给消费者,消费者端只需要路由,消息将会发送给对应消费者,但是生产者无法直接指定接收消息的队列
13、为什么生产者端发送消息时一定要指定路由?
生产者端把消息发送到交换机中,交换机把消息发送到队列中,其中交换机和队列通过路由建立连接,所以生产者端发送消息的时候就需要将消息和路由绑定,然后消息到达交换机的时候,交换机可以清晰的知道这条消息将会发送到哪个队列中
14、消费者端接收到了消息,但是RabbitMQ控制台中的消息依然存在,原因是什么
- 消费者端开启了消息批量应答,并且设置了通道中可以同时存在的未消费消息数量大于1,但是消息数量小于同时存在的未消费消息数量,因此消费者端没有进行批量应答,所以RabbitMQ控制台中的消息依然存在;解释:这是我遇到的真实情况,之前和KG公司使用RabbitMQ传递消息的时候,他们就是遇到了该情况,后来发现设置的prefetchCount的值是10,参数设置方式如下:
15、交换机的几种类型
交换机的类型有以下几种:direct、topic、headers、fanout
- direct:交换机根据路由键完全匹配关系来发送消息到队列,首先消息携带的有路由键,而交换机和队列也通过路由键进行绑定,当这两个路由键完全相等的时候,交换机就会把消息发送给该队列
- fanout:交换机会把消息发送给所有和该交换机绑定的队列,和路由键无关
- topic:消息路由键都是由单词组成,中间用.分隔,队列和交换机绑定的路由键可以使用通配符*和#
对比:
- 交换机使用topic模式,当队列使用 # 为路由键和交换机绑定时,它将接收所有消息,那么和交换机使用fanout模式一样
- 交换机使用topic模式,当队列中不使用*或者#特殊字符,那么和交换机使用direct模式一样
16、我们把消息发送到交换机上,但是没有任何队列和交换机相连,那么消息将何去何从?
消息将被丢弃
17、创建随机名称的临时队列有什意义?
平时为了队列持久化和消息持久化,我们会进行相关设置,但是有些时候我们不在乎队列持久化和消息持久化,我们只关心我们和交换机连接过程中获取到的消息,这就需要临时队列了,一旦断开交换机和消费者的连接,队列就会被自动删除,比如发布/订阅中举出的日志系统例子,我们只需要在连接上交换机的时候将消息实时展示在屏幕上,所以我们就可以使用临时队列来做这件事
18、basicQos()方法如何使用
官方的解释请点击我,该方法是一个重载方法,可以写一个参数,也可以写两个参数;
如果只写一个参数,代表每一个消费者在通道中的未确认消息的数量最大限制,当然这一个参数是0的话,那代表没有限制;
如果写两个参数,第二个参数是false的话代表每一个消费者在通道中的未确认消息数量的最大限制,第二个参数是true的话,代表通道中所有未确认消息数量的最大限制;
举例说明:
// 情况1:每一个消费者在通道中的未确认消息数量的最大限制
channel.basicQos(10);
// 情况2:没有任何限制
channel.basicQos(0);
// 情况3:通道限制是顶级限制,其次是消费者在通道中的未确认发消息的最大限制
// 每一个消费者在通道中的未确认消息数量的最大限制
channel.basicQos(10, false);
// 通道中所有未确认消息数量的最大限制
channel.basicQos(15, true);
19、basicReject()和basicNack()方法的区别
basicReject只能拒绝一条消息,但是basicNack可以拒绝一条或者多条消息,其中多条消息代表通道中的所有消息,我们用代码来说明一下吧,如下:
basicReject:
// 将该条消息拒绝掉,并且不放入队列
// 参数1:消息标签
// 参数2:是否重新放入队列
channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
basicNack:
// 将通道中的所有消息都拒绝掉,并且不放入队列
// 参数1:消息标签
// 参数2:是否批量确定;(1)true:代表批量确定,即通道中的所有消息都将被拒绝;(2)false:代表单条确定,即该条消息将被拒绝,不会影响通道中的其他消息
// 参数3:是否重新放入队列;(1)true:重新放入队列;(2)flase:不放入队列,成为死信
channel.basicNack(message.getEnvelope().getDeliveryTag(), true,false);