在用户请求订单系统的时候,订单系统的上限设置了10万个请求,如果超过了这个请求,那么订单系统可能会崩溃,这时候我们使用mq,用户先访问mq,mq在去访问订单系统,mq内部自带排队,这样就可以解决订单系统崩溃的问题,这就是流量削峰
订单系统在调用,库存系统,支付系统的时候,任何一个系统发送了故障都会导致下单失败,
当订单系统使用mq的时候,订单系统完成之后,发送到mq,mq再去监督库存系统,和支付系统,
有一个子系统出现故障,中单用户是无感知的,用户可以正常完成订单,mq会监督子系统的修复,这就是mq的应用解耦
A服务调用B服务的时候,等待处理特别耗时间,
这个时候我们使用mq,A服务调用B服务,B服务先返回一个成功给A,A不需要在继续等待,
B继续处理任务,处理成功把数据发送给mq,然后mq再发送给A服务,这就是mq的异步处理
首先关闭防火墙
systemctl stop firewalld.service
账号admin,密码123456 ,端口号15672,安装rabbitmq
docker run -id --name myrabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123456 -p 5672:5672 -p 15672:15672 rabbitmq:3-management
接下来我们访问浏览器
输入账号密码
接下来我们先看下简单模式,
简单模式就是一个生产者对应一个消费者
开始编写java代码,
在pom.xml引入
<!--rabbitmq 依赖客户端-->
<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>
java 访问rabbitmq的端口是5672
我们先创建生产者
package com.example.demo.mq;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//生产者
public class Producer {
//队列名称
private static final String QUEUE_NAME="hello";
public static void main(String[] args) throws IOException, TimeoutException {
//创建一个连接工厂
ConnectionFactory connectionFactory=new ConnectionFactory();
connectionFactory.setHost("192.168.184.142");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("123456");
//创建连接
Connection connection = connectionFactory.newConnection();
//获取通道
Channel channel = connection.createChannel();
//队列里面的消息是否持久化到(磁盘),默认在内存中 是否用完就删除
boolean durable=false;
//true:队列支持多个消费者消费,false:队列的消息只能有一个消费者消费
boolean exclusive=false;
//最后一个消费者断开连接后,该队列是否自动删除,true:自动删除
boolean autoDelete=false;
//生成一个队列
channel.queueDeclare( QUEUE_NAME, durable, exclusive, autoDelete,null);
//消息内容
String message="你好";
//发送一个消息
channel.basicPublish("", QUEUE_NAME,null,message.getBytes());
System.out.println("消息发送完毕");
}
}
可以看到,hello队列被创建,有1条消息未消费
接下来我们创建消费者
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//消费者
public class Consumer {
//队列名称
private static final String QUEUE_NAME="hello";
public static void main(String[] args) throws IOException, TimeoutException {
//创建一个连接工厂
ConnectionFactory connectionFactory=new ConnectionFactory();
connectionFactory.setHost("192.168.184.142");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("123456");
//创建连接
Connection connection = connectionFactory.newConnection();
//获取通道
Channel channel = connection.createChannel();
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
System.out.println(str);
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答
channel.basicConsume(QUEUE_NAME,true, deliverCallback,cancelCallback);
}
}
可以看到,消费者已经成功消费消息
接下来我们在看下工作模式
工作模式就是一个生产者,对应多个消费者,消费者接收对应的消息
package com.example.demo.mq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* mq工具类
*/
public class RabbitmqUtils {
/**
* 获取通道
* @return
* @throws IOException
* @throws TimeoutException
*/
public static Channel getChannel() throws IOException, TimeoutException {
//创建一个连接工厂
ConnectionFactory connectionFactory=new ConnectionFactory();
connectionFactory.setHost("192.168.184.142");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("123456");
//创建连接
Connection connection = connectionFactory.newConnection();
//获取通道
Channel channel = connection.createChannel();
return channel;
}
}
生产者发送大量的消息
package com.example.demo.mq;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
//生产者
public class Producer {
//队列名称
private static final String QUEUE_NAME="hello";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//队列里面的消息是否持久化到(磁盘),默认在内存中 是否用完就删除
boolean durable=false;
//true:队列支持多个消费者消费,false:队列的消息只能有一个消费者消费
boolean exclusive=false;
//最后一个消费者断开连接后,该队列是否自动删除,true:自动删除
boolean autoDelete=false;
//生成一个队列
channel.queueDeclare( QUEUE_NAME, durable, exclusive, autoDelete,null);
Scanner scanner=new Scanner(System.in);
System.out.println("请输入");
while (scanner.hasNext()){
//消息内容
String message=scanner.next();
//发送一个消息
channel.basicPublish("", QUEUE_NAME,null,message.getBytes());
System.out.println("消息发送完毕"+message);
}
}
}
创建多个消费者接收
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//消费者
public class Consumer {
//队列名称
private static final String QUEUE_NAME="hello";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
System.out.println(str);
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答
channel.basicConsume(QUEUE_NAME,true, deliverCallback,cancelCallback);
System.out.println("消费者1接收消息");
}
}
选中这里,就可以启动多个消费者
接下来在生产者发送消息
可以看到消费者1成功接收aa,cc
消费者2成功消费bb,dd
消费者消费消息的时候,消费者挂掉了,这时候,可能存在消息丢失的问题,rabbitmq引入了消息应答的机制;
消息应答:当消费者成功消费消息后,rabbitmq才把这条消息删除掉。
自动应答:消息者接收消息,就会删除消息,但是在代码还没有处理完,可能会存在消息丢失;
手动应答:消费者接收消息,代码处理完之后,成功消费消息,不会存在消息丢失的问题,在工作中,尽量使用手动应答;
接下来我们看下手动应答的代码
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//消费者 消费失败 重新放入队列 由其他消费者进行消费
public class Consumer {
//队列名称
private static final String QUEUE_NAME="hello";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者1接收到消息:"+str);
//手动应答消息
//第一个参数消息标记 tag
//第二个参数是否批量应答消息,true:批量,false:不是批量
channel.basicAck(var2.getEnvelope().getDeliveryTag(),false);
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答
channel.basicConsume(QUEUE_NAME,false, deliverCallback,cancelCallback);
}
}
package com.example.demo.mq;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rabbitmq.client.Delivery;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//消费者 消费失败 重新放入队列 由其他消费者进行消费
public class Consumer2 {
//队列名称
private static final String QUEUE_NAME="hello";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者2接收到消息:"+str);
//手动应答消息
//第一个参数消息标记 tag
//第二个参数是否批量应答消息,true:批量,false:不是批量
channel.basicAck(var2.getEnvelope().getDeliveryTag(),false);
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答
channel.basicConsume(QUEUE_NAME,false, deliverCallback,cancelCallback);
}
}
然后启动生产者和2个消费者,发送2条消息
我们可以看到队列还有一条消息没有被消费
这时候,我们把消费者2宕机,可以看到,消费者1接收到了消息,这就是手动应答
接下来我们在看下持久化
队列持久化:mq宕机后,如果不开启持久化,那么队列就会丢失,如果开启持久化Features就会显示一个大写的D
消息持久化:mq的消息默认在内存中,当mq宕机后,消息保存在磁盘中,mq启动后,消息恢复
我们先把mq进行重启,可以看到在没有持久化之前,队列已经丢失了
docker stop myrabbit
docker restart myrabbit
接下来我们对生产者的代码进行修改,把箭头这里改成true,报证队列的持久化
接下来我们在重启mq,可以看到队列持久化了,但是消息丢失了
接下来对生产者代码进行修改,在箭头这里加入
MessageProperties.PERSISTENT_TEXT_PLAIN 开启消息的持久化,到磁盘中
接下来我们在重启mq,可以看到消息也恢复了,这就是消息的持久化
接下来我们看下,不公平分发
如果有一个消费者消费快,一个消费者消费慢,那么这时候我想要消费快的那个多干点活,
这就是不公平分发
在消费者1和消费者2代码进行修改,箭头部分加入
channel.basicQos(1);
这时候在启动生产者和2个消费者
可以看到消息,大部分都被消费者1消费了,这就是不公平分发
接下来在看下预取值,生产者发送5条数据
设置消费者1,消费3条数据
channel.basicQos(3);
设置消费者2,消费2条数据
channel.basicQos(2);
不管消费者1消费多块,总有2条数据会是到消费者2的;
在通道这里,就可以看到预取值
消费者1消费3条数据
可以看到消费者2的消息一直在这里堆积
最后消费者2消费2条数据
遇到mq的消息堆积,可以先把消费者的预取值设置为1,不公平分发,这样一个消费者堆积消息,在开启一个新的消费者来消费消息,这样控制台最终会剩下1条消息没有被消费,这时候在控制台点击get message获取消息,手动处理,然后把这条队列删除掉,这样就可以解决消息堆积
接下来我们在看下发布确认
要想真正的保证消息的持久化,消息不丢失,
首先队列持久化,消息持久化,手动应答,发布确认,只有发布确认了,才能保证消息到磁盘上了
单个确认:发送一条,确认一条,速度慢一点
批量确认:批量发送,一次确认,但是有发送出错,无法感知
异步确认:批量发送,异步监听,成功和失败的回调,成功确认删除map中的数据,剩下的就是未确认的数据,失败回调,重新发送对应的消息,建议使用异步回调
接下来看下代码,进行对比
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeoutException;
//生产者
public class Producer {
//队列名称
private static final String QUEUE_NAME="hello";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//单个确认 耗时808毫秒
//singleConfirmation();
//批量确认 耗时28毫秒
//batchConfirmation();
//异步确认 耗时257毫秒
asynchronousConfirmation();
}
//单个确认
public static void singleConfirmation() throws IOException, TimeoutException, InterruptedException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//队列里面的消息是否持久化到(磁盘),默认在内存中 是否用完就删除
boolean durable=true;
//true:队列支持多个消费者消费,false:队列的消息只能有一个消费者消费
boolean exclusive=false;
//最后一个消费者断开连接后,该队列是否自动删除,true:自动删除
boolean autoDelete=false;
//生成一个队列
channel.queueDeclare( QUEUE_NAME, durable, exclusive, autoDelete,null);
//开启发布确认
channel.confirmSelect();
long begin = System.currentTimeMillis();
for (int i = 0; i <1000 ; i++) {
String message="消息内容"+i;
//MessageProperties.PERSISTENT_TEXT_PLAIN 消息持久化到磁盘中
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
//如果返回false,或者超时时间内未返回,可以消息重新发送
boolean flag = channel.waitForConfirms();
if(flag){
System.out.println("消息发送成功");
}
}
long end = System.currentTimeMillis();
System.out.println("发布单独确认消息,耗时" + (end - begin)+"毫秒");
}
//批量确认发布
public static void batchConfirmation() throws IOException, TimeoutException, InterruptedException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//队列里面的消息是否持久化到(磁盘),默认在内存中 是否用完就删除
boolean durable=true;
//true:队列支持多个消费者消费,false:队列的消息只能有一个消费者消费
boolean exclusive=false;
//最后一个消费者断开连接后,该队列是否自动删除,true:自动删除
boolean autoDelete=false;
//生成一个队列
channel.queueDeclare( QUEUE_NAME, durable, exclusive, autoDelete,null);
//开启发布确认
channel.confirmSelect();
long begin = System.currentTimeMillis();
for (int i = 0; i <1000 ; i++) {
String message="消息内容"+i;
//MessageProperties.PERSISTENT_TEXT_PLAIN 消息持久化到磁盘中
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
}
//全部发送完了 在进行确认
//如果返回false,或者超时时间内未返回,可以消息重新发送
boolean flag = channel.waitForConfirms();
if(flag){
System.out.println("消息发送成功");
}
long end = System.currentTimeMillis();
System.out.println("发布批量确认消息,耗时" + (end - begin)+"毫秒");
}
//异步确认
public static void asynchronousConfirmation() throws IOException, TimeoutException, InterruptedException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//队列里面的消息是否持久化到(磁盘),默认在内存中 是否用完就删除
boolean durable=true;
//true:队列支持多个消费者消费,false:队列的消息只能有一个消费者消费
boolean exclusive=false;
//最后一个消费者断开连接后,该队列是否自动删除,true:自动删除
boolean autoDelete=false;
//生成一个队列
channel.queueDeclare( QUEUE_NAME, durable, exclusive, autoDelete,null);
//开启发布确认
channel.confirmSelect();
//将序号和消息进行关联,只要给到序号,批量删除条目, 支持高并发(多线程)
ConcurrentSkipListMap<Long,String>map=new ConcurrentSkipListMap<>();
//成功确认的回调
ConfirmCallback ackCallback=(long var1, boolean var3)->{
System.out.println("成功确认消息"+var1);
//删除确认的消息,剩下的都是未确认的消息
if(var3){
//如果是批量确认 删除批量的消息
ConcurrentNavigableMap<Long, String> longStringConcurrentNavigableMap = map.headMap(var1);
longStringConcurrentNavigableMap.clear();
}else {
//删除单个确认的消息
map.remove(var1);
}
};
//消息确认失败,的回调
ConfirmCallback nackCallback=(long var1, boolean var3)->{
String message = map.get(var1);
System.out.println("未确认的消息是:"+message+",序号:"+var1);
//在这里可以重新发消息
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
};
//异步监听消息的确认
channel.addConfirmListener(ackCallback,nackCallback);
long begin = System.currentTimeMillis();
for (int i = 0; i <1000 ; i++) {
String message="消息内容"+i;
//MessageProperties.PERSISTENT_TEXT_PLAIN 消息持久化到磁盘中
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
//记录发送的消息和序号
map.put(channel.getNextPublishSeqNo(),message);
}
long end = System.currentTimeMillis();
System.out.println("发布异步确认消息,耗时" + (end - begin)+"毫秒");
}
}
临时队列:就是没有持久化,队列名称随机
接下来我们看下发布订阅模式
发布订阅模式就是生产者发送消息,可以多个消费者接收这一条消息
生产者发送给交换机,交换机绑定路由key,到队列,然后再发送到对应的消费者
我们默认的交换机就是AMQP default
接下来我们看下交换机的类型扇出(fanout) ,就是把消息广播到所有队列中
我们来看下代码
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeoutException;
//生产者
public class Producer {
//交换机名称
private static final String EXCHANGE_NAME="jiaohuanji";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明一个交换机,第一个参数是交换机的名称, 第二个参数是交换机的类型
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
Scanner sc = new Scanner(System.in);
System.out.println("请输入信息");
while (sc.hasNext()) {
String message = sc.nextLine();
//发出消息 第二个参数是路由key,可以为空串
channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("UTF-8"));
System.out.println("生产者发出消息" + message);
}
}
}
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//消费者
public class Consumer {
//交换机名称
private static final String EXCHANGE_NAME="jiaohuanji";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明交换机类型
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//声明一个临时的队列名称,消费者和该队列断开连接的时候,自动删除
String queueName=channel.queueDeclare().getQueue();
//把交换机和路由key 对应的队列进行绑定,路由key可以为空
channel.queueBind(queueName, EXCHANGE_NAME, "");
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
System.out.println("消费者1接收到消息:"+str);
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答
channel.basicConsume(queueName,true, deliverCallback,cancelCallback);
}
}
package com.example.demo.mq;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rabbitmq.client.Delivery;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//消费者
public class Consumer2 {
//交换机名称
private static final String EXCHANGE_NAME="jiaohuanji";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明交换机类型
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//声明一个临时的队列名称,消费者和该队列断开连接的时候,自动删除
String queueName=channel.queueDeclare().getQueue();
//把交换机和路由key 对应的队列进行绑定,路由key可以为空
channel.queueBind(queueName, EXCHANGE_NAME, "");
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
System.out.println("消费者2接收到消息:"+str);
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答
channel.basicConsume(queueName,true, deliverCallback,cancelCallback);
}
}
可以看到交换机和队列进行了绑定
生产者发送消息,消费者1,2都接收到了消息
接下来我们在看下直接交换机(direct),就是路由key不一样,如果路由key一样那么就是扇出了
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeoutException;
//生产者
public class Producer {
//交换机名称
private static final String EXCHANGE_NAME="jiaohuanji";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明一个交换机,第一个参数是交换机的名称, 第二个参数是交换机的类型
channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.DIRECT);
Scanner sc = new Scanner(System.in);
System.out.println("请输入信息");
while (sc.hasNext()) {
String message = sc.nextLine();
//发出消息 第二个参数是路由key
channel.basicPublish(EXCHANGE_NAME,"k1",null,message.getBytes("UTF-8"));
System.out.println("生产者发出消息" + message);
}
}
}
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//消费者
public class Consumer {
//交换机名称
private static final String EXCHANGE_NAME="jiaohuanji";
//队列的名称
private static final String QUEUE_NAME="q1";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明交换机类型
channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.DIRECT);
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//把交换机和路由key 对应的队列进行绑定,第三个参数路由key
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "k1");
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
System.out.println("消费者1接收到消息:"+str);
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答
channel.basicConsume(QUEUE_NAME,true, deliverCallback,cancelCallback);
}
}
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//消费者
public class Consumer2 {
//交换机名称
private static final String EXCHANGE_NAME="jiaohuanji";
//队列的名称
private static final String QUEUE_NAME="q2";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明交换机类型
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//把交换机和路由key 对应的队列进行绑定,第三个参数路由key
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "k2");
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
System.out.println("消费者2接收到消息:"+str);
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答
channel.basicConsume(QUEUE_NAME,true, deliverCallback,cancelCallback);
}
}
先把之前的交换机删除掉
接下来我们在看下k1的路由key
可以看到只有消费者1接收到消息了
接下来我们把生产者的代码路由key改成k2,再次启动
可以看到消费者2接收到消息了
在交换机这里就可以看到绑定的信息
这就是交换机的直接类型
接下来我们在看下主题模式,也叫主题交换机
主题交换机的路由key可以使用*表示一个单词,#表示多个单词,进行模糊匹配,包含扇出和直接交换机的功能,主题模式的命名规则 xx.xx.xx 最多不能超过255个字符
先把交换机删除掉
接下来看下代码
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeoutException;
//生产者
public class Producer {
//交换机名称
private static final String EXCHANGE_NAME="jiaohuanji";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明一个交换机,第一个参数是交换机的名称, 第二个参数是交换机的类型
channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.TOPIC);
String str="aa";
String str2="bb";
String str3="cc";
//发出消息 第二个参数是路由key
channel.basicPublish(EXCHANGE_NAME,"aa.11.22",null,str.getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME,"bb.33.44",null,str2.getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME,"bb.77.88.99",null,str3.getBytes("UTF-8"));
System.out.println("生产者发出消息");
}
}
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//消费者
public class Consumer {
//交换机名称
private static final String EXCHANGE_NAME="jiaohuanji";
//队列的名称
private static final String QUEUE_NAME="q1";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明交换机类型
channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.TOPIC);
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//把交换机和路由key 对应的队列进行绑定,第三个参数路由key
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "*.*.22");
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
System.out.println("消费者1接收到消息:"+str+",路由key:"+var2.getEnvelope().getRoutingKey());
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答
channel.basicConsume(QUEUE_NAME,true, deliverCallback,cancelCallback);
}
}
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//消费者
public class Consumer2 {
//交换机名称
private static final String EXCHANGE_NAME="jiaohuanji";
//队列的名称
private static final String QUEUE_NAME="q2";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明交换机类型
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//把交换机和路由key 对应的队列进行绑定,第三个参数路由key
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "bb.#");
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
System.out.println("消费者2接收到消息:"+str+",路由key:"+var2.getEnvelope().getRoutingKey());
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答
channel.basicConsume(QUEUE_NAME,true, deliverCallback,cancelCallback);
}
}
接下来先启动消费者,在启动生产者
消费者2接收到2条
消费者1接收到1条
在这里一定要注意,#结尾可以匹配多个单词,*只能匹配一个
接下来我们看一下死信队列
死信队列就是普通队列没有接收到消息,然后放入到死信队列中
场景有3种
ttl过期,消息被拒绝,队列超过最大长度
接下来我们先看下ttl过期
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeoutException;
//生产者
public class Producer {
//普通交换机的名称
private static final String PT_EXCHANGE="pt_exchange";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明一个交换机,第一个参数是交换机的名称, 第二个参数是交换机的类型
channel.exchangeDeclare(PT_EXCHANGE,BuiltinExchangeType.DIRECT);
//设置消息的ttl过期时间为10秒
AMQP.BasicProperties basicProperties=new AMQP.BasicProperties()
//这里是10000毫秒
.builder().expiration("10000").build();
for (int i = 0; i < 10; i++) {
String message="str"+i;
//发出消息 第二个参数是路由key
channel.basicPublish(PT_EXCHANGE,"pt_key",basicProperties,message.getBytes("UTF-8"));
System.out.println("生产者发出消息");
}
}
}
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
//消费者
public class Consumer {
//普通交换机的名称
private static final String PT_EXCHANGE="pt_exchange";
//普通队列的名称
private static final String PT_QUEUE_NAME="pt_c1";
//死信交换机的名称
private static final String DEAD_EXCHANGE="dead_exchange";
//死信队列的名称
private static final String DEAD_QUEUE_NAME="dead_c1";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明普通交换机类型 和死信交换机类型
channel.exchangeDeclare(PT_EXCHANGE,BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
//声明死信队列
channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);
//绑定死信队列 第三个参数 路由key
channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE, "dead_key");
//普通队列和死信队列进行绑定
Map<String,Object> map=new HashMap<>();
//普通队列设置死信交换机 参数 key 是固定值
map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
//普通队列设置死信 routing-key 参数 key 是固定值
map.put("x-dead-letter-routing-key", "dead_key");
//声明普通队列
channel.queueDeclare(PT_QUEUE_NAME,false,false,false,map);
//绑定普通队列 第三个参数 路由key
channel.queueBind(PT_QUEUE_NAME,PT_EXCHANGE,"pt_key");
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
System.out.println("消费者1接收到消息:"+str);
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答
channel.basicConsume(PT_QUEUE_NAME,true, deliverCallback,cancelCallback);
}
}
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//消费者
public class Consumer2 {
//死信交换机的名称
private static final String DEAD_EXCHANGE="dead_exchange";
//死信队列的名称
private static final String DEAD_QUEUE_NAME="dead_c1";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明交换机类型
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明队列
channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);
//把交换机和路由key 对应的队列进行绑定,第三个参数路由key
channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE, "dead_key");
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
System.out.println("消费者2接收到消息:"+str);
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答
channel.basicConsume(DEAD_QUEUE_NAME,true, deliverCallback,cancelCallback);
}
}
接下来我们先启动消费者1,然后关掉,模拟死信队列
接下来我们在启动生产者的代码,看下10秒钟后是否进入死信队列中,
10秒钟之后果然进入了死信队列中
接下来我们启动消费者2,去进行消费死信队列的数据
可以看到,数据已经被消费
接下来我们在看下,队列超过最大长度,进入死信队列
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeoutException;
//生产者
public class Producer {
//普通交换机的名称
private static final String PT_EXCHANGE="pt_exchange";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明一个交换机,第一个参数是交换机的名称, 第二个参数是交换机的类型
channel.exchangeDeclare(PT_EXCHANGE,BuiltinExchangeType.DIRECT);
for (int i = 0; i < 10; i++) {
String message="str"+i;
//发出消息 第二个参数是路由key
channel.basicPublish(PT_EXCHANGE,"pt_key",null,message.getBytes("UTF-8"));
System.out.println("生产者发出消息");
}
}
}
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
//消费者
public class Consumer {
//普通交换机的名称
private static final String PT_EXCHANGE="pt_exchange";
//普通队列的名称
private static final String PT_QUEUE_NAME="pt_c1";
//死信交换机的名称
private static final String DEAD_EXCHANGE="dead_exchange";
//死信队列的名称
private static final String DEAD_QUEUE_NAME="dead_c1";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明普通交换机类型 和死信交换机类型
channel.exchangeDeclare(PT_EXCHANGE,BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
//声明死信队列
channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);
//绑定死信队列 第三个参数 路由key
channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE, "dead_key");
//普通队列和死信队列进行绑定
Map<String,Object> map=new HashMap<>();
//普通队列设置死信交换机 参数 key 是固定值
map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
//普通队列设置死信 routing-key 参数 key 是固定值
map.put("x-dead-letter-routing-key", "dead_key");
//设置普通队列的长度限制,例如发10个,4个则为死信
map.put("x-max-length",6);
//声明普通队列
channel.queueDeclare(PT_QUEUE_NAME,false,false,false,map);
//绑定普通队列 第三个参数 路由key
channel.queueBind(PT_QUEUE_NAME,PT_EXCHANGE,"pt_key");
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
System.out.println("消费者1接收到消息:"+str);
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答
channel.basicConsume(PT_QUEUE_NAME,true, deliverCallback,cancelCallback);
}
}
注意要把之前的队列删除掉,然后模拟发送10条消息,超过6条的都放入死信队列
先启动消费者1,然后关闭,在启动生产者,可以看到6条在普通队列,
4条在死信队列
如果消费死信队列的数据,还是启动消费者2就可以了
接下来,我们看一下,消息被拒绝,放入死信队列
删除之前的队列
package com.example.demo.mq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
//消费者
public class Consumer {
//普通交换机的名称
private static final String PT_EXCHANGE="pt_exchange";
//普通队列的名称
private static final String PT_QUEUE_NAME="pt_c1";
//死信交换机的名称
private static final String DEAD_EXCHANGE="dead_exchange";
//死信队列的名称
private static final String DEAD_QUEUE_NAME="dead_c1";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道
Channel channel = RabbitmqUtils.getChannel();
//声明普通交换机类型 和死信交换机类型
channel.exchangeDeclare(PT_EXCHANGE,BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
//声明死信队列
channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);
//绑定死信队列 第三个参数 路由key
channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE, "dead_key");
//普通队列和死信队列进行绑定
Map<String,Object> map=new HashMap<>();
//普通队列设置死信交换机 参数 key 是固定值
map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
//普通队列设置死信 routing-key 参数 key 是固定值
map.put("x-dead-letter-routing-key", "dead_key");
//声明普通队列
channel.queueDeclare(PT_QUEUE_NAME,false,false,false,map);
//绑定普通队列 第三个参数 路由key
channel.queueBind(PT_QUEUE_NAME,PT_EXCHANGE,"pt_key");
//回调消息
DeliverCallback deliverCallback=(String var1, Delivery var2)->{
String str=new String(var2.getBody());
if(str.equals("str3")){
//模拟一个消息被拒绝放入死信队列 第二个参数 是否放入普通队列 true:放入,false: 不放入普通队列,如果配置了死信交换机,放入死信队列
channel.basicReject(var2.getEnvelope().getDeliveryTag(),false);
}else {
//手动应答 第二个参数是否批量应答,true:批量 false:单个应答
channel.basicAck(var2.getEnvelope().getDeliveryTag(),false);
System.out.println("消费者1接收到消息:"+str);
}
};
//取消回调
CancelCallback cancelCallback=(String var1)->{
System.out.println("消息消费被中断");
};
//消费消息 true:表示自动应答,false表示手动应答 这里要改成手动应答
channel.basicConsume(PT_QUEUE_NAME,false, deliverCallback,cancelCallback);
}
}
启动生产者,消费者1,可以看到有1条进入了死信队列,启动消费者2就可以把死信队列进行消费
接下来我们在看下延迟队列
延迟队列可以说是在ttl过期的基础上进行的
使用延时队列的场景;
1.说说发送定时任务;
2.会议预定开始前,提前提醒;
3.下单之后,10分钟内未支付;
4.发起退款,30分钟内未响应;
5.领导3天内没有审批;
6.预约成功后,30分钟内,没有签到;
接下来我们创建一个新的springboot项目,先把之前的队列都删除掉
引入pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--RabbitMQ 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--RabbitMQ 测试依赖-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置文件
spring.rabbitmq.host=192.168.184.142
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456
编写mq配置类
package com.example.demo.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
//ttl队列配置类
@Configuration
public class TtlQueueConfig {
//普通交换机
public static final String P_EXCHANGE="p_exchange";
//普通队列
public static final String Q1_QUEUE_NAME="q1";
//普通路由key
public static final String P_ROUTING_KEY="p_key";
//死信交换机
public static final String D_EXCHANGE="d_exchange";
//死信队列
public static final String S1_QUEUE_NAME="s1";
//死信路由key
public static final String D_ROUTING_KEY="d_key";
//声明普通交换机
@Bean
public DirectExchange pExchange(){
return new DirectExchange(P_EXCHANGE);
}
//声明死信交换机
@Bean
public DirectExchange dExchange(){
return new DirectExchange(D_EXCHANGE);
}
//声明普通队列
@Bean
public Queue queueA(){
Map<String,Object> map=new HashMap<>();
//声明当前队列绑定的死信交换机
map.put("x-dead-letter-exchange", D_EXCHANGE);
//声明当前队列的死信路由 key
map.put("x-dead-letter-routing-key", D_ROUTING_KEY);
//这里不需要设置ttl,我们在生产者发送消息的时候设置
//构建队列
return QueueBuilder.durable(Q1_QUEUE_NAME).withArguments(map).build();
}
//声明死信队列
@Bean
public Queue queueB(){
return new Queue(S1_QUEUE_NAME);
}
//绑定普通队列和普通交换机 普通路由key
@Bean
public Binding queryBindA(@Qualifier("queueA") Queue queue,
@Qualifier("pExchange") DirectExchange pExchange){
return BindingBuilder.bind(queue).to(pExchange).with(P_ROUTING_KEY);
}
//绑定死信队列和死信交换机 和死信路由key
@Bean
public Binding queryBindB(@Qualifier("queueB") Queue queue,
@Qualifier("dExchange") DirectExchange dExchange){
return BindingBuilder.bind(queue).to(dExchange).with(D_ROUTING_KEY);
}
}
创建生产者
package com.example.demo.controller;
import com.example.demo.config.TtlQueueConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
public class TestController {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 模拟延时队列
* @param str 内容
* @param ttl 延时的毫秒
*/
@GetMapping("send/{str}/{ttl}")
public void send(@PathVariable("str") String str,@PathVariable("ttl") String ttl){
//普通交换机
String exchange= TtlQueueConfig.P_EXCHANGE;
//普通路由key
String routingKey=TtlQueueConfig.P_ROUTING_KEY;
rabbitTemplate.convertAndSend(exchange,routingKey,str,x->{
//设置消息的过期时间
x.getMessageProperties().setExpiration(ttl);
return x;
});
System.out.println("当前时间:"+new Date()+",发送内容:"+str+",延时时间"+ttl+"毫秒");
}
}
创建消费者
package com.example.demo.config;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Date;
//消费者
@Component
public class Consumer {
//监听死信队列
@RabbitListener(queues = TtlQueueConfig.S1_QUEUE_NAME)
public void receive(Message message, Channel channel){
System.out.println("接收到消息,当前时间:"+new Date()+",内容:"+new String(message.getBody()));
}
}
启动项目,浏览器输入这2个请求
一个模拟20秒
localhost:8080/send/nihao/20000
一个模拟2秒
localhost:8080/send/nihao3/2000
可以看到延时20秒的成功了,但是延时2秒的在没有先出来,是因为在mq的队列中,先进先出,
所以设置短时间的也要等前面的出去了,才能往下走。
接下来我们进行优化,在mq中安装延时队列插件
打开Community Plugins — RabbitMQ
下载rabbitmq_delayed_message_exchange插件
选择你的mq的版本,否则会报下面的错误
Enabling plugins on node rabbit@99f955bd4205:
rabbitmq_delayed_message_exchange
Error:
Failed to enable some plugins:
rabbitmq_delayed_message_exchange:
Plugin doesn't support current server version. Actual broker version: "3.9.11", supported by the plugin: ["3.11.0-3.11.x"]
我的版本是3.9.11所以,我选择3.9.0
进入容器的plugins目录就可以看到是使用的哪个版本
把文件上传到服务器
然后执行下面的命令
拷贝到容器里面
docker cp rabbitmq_delayed_message_exchange-3.9.0.ez myrabbit:/plugins
进入容器里面
docker exec -it myrabbit /bin/bash
cd plugins
安装
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
出现下面的就表示安装成功了
然后退出容器,重启mq
exit
docker restart myrabbit
然后看下控制台出现这个箭头指向的类型, 表示已经安装成功
接下来我们在对代码进行下改进
新建mq配置类
package com.example.demo.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* 延迟队列配置
*/
@Configuration
public class DelayedQueueConfig {
//延迟交换机名称
public static final String DE_EXCHANGE="de_exchange";
//延迟队列的名称
public static final String DE_QUEUE_NAME="de_q";
//延迟路由key
public static final String DE_ROUTING_KEY="de_key";
//声明队列
@Bean
public Queue queueC(){
return new Queue(DE_QUEUE_NAME);
}
/**
* 自定义交换机 该类型消息支持延迟投递,消息传递后,并不会立即投递到目标队列中,而是存储在一个分布式数据系统表(mnesia)中,当达到
* 投递时间时,才投递到目标队列中
* @return
*/
@Bean
public CustomExchange customExchange(){
Map<String,Object> map=new HashMap<>();
//自定义交换机类型
map.put("x-delayed-type","direct");
//x-delayed-message 交换机类型 固定名称
return new CustomExchange(DE_EXCHANGE,"x-delayed-message",true,false,map);
}
//绑定队列和交换机和路由key
@Bean
public Binding bindingC(@Qualifier("queueC")Queue queue,
@Qualifier("customExchange")CustomExchange customExchange){
return BindingBuilder.bind(queue).to(customExchange).with(DE_ROUTING_KEY).noargs();
}
}
创建生产者
package com.example.demo.controller;
import com.example.demo.config.DelayedQueueConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
public class TestController {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 模拟延时队列
* @param str 内容
* @param ttl 延时的毫秒
*/
@GetMapping("send/{str}/{ttl}")
public void send(@PathVariable("str") String str,@PathVariable("ttl") Integer ttl){
//交换机
String exchange= DelayedQueueConfig.DE_EXCHANGE;
//路由key
String routingKey=DelayedQueueConfig.DE_ROUTING_KEY;
rabbitTemplate.convertAndSend(exchange,routingKey,str,x->{
//设置消息的过期时间
x.getMessageProperties().setDelay(ttl);
return x;
});
System.out.println("当前时间:"+new Date()+",发送内容:"+str+",延时时间"+ttl+"毫秒");
}
}
创建消费者
package com.example.demo.config;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Date;
//消费者
@Component
public class Consumer {
//监听延迟队列
@RabbitListener(queues = DelayedQueueConfig.DE_QUEUE_NAME)
public void receive(Message message, Channel channel){
System.out.println("接收到消息,当前时间:"+new Date()+",内容:"+new String(message.getBody()));
}
}
启动项目,再次浏览器访问
一个模拟20秒
localhost:8080/send/nihao/20000
一个模拟2秒
localhost:8080/send/nihao3/2000
可以看到,2秒的先打印出来,20秒的,等一会才显示
在代码中使用延时队列,就使用延时队列插件来比较高效
接下来我们看下发布确认高级
mq重启导致生产者发送消息失败,消息丢失,我们来看下代码,先把之前的队列都删掉
在配置文件添加
spring.rabbitmq.host=192.168.184.145
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456
#发布消息成功后,到交换机,触发回调方法 就是会调用MyCallback类的confirm方法
spring.rabbitmq.publisher-confirm-type=correlated
创建配置类
package com.example.demo.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//mq配置
@Configuration
public class ConfirmConfig {
//交换机名称
public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
//队列名称
public static final String CONFIRM_QUEUE_NAME = "confirm.queue";
//路由key
public static final String ROUTING_KEY = "ke1";
//声明业务 Exchange
@Bean
public DirectExchange confirmExchange() {
return new DirectExchange(CONFIRM_EXCHANGE_NAME);
}
// 声明确认队列
@Bean
public Queue confirmQueue() {
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}
// 声明确认队列绑定关系
@Bean
public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
@Qualifier("confirmExchange") DirectExchange exchange) {
//队列和交换机,路由key绑定
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
}
}
创建交换机回调类
package com.example.demo.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Slf4j
@Component
public class MyCallback implements RabbitTemplate.ConfirmCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
//把当前类注入到回调中
rabbitTemplate.setConfirmCallback(this);
}
/**
* 交换机不管是否收到消息的一个回调方法
* @param correlationData 消息相关数据
* @param b 交换机是否收到消息 true:收到, false:未收到
* @param s 未收到的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
String id="";
if(correlationData!=null){
id=correlationData.getId();
}
if(b){
log.info("交换机收到消息,id为{}:",id);
}else {
log.info("交换机没有收到消息,id为{id},原因是:{}",id,s);
}
}
}
创建生产者
package com.example.demo.controller;
import com.example.demo.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/**
* 生产者
*/
@Slf4j
@RestController
public class TestController {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 消息回调和退回
* @param str 内容
*/
@GetMapping("send/{str}")
public void send(@PathVariable("str") String str){
//指定消息id为1
CorrelationData correlationData=new CorrelationData("1");
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,ConfirmConfig.ROUTING_KEY,str,correlationData);
log.info("发送消息内容:{},路由key:{}",str,ConfirmConfig.ROUTING_KEY);
//发送一个错误的路由key
//指定消息id为2
CorrelationData correlationData2=new CorrelationData("2");
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,"key2",str,correlationData2);
log.info("发送消息内容:{},路由key:{}",str,"key2");
}
}
创建消费者
package com.example.demo.init;
import com.example.demo.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 消费者
*/
@Component
@Slf4j
public class Consumer {
//监听队列
@RabbitListener(queues = ConfirmConfig.CONFIRM_QUEUE_NAME)
public void receive(Message message){
String str=new String(message.getBody());
log.info("消费者接收到消息:{}",str);
}
}
访问浏览器
localhost:8080/send/你好
可以看到,发送了2条消息,消费者只接收了1条,另一个不存在的路由key,发送的消息,就丢失了
消息丢失,交换机是不知道的,我们要告诉生产者
接下来进行,回退消息
在配置文件加入
spring.rabbitmq.host=192.168.184.145
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456
#发布消息成功后,到交换机,触发回调方法 就是会调用MyCallback类的confirm方法
spring.rabbitmq.publisher-confirm-type=correlated
#消息退回 触发MyCallback类的returnedMessage方法
spring.rabbitmq.publisher-returns=true
修改回调类,加入回退
package com.example.demo.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* 回调类
*/
@Slf4j
@Component
public class MyCallback implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
//把当前类注入到回调中
rabbitTemplate.setConfirmCallback(this);
//把当前类注入到回退中
rabbitTemplate.setReturnsCallback(this);
}
/**
* 交换机不管是否收到消息的一个回调方法
* @param correlationData 消息相关数据
* @param b 交换机是否收到消息 true:收到, false:未收到
* @param s 未收到的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
String id="";
if(correlationData!=null){
id=correlationData.getId();
}
if(b){
log.info("交换机收到消息,id为{}:",id);
}else {
log.info("交换机没有收到消息,id为{id},原因是:{}",id,s);
}
}
/**
* 当消息无法路由的时候,回调方法,把消息退回给生产者
* 就是路由key,发送消息,消费者接收不到,消息退回的方法
* @param returnedMessage
*/
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
//消息内容
String str=new String(returnedMessage.getMessage().getBody());
log.info("消息内容:{},被交换机:{},退回,原因是:{},路由key是:{},code:{}",str,returnedMessage.getExchange(),returnedMessage.getReplyText(),
returnedMessage.getRoutingKey(),returnedMessage.getReplyCode());
}
}
再次执行
localhost:8080/send/你好
可以看到,错误的路由key,发送消息失败,被退回了
虽然消息回退了,但是怎么处理这些消息呢?
我们来创建一个备份交换机,备份交换机的类型为扇出(fanout),就是广播的意思,
当路由key发送失败,都进入备份交换机,
这样,备份交换机的路由key,都可以为空串,然后把消息发送到绑定的队列,
当然我们还可以建立一个报警队列,用来独立的消费者进行监测和报警
接下来我们对mq的配置类进行修改,先把交换机和队列都删除掉,防止报错
package com.example.demo.config;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//mq配置
@Configuration
public class ConfirmConfig {
//交换机名称
public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
//队列名称
public static final String CONFIRM_QUEUE_NAME = "confirm.queue";
//路由key
public static final String ROUTING_KEY = "ke1";
//备份交换机
public static final String BACKUP_EXCHANGE_NAME = "backup.exchange";
//备份普通队列
public static final String BACKUP_QUEUE_NAME = "backup.queue";
//备份报警队列
public static final String WARNING_QUEUE_NAME = "warning.queue";
// //声明业务 Exchange
// @Bean
// public DirectExchange confirmExchange() {
// return new DirectExchange(CONFIRM_EXCHANGE_NAME);
// }
// 声明确认队列
@Bean
public Queue confirmQueue() {
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}
// 声明确认队列绑定关系
@Bean
public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
@Qualifier("confirmExchange") DirectExchange exchange) {
//队列和交换机,路由key绑定
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
}
//声明备份交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange(BACKUP_EXCHANGE_NAME);
}
//声明备份普通队列
@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 backupBind(@Qualifier("backupQueue")Queue queue,
@Qualifier("fanoutExchange")FanoutExchange fanoutExchange){
return BindingBuilder.bind(queue).to(fanoutExchange);
}
//声明报警队列和备份交换机绑定
@Bean
public Binding warningBind(@Qualifier("warningQueue")Queue queue,
@Qualifier("fanoutExchange")FanoutExchange fanoutExchange){
return BindingBuilder.bind(queue).to(fanoutExchange);
}
//声明普通交换机的备份交换机
@Bean
public DirectExchange confirmExchange(){
ExchangeBuilder exchangeBuilder=ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME)
.durable(true)
//设置该交换机的备份交换机 alternate-exchange 固定的key 不可以改动
.withArgument("alternate-exchange",BACKUP_EXCHANGE_NAME);
return exchangeBuilder.build();
}
}
再次发送
localhost:8080/send/你好
可以看到,错误的路由key发送到了备份交换机,备份交换机发送到了绑定的队列
因为备份交换机的优先级比较高,所以Mycallback类的returnedMessage回退消息的方法就不会在触发了,因为扇出(fanout)类型的交换机是广播,所以多个消费者可以接收到消息
接下来我们看下消息幂等性
什么是幂等性,就是一条消息,被重复消费了,但是还没有签收消息,这时候消费者挂掉了,
然后消费者启动之后,再次收到了这条消息,这就是消息幂等,接下来代码演示一遍
之前的队列,交换机都删除掉
创建mq配置类
package com.example.demo.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//mq配置
@Configuration
public class MqConfig {
//交换机名称
public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
//队列名称
public static final String CONFIRM_QUEUE_NAME = "confirm.queue";
//路由key
public static final String ROUTING_KEY = "ke1";
//声明业务 Exchange
@Bean
public DirectExchange confirmExchange() {
return new DirectExchange(CONFIRM_EXCHANGE_NAME);
}
// 声明确认队列
@Bean
public Queue confirmQueue() {
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}
// 声明确认队列绑定关系
@Bean
public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
@Qualifier("confirmExchange") DirectExchange exchange) {
//队列和交换机,路由key绑定
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
}
}
创建生产者
package com.example.demo.controller;
import com.example.demo.config.MqConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.UUID;
/**
* 生产者
*/
@Slf4j
@RestController
public class TestController {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 消息回调和退回
* @param str 内容
*/
@GetMapping("send/{str}")
public void send(@PathVariable("str") String str){
//指定随机消息id
String messageId=UUID.randomUUID().toString();
//把消息id,放入消息中
Message message = MessageBuilder.withBody(str.getBytes())
.setMessageId(messageId).build();
rabbitTemplate.convertAndSend(MqConfig.CONFIRM_EXCHANGE_NAME,MqConfig.ROUTING_KEY,message);
log.info("发送消息内容:{},路由key:{},消息id:{}",str,MqConfig.ROUTING_KEY,messageId);
}
}
创建消费者
package com.example.demo.init;
import com.example.demo.config.MqConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* 消费者
*/
@Component
@Slf4j
public class Consumer {
//监听队列
@RabbitListener(queues = MqConfig.CONFIRM_QUEUE_NAME)
public void receive(Message message, Channel channel){
//消息属性
MessageProperties messageProperties = message.getMessageProperties();
//消息id
String messageId =messageProperties.getMessageId();
//消息内容
String str=new String(message.getBody());
log.info("消费者接收到消息:{},消息id为:{}",str,messageId);
try {
//模拟耗时
try {
Thread.sleep(20*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//手动应答 false表示单个应答 true表示批量应答
channel.basicAck(messageProperties.getDeliveryTag(),false);
} catch (IOException e) {
e.printStackTrace();
}
}
}
配置文件开启手动应答
spring.rabbitmq.host=192.168.184.145
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456
#发布消息成功后,到交换机,触发回调方法 就是会调用MyCallback类的confirm方法
#spring.rabbitmq.publisher-confirm-type=correlated
#消息退回 触发MyCallback类的returnedMessage方法
#spring.rabbitmq.publisher-returns=true
#消息开启手动确认
spring.rabbitmq.listener.direct.acknowledge-mode=manual
然后浏览器打开
localhost:8080/send/你好
收到消息后,立马关闭项目,可以看到有1条消息,没有被签收
我们在启动项目,可以看到消费者再次消息了这条消息,这就是重复消费,也叫幂等性
那怎么解决呢?我们在服务器装一下redis
docker run -d -p 6379:6379 --name redis-node-1 -v /data/redis/share/redis-node-1:/data --privileged=true redis
利用setnx的原子性,判断key是否存在,如果存在返回0,如果不存在,就创建一条数据,返回1
在pom.xml加入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置文件
spring.rabbitmq.host=192.168.184.145
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456
#发布消息成功后,到交换机,触发回调方法 就是会调用MyCallback类的confirm方法
#spring.rabbitmq.publisher-confirm-type=correlated
#消息退回 触发MyCallback类的returnedMessage方法
#spring.rabbitmq.publisher-returns=true
#消息开启手动确认
spring.rabbitmq.listener.direct.acknowledge-mode=manual
spring.redis.host=192.168.184.145
spring.redis.port=6379
修改消费者
package com.example.demo.init;
import com.example.demo.config.MqConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* 消费者
*/
@Component
@Slf4j
public class Consumer {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//监听队列
@RabbitListener(queues = MqConfig.CONFIRM_QUEUE_NAME)
public void receive(Message message, Channel channel){
//消息属性
MessageProperties messageProperties = message.getMessageProperties();
//消息id
String messageId =messageProperties.getMessageId();
//把消息id 当作key和value,
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(messageId, messageId);
if(!flag){
//如果返回false,说明存在
//模拟从数据库查询出来的数据,如果没有进行数据库操作,可以继续往下走
int count=1;
if(count>0){
//数据库已存在,业务已经被处理
return;
}
}
//如果不存在,那么会自动添加到redis一条消息
//消息内容
String str=new String(message.getBody());
log.info("消费者接收到消息:{},消息id为:{}",str,messageId);
try {
//模拟耗时
try {
Thread.sleep(20*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//手动应答 false表示单个应答 true表示批量应答
channel.basicAck(messageProperties.getDeliveryTag(),false);
} catch (IOException e) {
e.printStackTrace();
}
}
}
浏览器打开
localhost:8080/send/你好
执行后立马关闭项目,然后再次启动项目,就可以看到,重复的消息,已经被拦截了,并且重复的消息也被签收了,这就解决了消息幂等
接下来我们在看下优先级队列,就是设置在生产者设置优先级,和队列进行绑定,
然后在消费者接收的时候,优先级比较高的,先消费
package com.example.demo.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
//mq配置
@Configuration
public class MqConfig {
//交换机名称
public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
//队列名称
public static final String CONFIRM_QUEUE_NAME = "confirm.queue";
//路由key
public static final String ROUTING_KEY = "ke1";
//声明业务 Exchange
@Bean
public DirectExchange confirmExchange() {
return new DirectExchange(CONFIRM_EXCHANGE_NAME);
}
// 声明确认队列
@Bean
public Queue confirmQueue() {
//设置队列的优先级,最大可以设置255,官网推荐设置1-10,如果设置太高比较吃内存和cpu
Map<String, Object> params = new HashMap();
params.put("x-max-priority", 10);
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).withArguments(params).build();
}
// 声明确认队列绑定关系
@Bean
public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
@Qualifier("confirmExchange") DirectExchange exchange) {
//队列和交换机,路由key绑定
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
}
}
package com.example.demo.init;
import com.example.demo.config.MqConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* 消费者
*/
@Component
@Slf4j
public class Consumer {
//监听队列
@RabbitListener(queues = MqConfig.CONFIRM_QUEUE_NAME)
public void receive(Message message){
//消息内容
String str=new String(message.getBody());
log.info("消费者接收到消息:{}",str);
}
}
package com.example.demo.controller;
import com.example.demo.config.MqConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.UUID;
/**
* 生产者
*/
@Slf4j
@RestController
public class TestController {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息
*/
@GetMapping("send")
public void send(){
for (int i = 0; i < 10; i++) {
if(i==6){
//模拟第6个消息,先消费
rabbitTemplate.convertAndSend(MqConfig.CONFIRM_EXCHANGE_NAME,MqConfig.ROUTING_KEY,i+"",x->{
//设置优先级为6
x.getMessageProperties().setPriority(6);
return x;
});
}else {
rabbitTemplate.convertAndSend(MqConfig.CONFIRM_EXCHANGE_NAME,MqConfig.ROUTING_KEY,i+"");
}
}
log.info("发送成功");
}
}
先把之前的交换机,队列删除掉,否则报错
消费者先把监听注释掉,先发送生产者消息,让消息先有个排序的时间
浏览器打开
localhost:8080/send
然后开启消费者,可以看到6,被先消费了
接下来我们看下mq集群,先把之前的mq删除掉
配置文件修改
#rabbitmq集群配置
spring.rabbitmq.addresses=192.168.184.145:8071,192.168.184.143:8072,192.168.184.144:8073
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
参考这篇文章安装好集群
安装好之后就可以通过任意端口访问
http://192.168.184.145:8081/#/
http://192.168.184.145:8082/#/
http://192.168.184.145:8083/#/
在别的端口也能看到其他的队列消息
接下来我们把mq1宕机,可以在其他端口上看到,mq1显示宕机
接下来我们看下镜像队列
就是mq1的队列挂掉了,会自动备份到其他节点上
在admin->策略这里
backup-name 随机起的名字
Pattern是规则的意思,队列的名字以^backup开头
ha-mode:exactly
ha-params:2
ha-sync-mode:automatic
自动备份,主机1份,从机1份
添加之后,就会显示一个策略
接下来我们在发送一个消息,队列这里有一个加1的操作,就是备份了,点进去
可以看到在mq3上备份了一份
接下来我们把mq1宕机,可以看到在mq2上备份了一份
接下来我们在把mq1的机器启动起来,并进行消费,可以看到,已经把数据给消费掉了
我们还可以在其他节点的控制台,获取消息