RabbitMq
一、介绍
MQ(Message Queue),本质是一个FIFO队列,先入先出,队列中存放的是消息,是一种跨进程的通讯机制,对通信的双方进行解耦,使得发送方只依赖MQ
MQ功能:
1.流量削峰
使用消息队列做缓存,把同时间的大量请求放入MQ中,进行排队处理,解决高并发问题
2.应用解耦
在多个系统之间调用中,如果其中某一个系统故障,会导致一系列的操作不可用。使用MQ可以减少系统调
用,当某个系统故障时,该系统需要处理的数据会被缓存在MQ中,在系统修复后,可以继续处理相关数据
3.异步处理
存在两个服务,服务A调用服务B,需要知道服务B是否处理完,在服务B处理完成后,将信息发送到MQ,然
后MQ再将信息发送个服务A,通知它处理完成
消息队列分类:
1.kafka
基于pull模式处理消息,适用处理大数据量消息,吞吐量高,适合做日志采集
2.RocketMQ
阿里开发用于双11交易,可靠性高,适合处理高并发场景
3.RabbitMQ
结合erlang语言,性能好,时效性在微秒级
4.ActiveMQ
是Apache下的开源项目,老牌稳定性高的消息队列
二、基本概念
RabbitMQ作为一个消息中间件,它不处理消息,而是进行接受、存储、转发消息
1.生产者
产生数据发送消息的程序
2.交换机
接受来自生产者的消息,将消息推送到队列中
3.队列
消息缓冲区,生产者发送的消息存放在队列中,消费者从队列中接受数据
4.消费者
接受消息的程序
Broker:接受分发消息的应用,即RabbitMQ
Virtual Host:多个用户连接同一个RabbitMQ时,划分出不同的vhost,每个用户在自己的vhost中创建交换机
和队列
Connection:生产者、消费者和Broker之间的连接
Channel:Rabbitmq建立连接开销巨大,Channel是内部逻辑连接通过Channel进行通讯,每一个Channel之
间时完全隔离的
三、安装
RabbitMQ推荐使用Linux环境安装,首先查看Linux对应的版本:
uname -a
由于RabbitMQ基于erlang语言开发,需要下载erlang对应的安装包
RabbitMQ和Erlang版本要求 :https://www.rabbitmq.com/which-erlang.html
由于我的Linux环境为el7版本,erlang 24、25版本只支持el8、el9,我只能选择erlang23.3.4.11,相应的
rabbitmq选择3.9.14
erlang下载地址:https://github.com/rabbitmq/erlang-rpm/releases?q=23.3.4.11&expanded=true
rabbitmq下载地址:https://github.com/rabbitmq/rabbitmq-server/releases?q=3.9.14&expanded=true
然后将安装包传到Linux系统中
安装rpm文件:
rpm -ivh erlang-23.3.4.11-1.el7.x86_64.rpm
yum install socat -y //rabbitmq-server依赖包
rpm -ivh rabbitmq-server-3.9.14-1.el7.noarch.rpm
添加开机启动RabbitMQ服务:
chkconfig rabbitmq-server on
启动服务:
/sbin/service rabbitmq-server start
查看服务状态:
/sbin/server rabbitmq-server status
停止服务:
/sbin/server rabbitmq-server stop
开启web管理插件(需要关闭RabbitMQ服务):
rabbitmq-plugins enable rabbitmq_management
重启服务后,浏览器访问 ip地址:15672(需要关闭Linux防火墙,如果是云服务器,需要配置安全组规则,开
启端口15672),使用默认账户密码:guest/guest访问
没有权限登录,需要自己创建用户:
rabbitmqctl add_user 用户名 密码
设置用户角色:
rabbitmqctl set_user_tags 用户名 administrator //管理员角色
设置用户权限:
//设置用户具有'/'这个vhost中所有资源的配置、写、读权限
//set_permissions [-p <vhostpath>] <user> <conf> <write> <read>
rabbitmqctl set_permissions -p "/" 用户名 ".*" ".*" ".*"
显示当前用户和角色
rabbitmqctl list_users
使用创建的用户登录
四、Java操作队列
创建maven工程,导入依赖:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.8.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
1.Hello world(最简单的队列)
包含一个生产者、一个队列、一个消费者
/**
* 生产者
*/
public class Producer {
public static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
//创建工厂
ConnectionFactory factory = new ConnectionFactory();
//配置IP地址
factory.setHost("ip地址");
//配置用户名
factory.setUsername("用户名");
//配置密码
factory.setPassword("密码");
//创建连接
Connection connection = factory.newConnection();
//创建通道
Channel channel = connection.createChannel();
//生成队列
// String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
// 队列名称 是否持久化 是否只能一个消费者消费 是否自动删除 其他参数
Map<String, Object> argument = new HashMap<>();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 发送的消息
String message = "Hello World!";
// 发送消息
// String exchange, String routingKey, BasicProperties props, byte[] body
// 发送到那个交换机(空串使用默认交换机) 路由key值 其他参数 消息体
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
}
}
/**
* 消费者
*/
public class Consumer {
public static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
//创建工厂
ConnectionFactory factory = new ConnectionFactory();
//配置IP地址
factory.setHost("ip地址");
//配置用户名
factory.setUsername("用户名");
//配置密码
factory.setPassword("密码");
//创建连接
Connection connection = factory.newConnection();
//创建通道
Channel channel = connection.createChannel();
DeliverCallback deliverCallback = (consumerTag, message) -> System.out.println(new String(message.getBody()));
CancelCallback cancelCallback = System.out::println;
//消费消息
// String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback
// 队列名称 消费成功是否自动应答 消费者成功消费的回调 消费者取消消费的回调
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
System.out.println("消费者等待接收消息:");
}
}
2.优先级队列
定义队列时设置最大优先级(0 ~ 255),发送消息时指定优先级,在消费者进行消费时,会根据优先级排列的
顺序消费消息
public class PriorityProducer {
public static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
Map<String, Object> argument = new HashMap<>();
// 设置队列最大优先级为10, 优先级范围为0 ~ 10,默认为0 ~ 255 优先级太多 排序消耗的资源太多
argument.put("x-max-priority", 10);
channel.queueDeclare(QUEUE_NAME, false, false, false, argument);
//需要先将所有消息发送到队列之中后才能让消费者消费
for (int i = 0; i <= 10; i++) {
String message = String.valueOf(i);
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().priority(i).build();
channel.basicPublish("", QUEUE_NAME, properties, message.getBytes(StandardCharsets.UTF_8));
System.out.println("发送消息成功:" + message);
}
}
}
测试:
消费者消费的消息优先级由高到低
3.Work Queue(工作队列)
包含一个生产者、一个队列、二个消费者
封装工具类:
/**
* Rabbitmq工具类: 连接工厂、创建信道
*/
public class RabbitmqUtils {
public static Channel getChannel() throws IOException, TimeoutException {
//创建工厂
ConnectionFactory factory = new ConnectionFactory();
//配置IP地址
factory.setHost("ip地址");
//配置用户名
factory.setUsername("用户名");
//配置密码
factory.setPassword("密码");
//创建连接
Connection connection = factory.newConnection();
//返回创建的通道
return connection.createChannel();
}
}
/**
* 生产者
*/
public class Producer {
public static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
//使用工具类获取信道
Channel channel = RabbitmqUtils.getChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
Scanner scanner = new Scanner(System.in);
String flag;
// 循环遍历发送消息
do
{
System.out.print("请输入发送的消息内容:");
String message = scanner.nextLine();
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("成功发送消息:\t" + message);
System.out.print("是否结束发送消息(y/n):");
flag = scanner.nextLine();
}while(!flag.equalsIgnoreCase("y"));
System.out.println("---------------发送消息结束--------------");
}
}
/**
* 多个消费者 默认轮询消费消息
*/
public class Consumer {
public static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitmqUtils.getChannel();
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println(Thread.currentThread().getName() + "接受消息:" + new String(message.getBody()));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println(Thread.currentThread().getName() + "取消消费" + consumerTag);
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
System.out.println("-----------" + Thread.currentThread().getName() +Thread.currentThread().getId() + "等待接受消息-----------");
}
}
先启动两个消费者,然后启动生产者发送消息(不然消息会被一个消费者消费):
两个消费者各自消费两条消息,是轮询的:
4.消息应答
为保证消息在发送过程中不丢失,使用消息应答机制,即消费者在接受消息并处理消息之后,告诉
RabbitMQ它已经处理了,此时RabbitMQ才能将消息删除
自动应答:
没有对传递消息数量进行限制,可能导致消费者接受不及最终内存耗尽崩溃,仅使用于消费者可以高效并快
速处理大量消息的情况下
手动应答:
Channel.basicAck(用于肯定应答)
void basicAck(long deliveryTag, boolean multiple)
multiple:指定是否批量应答
**注意:**不推荐使用批量确认,确认消息处理完才进行应答,保证消息不丢失
Channel.basicNack(用于否定应答)
void basicNack(long deliveryTag, boolean multiple, boolean requeue)
requeue:是否将不确认的消息重新入队列
Channel.basicReject(用于否定应答) 不处理该消息直接拒绝,将消息丢弃
void basicReject(long deliveryTag, boolean requeue)
手动应答保证消息不丢失:
/**
* 生产者
*/
public class Producer {
private static final String QUEUE_NAME = "ack";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitmqUtils.getChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
Scanner scanner = new Scanner(System.in);
String flag;
do
{
System.out.print("请输入发送的消息内容:");
String message = scanner.nextLine();
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("时间:"+ LocalDateTime.now() + "成功发送消息:\t" + message);
System.out.print("是否结束发送消息(y/n):");
flag = scanner.nextLine();
}while(!flag.equalsIgnoreCase("y"));
System.out.println("---------------发送消息结束--------------");
}
}
/**
* 消费者1(处理消息快)
*/
public class ConsumerAck {
private static final String QUEUE_NAME = "ack";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitmqUtils.getChannel();
System.out.println("Consumer1等待接受消息,处理时间短...");
DeliverCallback deliverCallback = (consumerTag, message) -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("时间:"+ LocalDateTime.now() + "Consumer1接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
//消息应答
//long deliveryTag, boolean multiple
// 消息的标记 是否批量应答
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("Consumer1取消消费消息:" + consumerTag);
};
channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
}
}
/**
* 消费者2(处理消息慢)
* 如果消费者宕机或由于其他原因,消息未应答,消息会自动重新入队,交给其他消费者消费应答
*/
public class ConsumerLongAck {
private static final String QUEUE_NAME = "ack";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitmqUtils.getChannel();
System.out.println("Consumer2等待接受消息,处理时间长...");
DeliverCallback deliverCallback = (consumerTag, message) -> {
try {
TimeUnit.SECONDS.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("时间:"+ LocalDateTime.now() + "Consumer2接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
//消息应答
//long deliveryTag, boolean multiple
// 消息的标记 是否批量应答
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("Consumer2取消消费消息:" + consumerTag);
};
channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
}
}
测试:
正常情况下:
异常情况(Consumer2处理消息过程中宕机):
消息不会丢失,会转发给Consumer1进行处理
5.RabbitMQ持久化
为保证消息不丢失,将RabbitMQ队列和发送的消息进行持久化
/**
* 队列、消息持久化,保证消息不丢失
*/
public class DurableProducer {
private static final String QUEUE_NAME = "durable_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitmqUtils.getChannel();
//队列持久化
//第二个参数 durable置为true
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext())
{
String message = scanner.nextLine();
//消息持久化 MessageProperties.PERSISTENT_TEXT_PLAIN -> 将消息保存到磁盘
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
System.out.println("发送消息:" + message);
}
}
}
查看web界面,Queues选项卡:
6.不公平分发
RabbitMQ默认使用轮询发送消息,在消费者处理速度不一致的情况下就不合适了,应该让效率高的消费者处
理更多的消息,能者多劳
对之前的两个消费者进行修改,加上Channel.basicQos(1)
public class ConsumerLongAck {
private static final String QUEUE_NAME = "ack";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitmqUtils.getChannel();
System.out.println("Consumer2等待接受消息,处理时间长...");
DeliverCallback deliverCallback = (consumerTag, message) -> {
try {
TimeUnit.SECONDS.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("时间:" + LocalDateTime.now() + "Consumer2接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
//消息应答
//long deliveryTag, boolean multiple
// 消息的标记 是否批量应答
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("Consumer2取消消费消息:" + consumerTag);
};
//设置消息不公平分发,能者多劳
channel.basicQos(1);
channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
}
}
public class ConsumerAck {
private static final String QUEUE_NAME = "ack";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitmqUtils.getChannel();
System.out.println("Consumer1等待接受消息,处理时间短...");
DeliverCallback deliverCallback = (consumerTag, message) -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("时间:" + LocalDateTime.now() + "Consumer1接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
//消息应答
//long deliveryTag, boolean multiple
// 消息的标记 是否批量应答
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("Consumer1取消消费消息:" + consumerTag);
};
//设置prefetchCount = 1 -> 消息不公平分发,能者多劳
//prefetchCount > 1 -> 表示预取值,将要收到的消息数(不一定)
channel.basicQos(1);
channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
}
}
测试:
Consumer1接受8条消息
Consumer2接受2条消息
当Channel.basicQos()
中设置的值大于1时,表示该消费者将要接受的消息数量(不一定)
修改Consumer1:Channel.basicQos(2),设置将要接受2条消息
修改Consumer2:Channel.basicQos(3),设置将要接受3条消息
测试:
五、交换机
生产者生产的消息不会直接发送到队列,而是发送到交换机,再由交换机根据路由将消息推入相应的队列
交换机的分类:
直接交换机、主题交换机、标题交换机、扇出交换机
之前我们发送消息时在交换机参数填入空字符串,使用的是默认交换机,它的类型是直接交换机,路由等于
队列名称
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
在web界面中exchange标签页第一个:
1.Publish/Subscribe(发布订阅)
使用扇出交换机(fanout),将收到的所有消息广播到绑定的所有队列中
发布订阅结构图:
/**
* 生产者
*/
public class Sender {
private static final String EXCHANGE_NAME = "fanout_test";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
Scanner scanner = new Scanner(System.in);
String flag;
do {
System.out.print("请输入发送的消息内容:");
String message = scanner.nextLine();
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("时间:" + LocalDateTime.now() + "\t成功发送消息:\t" + message);
System.out.print("是否结束发送消息(y/n):");
flag = scanner.nextLine();
} while (!flag.equalsIgnoreCase("y"));
System.out.println("---------------发送消息结束--------------");
}
}
/**
* 消费者
*/
public class Receiver01 {
private static final String EXCHANGE_NAME = "fanout_test";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
//生成一个临时队列, 当消费者断开连接时,队列自动删除
String queueName = channel.queueDeclare().getQueue();
//绑定交换机和队列 路由为""
channel.queueBind(queueName, EXCHANGE_NAME, "");
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Receiver01接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("Receiver01取消消息:" + consumerTag);
};
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
System.out.println("Receiver01等待接受消息...");
}
}
/**
* 消费者
*/
public class Receiver02 {
private static final String EXCHANGE_NAME = "fanout_test";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
//生成一个临时队列, 当消费者断开连接时,队列自动删除
String queueName = channel.queueDeclare().getQueue();
//绑定交换机和队列 路由为""
channel.queueBind(queueName, EXCHANGE_NAME, "");
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Receiver02接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("Receiver02取消消息:" + consumerTag);
};
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
System.out.println("Receiver02等待接受消息...");
}
}
测试:
交换机广播消息,两个消费者都接受到消息
临时队列:
队列名称随机,一旦断开连接队列将自动删除
String queueName = channel.queueDeclare().getQueue();
2.Routing(路由模式)
使用直接交换机(direct),会根据路由匹配,将消息推送到相应的队列
路由模式结构图:
/**
* 生产者
*/
public class Sender {
private static final String EXCHANGE_NAME = "direct_test";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
Scanner scanner = new Scanner(System.in);
String flag;
do {
System.out.print("请输入路由:");
String routing_key = scanner.nextLine();
System.out.print("请输入消息:");
String message = scanner.nextLine();
//路由对应发送到的队列
channel.basicPublish(EXCHANGE_NAME, routing_key, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("Sender发送消息:" + message);
System.out.print("是否结束发送消息(y/n):");
flag = scanner.nextLine();
} while (!flag.equalsIgnoreCase("y"));
}
}
/**
* 消费者
*/
public class Receiver01 {
private static final String EXCHANGE_NAME = "direct_test";
private static final String QUEUE_NAME = "console";
private static final String ROUTING_KEY1 = "info";
private static final String ROUTING_KEY2 = "warning";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_NAME, false, false,false,null);
//绑定交换机和队列
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY1);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY2);
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Receiver01接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("Receiver01取消消息:" + consumerTag);
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
System.out.println("Receiver01等待接受消息...");
}
}
/**
* 消费者
*/
public class Receiver02 {
private static final String EXCHANGE_NAME = "direct_test";
private static final String QUEUE_NAME = "disk";
private static final String ROUTING_KEY = "error";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_NAME, false, false,false,null);
//绑定交换机和队列 路由为""
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Receiver02接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("Receiver02取消消息:" + consumerTag);
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
System.out.println("Receiver02等待接受消息...");
}
}
测试:
3.Topics(主题模式)
使用主题交换机(topic),路由必须是一个单词列表以点做分割,根据路由进行匹配推送消息到队列
路由中可以使用替换符,最多255个字节:
*(星号)
:可以代替一个单词
#(井号)
:可以代替零个或多个单词
主题模式结构图:
/**
* 生产者
*/
public class Sender {
private static final String EXCHANGE_NAME = "topic_test";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
Scanner scanner = new Scanner(System.in);
String flag;
do {
System.out.print("请输入路由:");
String routing_key = scanner.nextLine();
System.out.print("请输入消息:");
String message = scanner.nextLine();
//路由对应发送到的队列
channel.basicPublish(EXCHANGE_NAME, routing_key, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("Sender发送消息:" + message);
System.out.print("是否结束发送消息(y/n):");
flag = scanner.nextLine();
} while (!flag.equalsIgnoreCase("y"));
System.out.println("---------------发送消息结束--------------");
}
}
/**
* 消费者
*/
public class Receiver01 {
private static final String EXCHANGE_NAME = "topic_test";
private static final String QUEUE_NAME = "q1";
private static final String ROUTING_KEY = "*.orange.*";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
channel.queueDeclare(QUEUE_NAME, false, false,false,null);
//绑定交换机和队列
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Receiver01接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("Receiver01取消消息:" + consumerTag);
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
System.out.println("Receiver01等待接受消息...");
}
}
/**
* 消费者
*/
public class Receiver02 {
private static final String EXCHANGE_NAME = "topic_test";
private static final String QUEUE_NAME = "q2";
private static final String ROUTING_KEY1 = "*.*.rabbit";
private static final String ROUTING_KEY2 = "lazy.#";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
channel.queueDeclare(QUEUE_NAME, false, false,false,null);
//绑定交换机和队列 路由为""
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY1);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY2);
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Receiver02接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("Receiver02取消消息:" + consumerTag);
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
System.out.println("Receiver02等待接受消息...");
}
}
测试:
4.死信队列
由于某些原因(消费者消费消息发生异常)导致队列中的消息无法被消费,使用死信队列可以保证消息不丢失
在以下情况,消息将变成死信,将放入死信队列中:
- 消息TTL过期
- 队列满了,无法再添加消息
- 消息被拒绝(basic.nack或者basic.reject,且requeue = false)
死信队列结构图:
/**
* 生产者
*/
public class Producer {
private static final String NORMAL_EXCHANGE_NAME = "normal_exchange";
private static final String NORMAL_QUEUE_ROUTING_KEY = "zhangsan";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
//设置 发送消息的过期时间 ttl
//AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
// .expiration("10000")
// .build();
for (int i = 0; i < 10; i++) {
String message = "info" + i;
//channel.basicPublish(NORMAL_EXCHANGE_NAME, NORMAL_QUEUE_ROUTING_KEY, properties, message.getBytes(StandardCharsets.UTF_8));
channel.basicPublish(NORMAL_EXCHANGE_NAME, NORMAL_QUEUE_ROUTING_KEY, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("发送消息:" + message);
}
}
}
/**
* 消费者
*/
public class Consumer01 {
private static final String NORMAL_EXCHANGE_NAME = "normal_exchange";
private static final String DEAD_EXCHANGE_NAME = "dead_exchange";
private static final String NORMAL_QUEUE_NAME = "normal_queue";
private static final String NORMAL_QUEUE_ROUTING_KEY = "zhangsan";
private static final String DEAD_QUEUE_NAME = "dead_queue";
private static final String DEAD_QUEUE_ROUTING_KEY = "lisi";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
//声明普通交换机和死信交换机
channel.exchangeDeclare(NORMAL_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//声明普通队列和死信队列
//消息变成死信后转发到死信交换机
HashMap<String, Object> argument = new HashMap<>();
//指定死信交换机和路由
argument.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
argument.put("x-dead-letter-routing-key", DEAD_QUEUE_ROUTING_KEY);
//指定队列的最大容量
//argument.put("x-max-length", 6);
channel.queueDeclare(NORMAL_QUEUE_NAME, false, false, false, argument);
channel.queueDeclare(DEAD_QUEUE_NAME, false, false, false, null);
//绑定交换机和队列
channel.queueBind(NORMAL_QUEUE_NAME, NORMAL_EXCHANGE_NAME, NORMAL_QUEUE_ROUTING_KEY);
channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_QUEUE_ROUTING_KEY);
DeliverCallback deliverCallback = (consumerTag, message) -> {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
//channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
//System.out.println("Consumer01接受消息:" + msg);
if (msg.equals("info2")) {
//拒绝info2消息
channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
System.out.println("消息" + msg + "被拒绝!!!");
} else {
//确认其他消息
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
System.out.println("Consumer01接受消息:" + msg);
}
};
channel.basicConsume(NORMAL_QUEUE_NAME, false, deliverCallback, System.out::println);
System.out.println("Consumer01等待接受消息...");
}
}
/**
* 消费者
*/
public class Consumer02 {
private static final String DEAD_QUEUE_NAME = "dead_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Consumer02接受消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
};
channel.basicConsume(DEAD_QUEUE_NAME, true, deliverCallback, System.out::println);
System.out.println("Consumer02等待接受消息...");
}
}
测试:
1.消息被拒绝进入死信队列
2.消息过期
3.队列满了
剩余的4条消息放入死信队列
六、整合springboot
导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
</exclusion>
</exclusions>
</dependency>
如果是云服务器需要修改安全组规则,确认端口5672和15672都开启
编写application.yml配置文件:
spring:
rabbitmq:
host: ip地址
port: 5672
username: 用户名
password: 密码
1.TTL(延迟队列)
延时队列就是用来存放需要在指定时间处理元素的队列
延时队列结构图:
延时队列配置类(配置交换机和队列信息):
/**
* 延时队列配置文件类
*/
@Configuration
public class TTLQueueConfig {
//普通交换机名称
private static final String NORMAL_EXCHANGE_NAME = "X";
//死信交换机名称
private static final String DEAD_EXCHANGE_NAME = "Y";
//普通队列名称
private static final String NORMAL_QUEUE_A = "QA";
private static final String NORMAL_QUEUE_A_ROUTING_KEY = "XA";
private static final String NORMAL_QUEUE_B = "QB";
private static final String NORMAL_QUEUE_B_ROUTING_KEY = "XB";
//死信队列名称
private static final String DEAD_QUEUE_NAME = "QD";
//路由
private static final String DEAD_QUEUE_ROUTING_KEY = "YD";
//声明交换机
@Bean("exchange_X")
public DirectExchange exchange_x(){
return new DirectExchange(NORMAL_EXCHANGE_NAME);
}
@Bean("exchange_Y")
public DirectExchange exchange_y(){
return new DirectExchange(DEAD_EXCHANGE_NAME);
}
@Bean
public Binding queue_ABindExchange_X(@Qualifier("queue_A") Queue queue_A,
@Qualifier("exchange_X") Exchange exchange_X)
{
return BindingBuilder.bind(queue_A).to(exchange_X).with(NORMAL_QUEUE_A_ROUTING_KEY).noargs();
}
@Bean
public Binding queue_BBindExchange_X(@Qualifier("queue_B") Queue queue_B,
@Qualifier("exchange_X") Exchange exchange_X)
{
return BindingBuilder.bind(queue_B).to(exchange_X).with(NORMAL_QUEUE_B_ROUTING_KEY).noargs();
}
@Bean
public Binding queue_DBindExchange_Y(@Qualifier("queue_D") Queue queue_D,
@Qualifier("exchange_Y") Exchange exchange_Y)
{
return BindingBuilder.bind(queue_D).to(exchange_Y).with(DEAD_QUEUE_ROUTING_KEY).noargs();
}
@Bean("queue_A")
public Queue queue_A(){
Map<String, Object> argument = new HashMap<>(3);
//设置死信交换机
argument.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
//设置死信路由
argument.put("x-dead-letter-routing-key", DEAD_QUEUE_ROUTING_KEY);
//设置TTL 10s
argument.put("x-message-ttl", 10000);
return QueueBuilder.durable(NORMAL_QUEUE_A).withArguments(argument).build();
}
@Bean("queue_B")
public Queue queue_B(){
Map<String, Object> argument = new HashMap<>(3);
//设置死信交换机
argument.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
//设置死信路由
argument.put("x-dead-letter-routing-key", DEAD_QUEUE_ROUTING_KEY);
//设置TTL 40s
argument.put("x-message-ttl", 40000);
return QueueBuilder.durable(NORMAL_QUEUE_B).withArguments(argument).build();
}
@Bean("queue_D")
public Queue queue_D(){
return QueueBuilder.durable(DEAD_QUEUE_NAME).build();
}
}
/**
* 生产者
*/
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMessageController {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/message/{message}")
public void sendMessage(@PathVariable("message") String message){
log.info("时间{} -发送消息:{}", LocalDateTime.now(), message);
rabbitTemplate.convertAndSend("X", "XA", message);
rabbitTemplate.convertAndSend("X", "XB", message);
}
}
/**
* 消费者
*/
@Component
@Slf4j
public class DeadLetterQueueListener {
@RabbitListener(queues = {"QD"})
public void receive(Message message, Channel channel) throws Exception {
String msg = new String(message.getBody());
log.info("时间:{} -收到消息:{}", LocalDateTime.now(), msg);
}
}
测试:
访问浏览器:http://localhost:8081/ttl/message/123
以上两个队列都完成了延时效果,但是延时的时间都是固定的无法更改,我们需要增加一个延时队列,延时
的时间随机,由自己指定
结构图:
配置类添加队列QC:
private static final String NORMAL_QUEUE_C = "QC";
private static final String NORMAL_QUEUE_C_ROUTING_KEY = "XC";
@Bean
public Binding queue_CBindExchange_X(@Qualifier("queue_C") Queue queue_C,
@Qualifier("exchange_X") Exchange exchange_X)
{
return BindingBuilder.bind(queue_C).to(exchange_X).with(NORMAL_QUEUE_C_ROUTING_KEY).noargs();
}
/**
* 此处不写死队列QC的TTL
*/
@Bean("queue_C")
public Queue queue_C(){
Map<String, Object> argument = new HashMap<>(2);
//设置死信交换机
argument.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
//设置死信路由
argument.put("x-dead-letter-routing-key", DEAD_QUEUE_ROUTING_KEY);
return QueueBuilder.durable(NORMAL_QUEUE_C).withArguments(argument).build();
}
/**
* 生产者
*/
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMessageController {
@Autowired
@RequestMapping("/message/{message}/{ttl}")
public void sendExpiredMessage(@PathVariable("message") String message,
@PathVariable("ttl") String ttl){
log.info("时间:{} -发送消息:{} -ttl:{}ms", LocalDateTime.now(), message, ttl);
rabbitTemplate.convertAndSend("X", "XC", message, msg -> {
//由生产者设置消息的过期时间
msg.getMessageProperties().setExpiration(ttl);
return msg;
});
}
}
测试:
访问浏览器:http://localhost:8081/ttl/message/@/1000 http://localhost:8081/ttl/message/@/2000
但是,死信队列存在问题,如果第一个消息延时较长,第二个消息延时较短,那么第二个消息需要等待第一
个消息执行完才开始执行,而不会先得到执行
访问浏览器:http://localhost:8081/ttl/message/@/20000 http://localhost:8081/ttl/message/!/1000
解决方法:
下载RabbitMQ rabbitmq_delayed_message_exchange插件:
https://www.rabbitmq.com/#community-plugins.html=
将插件上传到Linux系统中,然后移动到RabbitMQ安装目录中plugins下面
/usr/lib/rabbitmq/lib/rabbitmq_server-xxx/plugins
应用该插件:
rabbitmq-plugins enable rabbitmq_delayed_message_exchange-3.9.0.ez
重新启动RabbitMq,观察web界面,在exchanges标签页,添加交换机类型中存在x-delayed-message则安
装成功
基于插件延时队列结构图:
@Configuration
public class DelayedQueueConfig {
private static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";
private static final String DELAYED_QUEUE_NAME = "delayed.queue";
private static final String DELAYED_QUEUE_ROUTING_KEY = "delayed.routing.key";
@Bean
public Binding delayedQueueToExchange(@Qualifier("delayedExchange") CustomExchange delayedExchange,
@Qualifier("delayedQueue") Queue delayedQueue){
return BindingBuilder.bind(delayedQueue).to(delayedExchange).with(DELAYED_QUEUE_ROUTING_KEY).noargs();
}
@Bean
public CustomExchange delayedExchange(){
Map<String, Object> argument = new HashMap<>();
//配置类型为 直接交换机
argument.put("x-delayed-type", "direct");
//String name, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments
//交换机名称 交换机类型 是否持久化 是否自动删除 配置信息
return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false, argument);
}
@Bean
public Queue delayedQueue(){
return QueueBuilder.durable(DELAYED_QUEUE_NAME).build();
}
}
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMessageController {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/delayMessage/{message}/{delayTime}")
public void sendDelayMessage(@PathVariable("message") String message,
@PathVariable("delayTime") Integer delayTime){
log.info("时间:{} -发送消息:{} -延迟时间:{}ms", LocalDateTime.now(), message, delayTime);
rabbitTemplate.convertAndSend("delayed.exchange", "delayed.routing.key", message, msg -> {
//设置消息的延迟时长
msg.getMessageProperties().setDelay(delayTime);
return msg;
});
}
}
@Component
@Slf4j
public class DelayQueueListener {
@RabbitListener(queues = {"delayed.queue"})
public void receive(Message message, Channel channel)
{
String msg = new String(message.getBody());
log.info("时间:{} -收到消息:{}", LocalDateTime.now(), msg);
}
}
测试:
访问浏览器:http://localhost:8081/ttl/delayMessage/@/20000 http://localhost:8081/ttl/delayMessage/!/2000
延时较短的消息先得到执行
2.Publisher Confirms(发布确认)
首先队列和消息都需要进行持久化
原理:
生产者将信道设置为confirm模式,此时该信道上发布的消息都会被指派一个唯一的id,一旦消息被消费者消
费,交换机会发送一个确认给生产者,确保消息不丢失
开启发布确认:
channel.confirmSelect();
单个发布确认:
同步阻塞,当消息被确认之后才能发送下一条消息,导致消息速度慢
/**
* 发布确认- 单个确认模式
*/
public class SinglePublishConfirm {
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
String queueName = "single";
channel.queueDeclare(queueName, true, false, false, null);
//开启发布确认
channel.confirmSelect();
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, String.valueOf(i).getBytes(StandardCharsets.UTF_8));
//单个消息发送后就确认
boolean flag = channel.waitForConfirms();
if(flag)
{
System.out.println("消息发送成功!");
}
}
long end = System.currentTimeMillis();
System.out.println("单个发布确认,发送100条消息,用时:" + (end - start) + "ms");
//单个发布确认,发送100条消息,用时:5232ms
}
}
批量发布确认:
效率比单个发布确认高,但是如果出现问题时,无法确定哪条消息没有被确认
public class BatchPublishConfirm {
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
String queueName = "batch";
channel.queueDeclare(queueName, true, false, false, null);
//开启发布确认
channel.confirmSelect();
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, String.valueOf(i).getBytes(StandardCharsets.UTF_8));
if(i % 10 == 0)
{
//每发送10条确认一次
boolean flag = channel.waitForConfirms();
if (flag) {
System.out.println("10条消息发送成功!");
}
}
}
long end = System.currentTimeMillis();
System.out.println("批量发布确认,发送100条消息,用时:" + (end - start) + "ms");
//批量发布确认,发送100条消息,用时:669ms
}
}
异步发布确认(推荐使用):
效率最高,可以知道是哪一条消息未确认
public class AsyncPublishConfirm {
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtils.getChannel();
String queueName = "async";
channel.queueDeclare(queueName, true, false, false, null);
//开启发布确认
channel.confirmSelect();
//创建可并发哈希表,存储发送的消息,删除已确认的消息
ConcurrentSkipListMap<Long, String> map = new ConcurrentSkipListMap<>();
long start = System.currentTimeMillis();
//确认监听器
//ConfirmCallback ackCallback, ConfirmCallback nackCallback
// 消息确认成功回调 消息确认失败回调
ConfirmCallback ackCallback = (deliveryTag, multiple) -> {
System.out.println("确认的消息:" + deliveryTag);
//删除哈希表中已确认的消息
//批量删除
if (multiple) {
ConcurrentNavigableMap<Long, String> confirmed = map.headMap(deliveryTag);
confirmed.clear();
} else {
//单个删除
map.remove(deliveryTag);
}
};
ConfirmCallback nackCallback = (deliveryTag, multiple) -> {
String message = map.get(deliveryTag);
System.out.println("未确认的消息标记:" + deliveryTag + ", 内容:" + message);
};
//添加监听器 监听消息是否确认成功
channel.addConfirmListener(ackCallback, nackCallback);
for (int i = 0; i < 100; i++) {
String message = String.valueOf(i);
channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
//存储发送的消息
map.put(channel.getNextPublishSeqNo(), message);
}
long end = System.currentTimeMillis();
System.out.println("异步发布确认,发送100条消息,用时:" + (end - start) + "ms");
//异步发布确认,发送100条消息,用时:27ms
}
}
ConfirmCallback
接口确认交换机接受到生产者发送的消息调用回调,ReturnsCallback
接口在队列接受不
到消息时,将消息回退给生产者
修改application.yml
publisher-confirm-type: CORRELATED # 发布消息成功到交换机后会触发回调
publisher-returns: true # 表示交换机消息发送失败会将消息回退给生产者
public static enum ConfirmType {
SIMPLE, // 会触发回调方法,并且在发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待broker节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDie方法如果返回false则会关闭channel,则接下来无法发送消息到broker
CORRELATED, // 发布消息成功到交换机会触发回调方法
NONE; // 默认值,禁止发布确认模式
private ConfirmType() {
}
}
@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
//将重写后的确认回调和回退消息回调注入rabbitTemplate
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnsCallback(this);
}
/**
* 交换机对生产者的消息产生应答
* @param correlationData 回调消息的id和信息,需要生产者发送
* @param b 交换机是否收到消息 true
* @param s 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
String id = correlationData != null ? correlationData.getId() : "";
if (b) {
log.info("交换机收到id为{}的消息", id);
} else {
log.info("交换机未收到id为{}的消息,原因:{}", id, s);
}
}
/**
*
* @param returnedMessage 回退信息
* Message message //消息内容
* int replyCode //相应码
* String replyText //相应结果
* String exchange // 交换机名称
* String routingKey // 路由
*/
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
log.info("消息{}被交换机{}退回,路由:{},响应码:{},原因是:{}",
returnedMessage.getMessage(),
returnedMessage.getExchange(),
returnedMessage.getRoutingKey(),
returnedMessage.getReplyCode(),
returnedMessage.getReplyText()
);
}
}
/**
* 发布确认 配置类
*/
@Configuration
public class ConfirmConfig {
private static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
private static final String CONFIRM_QUEUE_NAME = "confirm.queue";
private static final String CONFIRM_QUEUE_ROUTING_KEY = "key1";
@Bean
public FanoutExchange backupExchange(){
return ExchangeBuilder.fanoutExchange(BACKUP_EXCHANGE_NAME).build();
}
@Bean
public Queue confirmQueue(){
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}
@Bean
public Binding confirmQueueToConfirmExchange(@Qualifier("confirmExchange") Exchange confirmExchange,
@Qualifier("confirmQueue") Queue confirmQueue){
return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_QUEUE_ROUTING_KEY).noargs();
}
}
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMessageController {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/confirm/sendMessage/{message}")
public void sendConfirmMessage(@PathVariable("message") String message){
log.info("时间:{} -发送消息:{}", LocalDateTime.now(), message);
//rabbitTemplate.convertAndSend("confirm.exchange", "key1", message);
//测试 交换机应答和无法投递消息进行回退
rabbitTemplate.convertAndSend("confirm.exchange", "key2", message, new CorrelationData("1"));
}
}
@Component
@Slf4j
public class Consumer {
@RabbitListener(queues = {"confirm.queue"})
public void receive(Message message, Channel channel){
String msg = new String(message.getBody());
log.info("时间:{} -收到消息:{}", LocalDateTime.now(), msg);
}
}
测试:
3.备份交换机
无法投递的消息将发送给备份交换机
结构图:
@Configuration
public class ConfirmConfig {
private static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
private static final String CONFIRM_QUEUE_NAME = "confirm.queue";
private static final String CONFIRM_QUEUE_ROUTING_KEY = "key1";
//备份交换机
private static final String BACKUP_EXCHANGE_NAME = "backup.exchange";
private static final String BACKUP_QUEUE_NAME = "backup.queue";
private static final String WARNING_QUEUE_NAME = "warning.queue";
@Bean
public DirectExchange confirmExchange(){
//指定备份交换机 alternate-exchange
return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME).withArgument("alternate-exchange", BACKUP_EXCHANGE_NAME).build();
}
@Bean
public FanoutExchange backupExchange(){
return ExchangeBuilder.fanoutExchange(BACKUP_EXCHANGE_NAME).build();
}
@Bean
public Queue confirmQueue(){
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}
@Bean
public Queue backupQueue(){
return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();
}
@Bean
public Queue warningQueue(){
return QueueBuilder.durable(WARNING_QUEUE_NAME).build();
}
@Bean
public Binding confirmQueueToConfirmExchange(@Qualifier("confirmExchange") Exchange confirmExchange,
@Qualifier("confirmQueue") Queue confirmQueue){
return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_QUEUE_ROUTING_KEY).noargs();
}
@Bean
public Binding backupQueueToBackupExchange(@Qualifier("backupExchange") Exchange backupExchange,
@Qualifier("backupQueue") Queue backupQueue){
return BindingBuilder.bind(backupQueue).to(backupExchange).with("").noargs();
}
@Bean
public Binding warningQueueToBackupExchange(@Qualifier("backupExchange") Exchange backupExchange,
@Qualifier("warningQueue") Queue warningQueue){
return BindingBuilder.bind(warningQueue).to(backupExchange).with("").noargs();
}
}
@Slf4j
@Component
public class WarningListener {
@RabbitListener(queues = {"warning.queue"})
public void receive(Message message, Channel channel){
String msg = new String(message.getBody());
log.info("报警发现非法路由:{},发送消息:{}",
message.getMessageProperties().getReceivedRoutingKey(),
msg);
}
}
测试:
访问浏览器:http://localhost:8081/ttl/confirm/sendMessage/123
注意:
同时配置publisher-returns回退消息和备份交换机时,消息不会回退给消费者,备份交换机的优先级更高
4.惰性队列
一般消息时保存在内存中,惰性队列中的消息将会保存在磁盘中,消息被消费的效率更低,但是占用内存小
声明惰性队列,在声明队列时指定参数:
HashMap<String, Object> argument = new HashMap<>();
argument.put("x-queue-mode", "lazy");
channel.queueDeclare(队列名称, false, false, false, argument);