一、依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.14.2</version>
</dependency>
二、代码
1、死信队列
1.1、TTL过期
1.1.1、生产者
public class Provider {
// 队列名称
private static final String COMMON_EXCHANGE_NAME = "common_exchange";
private static final String COMMON_QUEUE_NAME = "common_queue";
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(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
/**
* 消息属性
* new AMQP.BasicProperties(XXX)是直接从MessageProperties.PERSISTENT_TEXT_PLAIN消息持久化配置中拿过来的
* expiration(XXX)用来设置消息过期时间,单位是毫秒
*/
AMQP.BasicProperties properties = new AMQP.BasicProperties("text/plain",
null,
null,
2,
0, null, null, null,
null, null, null, null,
null, null).builder().expiration("10000").build();
// 消息内容
String message = "Hello World!";
/**
* 发送消息到队列
* 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
* 参数1:交换机名称
* ""是默认交换机的名称,其中默认交换机隐式绑定到每个队列,路由密钥等于队列名称,无法删除默认交换机
* 参数2:路由名称
* 由于默认交换机和队列之间的路由是队列名称,所以消息路由是队列名称
* 参数3:其他消息属性,比如MessageProperties.PERSISTENT_TEXT_PLAIN就是消息持久化
* 参数4,消息内容
* 消息内容是字节数组,所以我们需要使用getBytes()方法
*/
channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.1.2、消费者
public class Consumer {
// 队列名称
private static final String COMMON_EXCHANGE_NAME = "common_exchange";
private static final String DEAD_EXCHANGE_NAME = "dead_exchange";
private static final String COMMON_QUEUE_NAME = "common_queue";
private static final String DEAD_QUEUE_NAME = "dead_queue";
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.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
// 创建死信交换机
channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
// 为死信指明死信交换机和死信路由
Map<String, Object> arguments = new HashMap<>();
// 设置死信的目的交换机
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
// 设置死信携带的路由
arguments.put("x-dead-letter-routing-key", DEAD_QUEUE_NAME);
/**
* 创建普通队列
* 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
* 参数1:队列名称
* 参数2:是否持久化
* 队列持久化之后,即使RabbitMQ重启,队列依然存在
* 参数3:是否排他
* 设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
* 参数4:附加参数
* 比如可以设置死信队列等
*/
channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, arguments);
/**
* 创建死信队列
*/
channel.queueDeclare(DEAD_QUEUE_NAME, true, false, false, null);
// 绑定普通队列和普通交换机
channel.queueBind(COMMON_QUEUE_NAME, COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME);
// 绑定死信队列和死信交换机
channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_QUEUE_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(DEAD_QUEUE_NAME, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.1.3、说明
TTL代表time to live
,即消息存活时间,默认情况下队列中的消息不会过期,不过部分场景需要设置过期时间,比如淘宝购买商品的时候,必须要在两个小时内付款,否则订单自动取消;又或者说在12306上买票的时候,必须在30分钟内付款,否则订单自动取消;这些过程都可以用到死信队列,毕竟这些场景的时间都是定值,所以开发者可以设置一个普通队列,该队列的消息过期时间是2小时或者30分钟,这个根据场景来定,当然这个过程时间也可以由发送者设置,比如我们上面的例子中就是由发送者设置的,但是我们现在说的场景是固定时间由普通队列设置,并且该队列和死信交换机进行绑定,并且设置了相关路由键,也就是一旦消息过期,消息将携带设置的路由键进入死信交换机,然后死信交换机把消息交给死信队列,但是这个普通队列不允许任何消费者监听,毕竟该队列就是进行计时的,不需要被监听,我们真正应该监听的是死信队列,毕竟消息过期的时候也代表时间够了,然后我们消息经由普通队列》死信交换机》死信队列》消费者,然后我们根据订单的状态来做对应的事情,比如订单已经被支付了,那我们什么都不用做,如果订单依然没有被支付,我们就可以进行取消订单操作,如果订单已经被取消了,我们也可以什么都不用做,至于这整个流程我们可以这样来看,如下:
1.2、消息数量超过队列最大长度
1.2.1、生产者
public class Provider {
// 队列名称
private static final String COMMON_EXCHANGE_NAME = "common_exchange";
private static final String COMMON_QUEUE_NAME = "common_queue";
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(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
for (int i = 1; i <= 10; i++) {
// 消息内容
String message = "Hello World!" + i;
/**
* 发送消息到队列
* 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
* 参数1:交换机名称
* ""是默认交换机的名称,其中默认交换机隐式绑定到每个队列,路由密钥等于队列名称,无法删除默认交换机
* 参数2:路由名称
* 由于默认交换机和队列之间的路由是队列名称,所以消息路由是队列名称
* 参数3:其他消息属性
* MessageProperties.PERSISTENT_TEXT_PLAIN:消息持久化,即消息将会存储到磁盘中,只要消息队列也是持久化的,即使RabbitMQ重启,消息依然存在于队列中
* 参数4,消息内容
* 消息内容是字节数组,所以我们需要使用getBytes()方法
*/
channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.2.2、普通消费者
/*
* Copyright (c) 2006-2021, BeiJing Leadal Technology Co.,Ltd.
* All rights reserved.
*
* Project : kms-wiki
* Version : 1.1.0
* JDK Version : 1.6
*
* Comments
*
* History
* Sr Date ModifiedBy Why & What is modified
* 1. 2022/4/22 guoming Created
*/
package com.atguigu.demo.dead_letter.max_queue_length;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.HashMap;
import java.util.Map;
/**
* 消费者(作用:用来接收普通消息;注意:该消费者只用创建交换机和队列,创建完成之后就可以关闭了)
*
* @author guoming
* @date 2022/4/22 23:14
*/
public class Consumer1 {
// 队列名称
private static final String COMMON_EXCHANGE_NAME = "common_exchange";
private static final String DEAD_EXCHANGE_NAME = "dead_exchange";
private static final String COMMON_QUEUE_NAME = "common_queue";
private static final String DEAD_QUEUE_NAME = "dead_queue";
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.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
// 创建死信交换机
channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
// 为死信指明死信交换机和死信路由
Map<String, Object> arguments = new HashMap<>();
// 设置普通队列最大长度,也就是队列同时最多能存在的消息数量
arguments.put("x-max-length", 6);
// 设置死信的目的交换机
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
// 设置死信携带的路由
arguments.put("x-dead-letter-routing-key", DEAD_QUEUE_NAME);
/**
* 创建普通队列
* 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
* 参数1:队列名称
* 参数2:是否持久化
* 队列持久化之后,即使RabbitMQ重启,队列依然存在
* 参数3:是否排他
* 设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
* 参数4:附加参数
* 比如可以设置死信队列等
*/
channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, arguments);
/**
* 创建死信队列
*/
channel.queueDeclare(DEAD_QUEUE_NAME, true, false, false, null);
// 绑定普通队列和普通交换机
channel.queueBind(COMMON_QUEUE_NAME, COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME);
// 绑定死信队列和死信交换机
channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_QUEUE_NAME);
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.2.3、死信消费者
public class Consumer2 {
// 队列名称
private static final String DEAD_EXCHANGE_NAME = "dead_exchange";
private static final String DEAD_QUEUE_NAME = "dead_queue";
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.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
// 创建死信队列
channel.queueDeclare(DEAD_QUEUE_NAME, true, false, false, null);
// 绑定死信队列和死信交换机
channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_QUEUE_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(DEAD_QUEUE_NAME, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.2.4、说明
如果队列中的最大消息数量有限制,超出数量的消息将成为死信,我们本次的模拟办法是设置队列的最大消息数量是6,然后我们发送10条消息,那么消息队列只能存储6个,由于队列是先进先出的,所以最前面到来的4个消息将会成为死信。普通队列对应有死信交换机,并且设置了死信消息的路由是死信队列名称,这样我们就可以把死信消息发送到死信队列,然后被消费者消费掉,整个过程如下图:
1.3、消费者拒绝接收消息,并拒绝将消息重新放回队列
1.3.1、生产者
public class Provider {
// 队列名称
private static final String COMMON_EXCHANGE_NAME = "common_exchange";
private static final String COMMON_QUEUE_NAME = "common_queue";
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(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
for (int i = 1; i <= 10; i++) {
// 消息内容
String message = "Hello World!" + i;
/**
* 发送消息到队列
* 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
* 参数1:交换机名称
* ""是默认交换机的名称,其中默认交换机隐式绑定到每个队列,路由密钥等于队列名称,无法删除默认交换机
* 参数2:路由名称
* 由于默认交换机和队列之间的路由是队列名称,所以消息路由是队列名称
* 参数3:其他消息属性
* MessageProperties.PERSISTENT_TEXT_PLAIN:消息持久化,即消息将会存储到磁盘中,只要消息队列也是持久化的,即使RabbitMQ重启,消息依然存在于队列中
* 参数4,消息内容
* 消息内容是字节数组,所以我们需要使用getBytes()方法
*/
channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.3.2、普通消费者
public class Consumer1 {
// 队列名称
private static final String COMMON_EXCHANGE_NAME = "common_exchange";
private static final String DEAD_EXCHANGE_NAME = "dead_exchange";
private static final String COMMON_QUEUE_NAME = "common_queue";
private static final String DEAD_QUEUE_NAME = "dead_queue";
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.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
// 创建死信交换机
channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
// 为死信指明死信交换机和死信路由
Map<String, Object> arguments = new HashMap<>();
// 设置死信的目的交换机
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
// 设置死信携带的路由
arguments.put("x-dead-letter-routing-key", DEAD_QUEUE_NAME);
/**
* 创建普通队列
* 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
* 参数1:队列名称
* 参数2:是否持久化
* 队列持久化之后,即使RabbitMQ重启,队列依然存在
* 参数3:是否排他
* 设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
* 参数4:附加参数
* 比如可以设置死信队列等
*/
channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, arguments);
/**
* 创建死信队列
*/
channel.queueDeclare(DEAD_QUEUE_NAME, true, false, false, null);
// 绑定普通队列和普通交换机
channel.queueBind(COMMON_QUEUE_NAME, COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME);
// 绑定死信队列和死信交换机
channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_QUEUE_NAME);
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
/**
* 手动拒绝接收消息
* 参数1:消息标签
* 参数2:是否重新将消息放回原来的队列
* 是:消息重新放回队列,然后重新发送消费者,该消息不会生成死信消息
* 否:消息不放回队列,该消息会成为死信消息
*/
channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
};
/**
* 消费者异步监听队列消息
* 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
* 参数1:队列名称;
* 指定消费者连接的队列
* 参数2:是否自动确认;
* 如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
* 如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
* 参数3:消息传递时的回调对象
* 参数4:消费者被取消时的回调,暂时没有看懂啥意思
*/
channel.basicConsume(COMMON_QUEUE_NAME, false, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.3.3、死信消费者
public class Consumer2 {
// 队列名称
private static final String DEAD_EXCHANGE_NAME = "dead_exchange";
private static final String DEAD_QUEUE_NAME = "dead_queue";
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.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
// 创建死信队列
channel.queueDeclare(DEAD_QUEUE_NAME, true, false, false, null);
// 绑定死信队列和死信交换机
channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_QUEUE_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(DEAD_QUEUE_NAME, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.3.4、说明
如果普通消费者通过basicReject()
方法拒绝了消息,并且通过设置requeue参数为false,那代表不将消息放回队列,这样的话消息将成为死信,在上面的例子中,普通队列中的死信将传递给死信交换机,并且消息路由键是死信队列名称,这样的话,死信队列的消费者就可以接收到被拒绝的消息,画图说明如下:
2、优先级队列
2.1、生产者
public class Provider {
// 普通交换机
public static final String COMMON_EXCHANGE_NAME = "common_exchange3";
// 普通队列
public static final String COMMON_QUEUE_NAME = "common_queue3";
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
) {
// 声明交换机
channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
// 消息发送
for (int i = 1; i <= 10; i++) {
String message = "测试消息" + i;
if (i == 5) {
// 默认优先级都是0,设置5号消息的优先级是5
AMQP.BasicProperties properties = new AMQP.BasicProperties("text/plain",
null,
null,
2,
5, null, null, null,
null, null, null, null,
null, null).builder().build();
channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));
} else {
channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.2、消费者
public class Consumer {
// 普通交换机
public static final String COMMON_EXCHANGE_NAME = "common_exchange3";
// 普通队列
public static final String COMMON_QUEUE_NAME = "common_queue3";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try {
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
// 声明交换机
channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
// 创建队列
Map<String, Object> arguments = new HashMap<>(1);
arguments.put("x-max-priority", 10);
channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, arguments);
// 绑定普通队列和普通交换机
channel.queueBind(COMMON_QUEUE_NAME, COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME);
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println(str);
};
channel.basicConsume(COMMON_QUEUE_NAME, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3、说明
默认的消息优先级是0,其中消息优先级越大,在队列中越靠前,然后越早被发送,我们可以设置队列中的最大消息优先级,代码如下:
Map<String, Object> arguments = new HashMap<>(1);
arguments.put("x-max-priority", 10);
channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, arguments);
设置之后代表发送者发送的消息最大优先级不应该超过10,其实在rabbitmq官网上也说明了消息优先级应该是1~255之间的正整数,并且队列中设置的最大优先级不应该超过10,避免优先级过多的时候将会导致 Erlang 进程消耗更多的CPU 资源,然后我们可以在发送者发送消息的时候设置消息优先级,如下:
// 默认优先级都是0,设置5号消息的优先级是5
AMQP.BasicProperties properties = new AMQP.BasicProperties("text/plain",
null,
null,
2,
5, null, null, null,
null, null, null, null,
null, null).builder().build();
channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));
3、自定义延迟交换机
3.1、生产者
public class Provider {
// 延时交换机
public static final String DELAY_EXCHANGE_NAME = "delay_exchange2";
// 普通队列
public static final String COMMON_QUEUE_NAME = "common_queue2";
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
) {
// 创建延迟交换机;注意交换机类型是"x-delayed-message",另外该交换机本质上还是一个direct类型交换机
Map<String, Object> arguments = new HashMap<>(1);
arguments.put("x-delayed-type", "direct");
channel.exchangeDeclare(DELAY_EXCHANGE_NAME, "x-delayed-message", true, false, arguments);
// 10s消息;说明:10s消息应该比2s的消息更晚到达
String message = "我是10s的消息";
HashMap<String, Object> header = new HashMap<>();
// 设置延迟10s:这个延迟操作是在交换机中进行了,10s之后会将消息发送给消息队列,当然本次使用的交换机是延迟交换机
header.put("x-delay", 10000);
AMQP.BasicProperties properties = new AMQP.BasicProperties("text/plain",
null,
null,
2,
0, null, null, null,
null, null, null, null,
null, null).builder().headers(header).build();
channel.basicPublish(DELAY_EXCHANGE_NAME, COMMON_QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));
// 2s消息;说明:2s消息应该比10s的消息更早到达
message = "我是2s的消息";
header = new HashMap<>();
header.put("x-delay", 2000);
properties = new AMQP.BasicProperties("text/plain",
null,
null,
2,
0, null, null, null,
null, null, null, null,
null, null).builder().headers(header).build();
channel.basicPublish(DELAY_EXCHANGE_NAME, COMMON_QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.2、消费者
public class Consumer {
// 延时交换机
public static final String DELAY_EXCHANGE_NAME = "delay_exchange2";
// 普通队列
public static final String COMMON_QUEUE_NAME = "common_queue2";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try {
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
// 创建延迟交换机
Map<String, Object> arguments = new HashMap<>(1);
arguments.put("x-delayed-type", "direct");
channel.exchangeDeclare(DELAY_EXCHANGE_NAME, "x-delayed-message", true, false, arguments);
// 创建队列
channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, null);
// 绑定普通队列和延迟交换机
channel.queueBind(COMMON_QUEUE_NAME, DELAY_EXCHANGE_NAME, COMMON_QUEUE_NAME);
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println(str);
};
channel.basicConsume(COMMON_QUEUE_NAME, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.3、Windows中安装并启用延迟交换机插件
(1)首先从https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases下载对应版本的插件
(2)解压到rabbitmq安装目录下面的plugins目录下面
(3)在rabbitmq安装目录下面的sbin目录下打开DOS窗口,然后执行rabbitmq-plugins enable rabbitmq_delayed_message_exchange
就可以让插件起作用
(4)我们打开rabbitmq控制台之后,选择交换机,然后就可以看到如下的交换机类型
3.3、说明
在此之前,我们如果想实现延时效果,需要借助于过期时间和死信队列来打成,假设我们设置一个消息过期时间是2s,另外一个消息过期时间是10s,然后先将过期时间10s的消息发送到一个队列中,在将过期时间是2s的消息发送到同样队列中,然后让消息过期,之后通过死信交换机把消息交给死信队列,另外我们有消息着在监听着死信队列,我们肯定以为2s的消息会首先被消费,然后10s的消息在被消费,其实不是这样的,而是10s的消息被消费之后,2s的消息才被消费,原因就是队列是先进先出的,所以我们无法实现想要的效果,上面的文字用画图表示如下:
为了解决该问题,我们安装了延迟交换机插件,其中延时过程是在交换机中进行的,当延时结束之后就会将消息发送给消息队列,所以就不会产生上面的延时失败问题了,在代码编写过程中,可以设置交换机类型为x-delayed-message
,即延时交换机,并且在消息头中设置延迟时间,如下所示:
// 创建延迟交换机;注意交换机类型是"x-delayed-message",另外该交换机本质上还是一个direct类型交换机
Map<String, Object> arguments = new HashMap<>(1);
arguments.put("x-delayed-type", "direct");
channel.exchangeDeclare(DELAY_EXCHANGE_NAME, "x-delayed-message", true, false, arguments);
// 10s消息;说明:10s消息应该比2s的消息更晚到达
String message = "我是10s的消息";
HashMap<String, Object> header = new HashMap<>();
// 设置延迟10s:这个延迟操作是在交换机中进行了,10s之后会将消息发送给消息队列,当然本次使用的交换机是延迟交换机
header.put("x-delay", 10000);
AMQP.BasicProperties properties = new AMQP.BasicProperties("text/plain",
null,
null,
2,
0, null, null, null,
null, null, null, null,
null, null).builder().headers(header).build();
channel.basicPublish(DELAY_EXCHANGE_NAME, COMMON_QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));
4、消息无法抵达交换机或者无法抵达队列的情况,也是异步发布确认的写法
4.1、生产者
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentHashMap;
/**
* 生产者
*
* @author 明快de玄米61
* @date 2022/4/22 23:14
*/
public class Provider {
// 存在的交换机
public static final String EXIST_EXCHANGE_NAME = "exist_exchange_name";
// 不存在的队列
public static final String NOT_EXIST_QUEUE_NAME = "not_exist_queue_name";
public static void main(String[] args) throws InterruptedException {
// 存储消息内容,方便异步监听时取出
ConcurrentHashMap<Long, String> map = new ConcurrentHashMap<>();
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
) {
// 开启确认发布,然后才会回调addConfirmListener方法
channel.confirmSelect();
//消息发送到交换机成功或者失败,都会调用该方法;注意:如果交换机不存在,程序将会报错,然后停止执行
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("成功,deliveryTag:" + deliveryTag + ",值:" + map.get(deliveryTag));
map.remove(deliveryTag);
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("失败:" + map.get(deliveryTag));
map.remove(deliveryTag);
}
});
// 无法发送消息到队列,就会调用该方法
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode,
String replyText,
String exchange,
String routingKey,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("没找到符合的队列而退回的消息 = " + new String(body, "utf-8"));
}
});
// 声明存在的交换机
channel.exchangeDeclare(EXIST_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
// 当交换机发送消息到队列失败的时候,将会回调该方法,成功不会回调
String message = "不存在的队列";
for (int i = 1; i < 1000; i++) {
System.out.println("标号:" + i);
message += i;
long nextPublishSeqNo = channel.getNextPublishSeqNo();
// 第三个参数设置为true,将会把找不到队列的消息返还给生产者,然后上面addReturnListener方法才能起到作用
channel.basicPublish(EXIST_EXCHANGE_NAME, NOT_EXIST_QUEUE_NAME, true, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
map.put(nextPublishSeqNo, message);
message = "不存在的队列";
}
// 同步等待,等异步监听跑完,毕竟try里面声明channel,代码执行完毕后将会关闭channel,这样会导致异步监听无法再执行
channel.waitForConfirms();
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.2、说明
1、只有开启确认发布,才会回调addConfirmListener方法
2、只有发送消息时第三个参数设置为true,才会把找不到队列的消息返还给生产者,然后addReturnListener方法才能起到作用
3、在开启异步监听的时候,记得在最后开启同步等待,避免异步监听程序还没执行完,然后channel通道就被关闭了
4、用并发Map保证在异步监听代码执行时也能取到数据
5、备用交换机
5.1、生产者
public class Provider {
// 普通交换机
public static final String COMMON_EXCHANGE_NAME = "common_exchange5";
// 备用交换机
public static final String BACKUP_EXCHANGE_NAME = "backup_exchange5";
// 普通队列
public static final String COMMON_QUEUE_NAME = "common_queue5";
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
) {
// 声明普通交换机
Map<String, Object> arguments = new HashMap<>();
arguments.put("alternate-exchange", BACKUP_EXCHANGE_NAME);
channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, arguments);
// 消息发送
String message = "测试消息1";
channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
// 消息发送(说明:我们故意将路由键写错,让交换机无法传递消息到普通队列,然后消息被被传递到了备用交换机中,然后我们的备用队列就可以收到消息了)
message = "测试消息2";
channel.basicPublish(COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME + "1", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.2、普通消费者
public class Consumer1 {
// 普通交换机
public static final String COMMON_EXCHANGE_NAME = "common_exchange5";
// 备用交换机
public static final String BACKUP_EXCHANGE_NAME = "backup_exchange5";
// 普通队列
public static final String COMMON_QUEUE_NAME = "common_queue5";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try {
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
// 声明普通交换机
Map<String, Object> arguments = new HashMap<>();
arguments.put("alternate-exchange", BACKUP_EXCHANGE_NAME);
channel.exchangeDeclare(COMMON_EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, arguments);
// 声明备用交换机
channel.exchangeDeclare(BACKUP_EXCHANGE_NAME, BuiltinExchangeType.FANOUT, true, false, null);
// 创建队列
channel.queueDeclare(COMMON_QUEUE_NAME, true, false, false, null);
// 绑定死信队列和死信交换机
channel.queueBind(COMMON_QUEUE_NAME, COMMON_EXCHANGE_NAME, COMMON_QUEUE_NAME);
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new java.lang.String(message.getBody(), StandardCharsets.UTF_8);
System.out.println("普通队列接收到的消息:" + str);
};
channel.basicConsume(COMMON_QUEUE_NAME, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.3、备用消费者
public class Consumer2 {
// 普通交换机
public static final String COMMON_EXCHANGE_NAME = "common_exchange5";
// 备用交换机
public static final String BACKUP_EXCHANGE_NAME = "backup_exchange5";
// 普通队列
public static final String COMMON_QUEUE_NAME = "common_queue5";
// 备用队列
public static final String BACKUP_QUEUE_NAME = "backup_queue5";
public static void main(String[] args) {
// 创建连接工厂,配置连接相关设置
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try {
// 根据上面设置的参数来创建连接
Connection connection = factory.newConnection();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
Channel channel = connection.createChannel();
// 声明备用交换机
channel.exchangeDeclare(BACKUP_EXCHANGE_NAME, BuiltinExchangeType.FANOUT, true, false, null);
// 创建队列
channel.queueDeclare(BACKUP_QUEUE_NAME, true, false, false, null);
// 绑定死信队列和死信交换机
channel.queueBind(BACKUP_QUEUE_NAME, BACKUP_EXCHANGE_NAME, "");
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println("备用队列接收到的消息:" + str);
};
channel.basicConsume(BACKUP_QUEUE_NAME, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.4、说明
如果交换机无法把消息发送给队列,比如通过消息路由键无法匹配到对应队列,那么在我们设置了普通交换机对应的备用交换机的前提下,消息将会发送给备用交换机,进而发送给备用队列,然后消费者就可以消费消息,过程画图如下:
6、文件分片传输
6.1、依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.14.2</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
6.2、消息对象
import java.io.Serializable;
public class Message implements Serializable {
// 分片文件
private byte[] body;
// 文件名
private String filename;
// 文件md5码
private String identifier;
// 文件uuid
private String uuid;
// 默认切片大小
private long chunkSize;
// 文件总大小
private long totalSize;
// 文件切片总数
private int totalChunks;
// 当前是第几片(切片)
private int chunkNumber;
// 当前切片大小
private long currentChunkSize;
public byte[] getBody() {
return body;
}
public void setBody(byte[] body) {
this.body = body;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public long getChunkSize() {
return chunkSize;
}
public void setChunkSize(long chunkSize) {
this.chunkSize = chunkSize;
}
public long getTotalSize() {
return totalSize;
}
public void setTotalSize(long totalSize) {
this.totalSize = totalSize;
}
public int getTotalChunks() {
return totalChunks;
}
public void setTotalChunks(int totalChunks) {
this.totalChunks = totalChunks;
}
public int getChunkNumber() {
return chunkNumber;
}
public void setChunkNumber(int chunkNumber) {
this.chunkNumber = chunkNumber;
}
public long getCurrentChunkSize() {
return currentChunkSize;
}
public void setCurrentChunkSize(long currentChunkSize) {
this.currentChunkSize = currentChunkSize;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
}
6.3、生产者端临时文件对象
// 已接收文件信息
public class FileInfo {
FileInfo() {
}
// 已接收分片数量
private int number;
// 文件路径
private String filePath;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
}
6.4、生产者
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.crypto.digest.MD5;
import com.rabbitmq.client.*;
import java.io.File;
import java.util.Arrays;
/**
* 生产者
*
* @author 明快de玄米61
* @date 2022/4/22 23:14
*/
public class Provider {
// 队列名称
private static final String QUEUE_NAME = "sharding_queue_spring";
public static void main(String[] args) {
// 准备分片参数信息
// 待传输文件
File file = new File("C:\\Users\\mingming\\Downloads\\1云平台一站式部署.mp4");
// 文件UUID,组装文件分片的唯一标识
String uuid = UUID.fastUUID().toString();
// 文件名
String filename = file.getName();
// 文件md5码
String identifier = MD5.create().digestHex(file);
// 默认切片大小:1M
int chunkSize = 1024 * 1024;
// 文件总大小
int totalSize = (int) file.length();
// 文件切片总数
int totalChunks = totalSize % chunkSize == 0 ? totalSize / chunkSize : (totalSize / chunkSize + 1);
// 文件字节数组
byte[] fileContent = FileUtil.readBytes(file);
// 创建连接工厂,配置连接相关设置
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:自动删除
* 没有消费者连接是否自动删除
* 参数5:附加参数
* 比如可以设置死信队列等
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
for (int i = 1; i <= totalChunks; i++) {
byte[] body;
if (i == totalChunks) {
// 最后一块
body = Arrays.copyOfRange(fileContent, chunkSize * (i - 1), totalSize);
} else {
body = Arrays.copyOfRange(fileContent, chunkSize * (i - 1), chunkSize * i);
}
// 当前是第几片(切片)
int chunkNumber = i;
// 当前切片大小
long currentChunkSize = body.length;
// 组装发送数据
Message message = new Message();
message.setBody(body);
message.setFilename(filename);
message.setIdentifier(identifier);
message.setUuid(uuid);
message.setChunkNumber(chunkNumber);
message.setChunkSize(chunkSize);
message.setCurrentChunkSize(currentChunkSize);
message.setTotalChunks(totalChunks);
message.setTotalSize(totalSize);
/**
* 发送消息到队列
* 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
* 参数1:交换机名称
* ""是默认交换机的名称,其中默认交换机隐式绑定到每个队列,路由密钥等于队列名称,无法删除默认交换机
* 参数2:路由名称
* 由于默认交换机和队列之间的路由是队列名称,所以消息路由是队列名称
* 参数3:其他消息属性
* MessageProperties.PERSISTENT_TEXT_PLAIN:消息持久化,即消息将会存储到磁盘中,只要消息队列也是持久化的,即使RabbitMQ重启,消息依然存在于队列中
* 参数4,消息内容
* 消息内容是字节数组,所以我们需要使用getBytes()方法
*/
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, Convert.toPrimitiveByteArray(message));
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("发送完成");
}
}
6.5、消费者
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 消费者
*
* @author 明快de玄米61
* @date 2022/4/22 23:14
*/
public class Consumer {
// 队列名称
private static final String QUEUE_NAME = "sharding_queue_spring";
public static void main(String[] args) {
// 文件信息存储Map
Map<String, FileInfo> map = new HashMap<>();
// 创建连接工厂,配置连接相关设置
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:自动删除
* 没有消费者连接是否自动删除
* 参数5:附加参数
* 比如可以设置死信队列等
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
Message msg = Convert.convert(Message.class, message.getBody());
String uuid = msg.getUuid();
// 处理首块分片
if (!map.containsKey(uuid)) {
String tmpdirPath = System.getProperty("java.io.tmpdir");
String dateStr = new SimpleDateFormat("yyyyMMdd").format(new Date());
String[] params = {tmpdirPath.substring(0, tmpdirPath.length() - 1), "tmpFile", dateStr, uuid};
String fileUrl = String.join(File.separator, Arrays.asList(params));
if (!new File(fileUrl).exists()) {
new File(fileUrl).mkdirs();
}
String filePath = fileUrl + File.separator + msg.getFilename();
new File(filePath).createNewFile();
FileInfo fileInfo = new FileInfo();
fileInfo.setNumber(0);
fileInfo.setFilePath(filePath);
map.put(uuid, fileInfo);
}
// 接收分片
FileInfo fileInfo = map.get(uuid);
//第一步 打开将要写入的文件
RandomAccessFile raf = new RandomAccessFile(fileInfo.getFilePath(), "rw");
//第二步 打开通道
try {
FileChannel fileChannel = raf.getChannel();
//第三步 计算偏移量
long position = (msg.getChunkNumber() - 1) * msg.getChunkSize();
//第四步 获取分片数据
byte[] fileData = msg.getBody();
//第五步 写入数据
fileChannel.position(position);
fileChannel.write(ByteBuffer.wrap(fileData));
fileChannel.force(true);
fileChannel.close();
fileInfo.setNumber(fileInfo.getNumber() + 1);
} finally {
IoUtil.close(raf);
}
// 判断分片是否接收完成
if (msg.getTotalChunks() == map.get(uuid).getNumber()) {
// 文件接收完成
System.out.println("接收文件全路径:" + fileInfo.getFilePath());
// 移除map中文件信息
map.remove(uuid);
}
};
/**
* 消费者异步监听队列消息
* 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
* 参数1:队列名称;
* 指定消费者连接的队列
* 参数2:是否自动确认;
* 如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
* 如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
* 参数3:消息传递时的回调对象
* 参数4:消费者被取消时的回调,暂时没有看懂啥意思
*/
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
7、完整版Demo
7.1、说明
完整版Demo中包含:
- 创建连接工厂
- 声明队列
- 声明交换机
- 声明队列和交换机的绑定关系
- 发送消息
- 消息出现问题退回
- 能者多劳
- 消息手动确定
7.2、依赖
<!--实现amqb协议的rabbitmq客户端依赖-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.14.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.10</version>
</dependency>
7.3、配置文件
把配置文件rabbitmq.properties放在resources目录下面
mq.host=127.0.0.1
mq.port=5672
mq.username=netiler
mq.password=netilermanager
位置截图如下:
7.4、配置类
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/**
* RabbitMq配置类
*/
public class MqConfig {
// RabbitMq或者kafka
static String mqMode;
// ip
static String mqHost;
// 端口
static int mqPort;
// 用户名
static String mqUsername;
// 密码
static String mqPassword;
// 通讯协议
static boolean mqSsl;
// vhost
static String mqVhost;
//MQ初始化连接数
static int mqInitConnections;
static {
init();
}
static void init() {
// 取出rabbitmq.properties
Resource r = new ClassPathResource("rabbitmq.properties");
Properties props = new Properties();
try {
props.load(new FileInputStream(r.getFile()));
} catch (IOException e) {
e.printStackTrace();
}
// 获取配置文件内容
String mode = props.getProperty("mq.mode");
String host = props.getProperty("mq.host");
String port = props.getProperty("mq.port");
String username = props.getProperty("mq.username");
String password = props.getProperty("mq.password");
String ssl = props.getProperty("mq.ssl");
String vhost = props.getProperty("mq.vhost");
String connections = props.getProperty("mq.connections");
// 处理数据
mqMode = mode != null ? mode : "rabbitmq";
mqHost = host != null ? host : "127.0.0.1";
mqPort = port != null ? Integer.valueOf(port) : 5672;
mqUsername = username != null ? username : "guest";
mqPassword = password != null ? password : "guest";
mqSsl = Boolean.parseBoolean(ssl);
mqVhost = vhost != null ? vhost : "/";
mqInitConnections = connections != null ? Integer.valueOf(connections) : 10;
}
public static String getMqMode() {
return mqMode;
}
public static void setMqMode(String mqMode) {
MqConfig.mqMode = mqMode;
}
public static String getMqHost() {
return mqHost;
}
public static void setMqHost(String mqHost) {
MqConfig.mqHost = mqHost;
}
public static int getMqPort() {
return mqPort;
}
public static void setMqPort(int mqPort) {
MqConfig.mqPort = mqPort;
}
public static String getMqUsername() {
return mqUsername;
}
public static void setMqUsername(String mqUsername) {
MqConfig.mqUsername = mqUsername;
}
public static String getMqPassword() {
return mqPassword;
}
public static void setMqPassword(String mqPassword) {
MqConfig.mqPassword = mqPassword;
}
public static boolean isMqSsl() {
return mqSsl;
}
public static void setMqSsl(boolean mqSsl) {
MqConfig.mqSsl = mqSsl;
}
public static String getMqVhost() {
return mqVhost;
}
public static void setMqVhost(String mqVhost) {
MqConfig.mqVhost = mqVhost;
}
public static int getMqInitConnections() {
return mqInitConnections;
}
public static void setMqInitConnections(int mqInitConnections) {
MqConfig.mqInitConnections = mqInitConnections;
}
}
7.5、工具类
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.*;
/**
* RabbitMQ工具类
*
* @author 明快de玄米61
* @date 2023/8/26 23:00
*/
public class RabbitMqUtil {
// 连接工厂
static ConnectionFactory factory;
// Connection连接对象池
private static ConcurrentLinkedQueue<Connection> connections = new ConcurrentLinkedQueue<Connection>();
private static Map<String,Integer> connectionChannelCount = new ConcurrentHashMap<String,Integer>();
// 线程池
private static ExecutorService executor = Executors.newFixedThreadPool(2);
//最小连接数
private static int MIN_CONNECTIONS = 10;
// 初始化连接工厂和连接对象
static {
// 创建连接工厂
factory = new ConnectionFactory();
/**
* 设置连接参数
* 比如:host(localhost)、port(5672)、virtualHost(/)、
* userName(guest)、password(guest)、connectionTimeout(60s)
* 等都可以自定义,否则将使用默认值,其中上述括号中的就是默认值,
* 我们往对应的setXXX()方法中点击一下就能够看到对应默认值了,
* 下面的代码作用是说明一下如何设置我们想要的值
*/
factory.setHost(MqConfig.getMqHost()); // 默认值:localhost
factory.setPort(MqConfig.getMqPort()); // 默认值:5672
factory.setUsername(MqConfig.getMqUsername()); // 默认值:guest
factory.setPassword(MqConfig.getMqPassword()); // 默认值:guest
factory.setVirtualHost(MqConfig.getMqVhost()); // 默认值:/
if (MqConfig.isMqSsl()) {
try {
factory.useSslProtocol();
} catch (KeyManagementException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
// 设置最小连接数
MIN_CONNECTIONS = MqConfig.getMqInitConnections();
// 创建Connection连接对象,并放入Connection连接对象池
init();
}
// 初始化操作
private static void init() {
for (int i = 0; i < MIN_CONNECTIONS; i++) {
try {
System.out.println("create default conn"+i);
forceCreateConn();
Thread.sleep(100l);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 强制创建Connection对象
private static void forceCreateConn() throws IOException, TimeoutException {
Connection conn = factory.newConnection(executor);
conn.setId(UUID.randomUUID().toString());
connections.add(conn);
}
// 创建Connection对象
public static synchronized Connection createConn() throws IOException, TimeoutException {
Connection conn = null;
//初始判断队列中是否存在10个连接
if(connections.size() < MIN_CONNECTIONS){
//不存在,添加连接
conn = factory.newConnection(executor);
conn.setId(UUID.randomUUID().toString());
connections.add(conn);
}else{
//存在10个,获取一个连接
conn = connections.poll();
if(conn == null || !conn.isOpen() ){
//连接为空活关闭,创建连接
if(conn != null){
conn.close();
}
conn = factory.newConnection(executor);
conn.setId(UUID.randomUUID().toString());
connections.add(conn);
}else{
//重新放到队列最后面
connections.offer(conn);
}
}
return conn;
}
// 创建Channel对象
public static Channel createChannel(Connection conn) throws IOException {
Channel channel = conn.createChannel();
if(conn.getId()==null) {
conn.setId(UUID.randomUUID().toString());
}
Integer count = connectionChannelCount.get(conn.getId());
if(count==null) {
count=0;
}
count++;
connectionChannelCount.put(conn.getId(), count);
return channel;
}
// 关闭Channel对象
public static void close(Channel channel) {
try {
if (channel != null && channel.isOpen()) {
channel.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
7.4、生产者
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentHashMap;
/**
* 生产者
*
* @author 明快de玄米61
* @date 2022/4/22 23:14
*/
public class Provider {
// 交换机名称
private static final String EXCHANGE_NAME = "full_version_exchange_spring";
// 队列名称
private static final String QUEUE_NAME = "full_version_queue_spring";
// 绑定键名称,指明队列和交换机的绑定关系
private static final String BINDING_KEY = "my_key";
// 路由键名称,对于直连交换机来说,只有 “绑定键” 全等于 “路由键”,才能把消息发到队列
private static final String ROUTING_KEY = "my_key";
public static void main(String[] args) throws IOException {
sendMsg("测试消息");
}
public static void sendMsg(String message) {
// 获取连接对象
Connection conn;
Channel channel = null;
// 存储消息内容,方便异步回调时取出
ConcurrentHashMap<Long, String> map = new ConcurrentHashMap<>();
// Connection对象位于连接池中,不用关闭,但是需要关闭Channel对象
try {
// 获取Connection对象
conn = RabbitMqUtil.createConn();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
channel = RabbitMqUtil.createChannel(conn);
/**
* 创建交换机
* 参数1:交换机名称
* 参数2:交换机类型;direct代表直接模式
* 参数3:持久化
* 参数4:自动删除
* 参数5:交换机属性
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
/**
* 创建普通队列
* 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
* 参数1:队列名称
* 参数2:是否持久化
* 队列持久化之后,即使RabbitMQ重启,队列依然存在
* 参数3:是否排他
* 设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
* 参数4:自动删除
* 没有消费者连接是否自动删除
* 参数5:附加参数
* 比如可以设置死信队列等
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
/**
* 创建队列和交换机绑定关系
* 参数1:队列名称
* 参数2:交换机名称
* 参数3:绑定键
*/
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, BINDING_KEY);
// 开启确认发布,然后才会回调addConfirmListener方法
channel.confirmSelect();
//消息发送到交换机成功或者失败,都会回调该方法;注意:如果交换机不存在,程序将会报错,然后停止执行
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
// 成功,删除已发送消息
map.remove(deliveryTag);
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("失败:" + map.get(deliveryTag));
map.remove(deliveryTag);
}
});
// 无法发送消息到队列,就会调用该方法
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode,
String replyText,
String exchange,
String routingKey,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("没找到符合的队列而退回的消息 = " + new String(body, "utf-8"));
}
});
/**
* 发送消息到队列
* 说明:由于我们要发送消息到队列,所以我们一定要先创建队列
* 参数1:交换机名称
* EXCHANGE_NAME是交换机名称
*
* 参数2:路由键名称
*
* 参数3:默认值成false,设置为true,将会把找不到队列的消息返还给生产者,然后上面addReturnListener方法才能起到作用
*
* 参数3:其他消息属性,目前设置的是消息持久化
*
* 参数4,消息内容
* 消息内容是字节数组,所以我们需要使用getBytes()方法
*/
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, true, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
// 同步等待,等异步回调跑完,毕竟try里面声明channel,代码执行完毕后将会关闭channel,这样会导致异步回调无法再执行
channel.waitForConfirms();
// 打印发送完成信息
System.out.println("发送消息:" + message);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭Channel对象
RabbitMqUtil.close(channel);
}
}
}
7.5、消费者
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;
/**
* 消费者
*
* @author 明快de玄米61
* @date 2022/4/22 23:14
*/
public class Consumer {
// 交换机名称
private static final String EXCHANGE_NAME = "full_version_exchange_spring";
// 队列名称
private static final String QUEUE_NAME = "full_version_queue_spring";
// 绑定键名称,指明队列和交换机的绑定关系
private static final String BINDING_KEY = "my_key";
public static void main(String[] args) {
// 消费消息只调用一次,就可以保持持续监听
consumeMsg();
}
public static void consumeMsg() {
// 获取连接对象
Connection conn = null;
Channel channel = null;
// 不使用try-with-resource语法的原因是“我们希望进程在消费者异步监听消息到达时始终保持活动状态”,不希望try中代码运行结束,就关闭Connection和Channel
try {
conn = RabbitMqUtil.createConn();
// 创建通道;一个连接里面可以创建多个通道,而通道用于消息发送和接收,这样也可以减少资源浪费
channel = RabbitMqUtil.createChannel(conn);
/**
* 创建交换机
* 参数1:交换机名称
* 参数2:交换机类型;direct代表直接模式
* 参数3:持久化
* 参数4:自动删除
* 参数5:交换机属性
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
/**
* 创建普通队列
* 注意:我们也在这里声明了队列,因为我们可能在发布者之前启动消费者,所以我们要确保队列存在,然后再尝试从中消费消息
* 参数1:队列名称
* 参数2:是否持久化
* 队列持久化之后,即使RabbitMQ重启,队列依然存在
* 参数3:是否排他
* 设置排他:仅限当前连接使用,虽然我不太懂,但是不要设置,毕竟其他连接也要使用
* 参数4:自动删除
* 没有消费者连接是否自动删除
* 参数5:附加参数
* 比如可以设置死信队列等
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
/**
* 创建队列和交换机绑定关系
* 参数1:队列名称
* 参数2:交换机名称
* 参数3:绑定键
*/
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, BINDING_KEY);
// 消息创建中的回调对象;用来处理从队列异步推送过来的消息
Channel finalChannel = channel;
DeliverCallback deliverCallback = (consumerTag, message) -> {
String str = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println("接收消息:" + str);
/**
* 手动确认消息
* 参数1:用来确认消息的发送标签
* 参数2:是否确认当前通道中的所有消息
* false(建议):只确认当前标签对应的消息
* true:确认当前通道中的所有未确认消息,这样可能造成部分正在被消费者处理的消息被确认,如果这些消息处理失败了,那么这些消息就丢失了
*/
finalChannel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
/**
* 消费者异步监听队列消息
* 说明:队列会异步推送消息到我们这里,我们使用deliverCallback回调对象来接受消息
* 参数1:队列名称;
* 指定消费者连接的队列
* 参数2:是否自动确认;
* 如果自动确认,消费者接收消息之后(未处理消息)就会向RabbitMQ服务端发送确认消息,然后RabbitMQ就会删除对应消息
* 如果不自动确认,那我们就需要手动通知RabbitMQ服务端消息已经可以删除了,然后RabbitMQ服务端才会删除对应消息
* 参数3:消息传递时的回调对象
* 参数4:消费者被取消时的回调,暂时没有看懂啥意思
*/
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> {
});
System.out.println("消费者创建完毕……");
} catch (Exception e) {
e.printStackTrace();
RabbitMqUtil.close(channel);
}
}
}