RabbitMQ
一 RabbitMQ介绍
1.1 介绍
MQ全称为Message Queue,即消息队列, RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。
RabbitMQ官方地址:http://www.rabbitmq.com/
开发中消息队列通常有如下应用场景:
- 任务异步处理。
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
- 应用程序解耦合
MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。
- 程序削峰
1.2 市场上常见MQ
ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ、Redis
1.3 使用RabbitMQ的原因
-
使得简单,功能强大。
-
基于AMQP协议。
-
社区活跃,文档完善。
-
高并发性能好,这主要得益于Erlang语言。
-
Spring Boot默认已集成RabbitMQ
1.4 其它相关知识
1.4.1 AMQP是什么?
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等
1.4.2 JMS是什么?
Java 消息服务(Java Message Service,JMS)应用程序接口是一个Java 平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java 消息服务是一个与具体平台无关的 API,绝大多数 MOM 提供商都对 JMS 提供支持。
二 RabbitMQ入门
2.1 RabbitMQ 的工作原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6L0Fgrki-1635666093860)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028205623965.png)]
组成部分说明如下:
-
Broker :消息队列服务进程,此进程包括两个部分:Exchange和Queue。
-
Exchange :消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
-
Queue :消息队列,存储消息的队列,消息到达队列并转发给指定的消费方。
-
Producer :消息生产者,即生产方客户端,生产方客户端将消息发送到MQ。
-
Consumer :消息消费者,即消费方客户端,接收MQ转发的消息。
消息发布接收流程:
-----发送消息-----
-
生产者和MQ建立TCP连接。
-
生产者和MQ建立通道。
-
生产者通过通道消息发送给MQ,由Exchange将消息进行转发。
-
Exchange将消息转发到指定的Queue(队列)
----接收消息-----
-
消费者和MQ建立TCP连接
-
消费者和MQ建立通道
-
消费者监听指定的Queue(队列)
-
当有消息到达Queue时MQ默认将消息推送给消费者。
-
消费者接收到消息。
2.2 RabbitMQ 下载安装
RabbitMQ由Erlang语言开发,Erlang语言用于并发及分布式系统的开发,在电信领域应用广泛,OTP(Open
Telecom Platform)作为Erlang语言的一部分,包含了很多基于Erlang开发的中间件及工具库,安装RabbitMQ需
要安装Erlang/OTP,并保持版本匹配,如下图:
RabbitMQ的下载地址:https://www.rabbitmq.com/which-erlang.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tnimqPlc-1635666093877)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028210457430.png)]
- 下载erlang
地址如下:
http://erlang.org/download/otp_win64_20.3.exe
-
正常步骤傻子安装,下一步下一步就行。
-
安装好后不要忘了配置一个path变量:ERLANG_HOME=自己的安装路径在path中添加%ERLANG_HOME%\bin;
-
最后在cmd中测试是否安装成功输入erl回车,接收到(Eshell V9.3 (abort with ^G))为安装成功
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OJRH2pr9-1635666093882)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028210903636.png)]
- 安装RabbitMQ
https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.7.3
-
软件安装找样下一步下一步就行
-
安装好后 ,进入cmd ,到rabbitmq安装路径下的sbin目录下,先不要着急看第三步:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GruFxftZ-1635666093886)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028211034542.png)]
-
执行命令:(注下面的前两步是在计算机名为中文的情况下)
- 执行 rabbitmq-service.bat remove 如: 后面一样
- 执行 set RABBITMQ_BASE=D:\rabbitmq_server\data(路径自己定义)
- 执行 rabbitmq-service.bat install
- 执行 rabbitmq-plugins enable rabbitmq_management
-
执行完上面的代码:在服务中找到, rabbitmq服务,重启
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HY7ibPmc-1635666093890)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028211237791.png)]
- 进入浏览器,输入:http://localhost:15672,初始账号和密码都是guest
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Anl16iTO-1635666093893)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028211422670.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-38mIW2JP-1635666093896)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028211518226.png)]
2.3 RabbitMQ Hello World
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qGcT7qze-1635666093898)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028211844261.png)]
2.3.1 搭建环境
创建maven工程,加入RabbitMQ java client的依赖。
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.0.3</version><!--此版本与spring boot 1.5.6版本匹配-->
</dependency>
2.3.2 创建RabbitMQ连接的工具类
public class ConnectionUtil {
public static Connection createCon() throws Exception{
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//指定ip地址
factory.setHost("127.0.0.1");
//指定端口号(一般固定为5672)
factory.setPort(5672);
//用户名,默认guest
factory.setUsername("guest");
//密码,默认guest
factory.setPassword("guest");
//虚拟主机,默认为/
factory.setVirtualHost("/");
//创建连接
Connection connection = factory.newConnection();
return connection;
}
}
2.3.3 生产者发送消息
public class MyProducer {
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.createCon();
//创建连接通道
Channel channel = connection.createChannel();
//声明队列,包括队列名称、是否持久化、队列是否独占此连接、队列不再使用时是否自动删除此队列、队列参数
channel.queueDeclare("hello_queue",true,false,false,null);
//声明一个消息
String msg = "你好RabbitMQ"+new Date();
//发布消息,包括交换机名字、路由名(没有交换机就写队列名)、消息包含的属性、消息体
channel.basicPublish("","hello_queue",null,msg.getBytes());
//关闭连接
channel.close();
connection.close();
}
}
启动之后:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iy2mCVaX-1635666093900)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028213240240.png)]
点击查看详情
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nUlvq89H-1635666093902)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028213341474.png)]
2.3.4 消费者接收消息
public class MyConsumer {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.createCon();
Channel channel = connection.createChannel();
channel.queueDeclare("hello_queue",true,false,false,null);
//定义消费方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//消息内容转字符集
String str = new String(body,"UTF-8");
System.out.println(str);
}
};
//监听队列,包括队列名称、是否自动消费、消费信息的方法
channel.basicConsume("hello_queue",true,defaultConsumer);
}
}
启动监听信息:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ce5D7Gss-1635666093904)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028213711618.png)]
这时rabbitMQ中的消息就没有了(被消费了)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q97TKV2D-1635666093905)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028213817266.png)]
2.3.5 总结
-
发送端操作流程
- 创建连接
- 创建通道
- 声明队列
- 发送消息
-
接收端操作流程
- 创建连接
- 创建通道
- 声明队列
- 声明消费方法
- 监听队列
- 接收消息
- ack回复
三 RabbitMQ 工作模式
RabbitMQ有以下几种工作模式 :
-
Work queues
-
Publish/Subscribe
-
Routing
-
Topics
-
Header
-
RPC
3.1 Work queues(工作模式)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fKbIY6yV-1635666093907)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028214256289.png)]
work queues与入门程序相比,多了一个消费端,两个消费端共同消费同一个队列中的消息。
应用场景:对于 任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
测试:
-
使用入门程序,启动多个消费者。
-
生产者发送多个消息。
3.1.1 生产者(发送多条消息)
public class MyProducer {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.createCon();
Channel channel = connection.createChannel();
channel.queueDeclare("work_queue",true,false,false,null);
//发送十条消息
for (int i = 0; i < 10; i++) {
String msg = "hello"+(i+1)+new Date();
channel.basicPublish("","work_queue",null,msg.getBytes());
}
channel.close();
connection.close();
}
}
3.1.2 消费者(两个)
public class MyConsumer {
public static void main(String[] args) throws Exception {
System.out.println("工作模式客户端一");
Connection connection = ConnectionUtil.createCon();
Channel channel = connection.createChannel();
channel.queueDeclare("work_queue",true,false,false,null);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String str = new String(body,"UTF-8");
System.out.println(str);
}
};
channel.basicConsume("work_queue",true,defaultConsumer);
}
}
public class MyConsumer2 {
public static void main(String[] args) throws Exception {
System.out.println("工作模式客户端二");
Connection connection = ConnectionUtil.createCon();
Channel channel = connection.createChannel();
channel.queueDeclare("work_queue",true,false,false,null);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String str = new String(body,"UTF-8");
System.out.println(str);
}
};
channel.basicConsume("work_queue",true,defaultConsumer);
}
}
3.1.3 结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IJY3fths-1635666093909)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028214842713.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8IiSmV2q-1635666093911)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028214853630.png)]
结果:
-
一条消息只会被一个消费者接收;
-
rabbit采用轮询的方式将消息是平均发送给消费者的;
-
消费者在处理完某条消息后,才会收到下一条消息
3.2 Publish/Subscribe(订阅模式)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7owTPH6n-1635666093912)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028215033712.png)]
发布订阅模式:
1、每个消费者监听自己的队列。
2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收
到消息
3.2.1 生产者(发送给交换机)
public class MyProducer {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.createConn();
Channel channel = connection.createChannel();
//创建交换器,包括交换机名、发送方式选择(fanout)
channel.exchangeDeclare("myfanout", BuiltinExchangeType.FANOUT);
channel.queueDeclare("email.fanout", true, false, false, null);
channel.queueDeclare("sms.fanout", true, false, false, null);
//将交换机和队列绑定,包括队列名、交换机名、路由Key
channel.queueBind("email.fanout", "myfanout", "");
channel.queueBind("sms.fanout", "myfanout", "");
String msg = "用户注册成功,开始发送短信和邮件,用户的手机号是:131....";
channel.basicPublish("myfanout", "", null, msg.getBytes());
channel.close();
connection.close();
}
}
3.2.2 消费者(邮件和短信)
public class MyEmailConsumer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }; channel.basicConsume("email.fanout", true, defaultConsumer); }}
public class MySmsConsumer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }; channel.basicConsume("sms.fanout", true, defaultConsumer); }}
3.2.3 结果
生产者发送消息,邮件和短信都能收到
3.3 Routing(路由模式)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mQonDXUi-1635666093915)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028215958965.png)]
路由模式:
1、每个消费者监听自己的队列,并且设置routingkey。
2、生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列
3.3.1 生产者(指定路由名称)
public class MyProducer { public static void main(String[] args) throws Exception { Connection connection = ConnectionUtil.createConn(); Channel channel = connection.createChannel(); //发送方式选择(direct) channel.exchangeDeclare("mydirect", BuiltinExchangeType.DIRECT); channel.queueDeclare("email.direct", true, false, false, null); channel.queueDeclare("sms.direct", true, false, false, null); //设置路由名称 channel.queueBind("email.direct", "mydirect", "email.direct.routingkey"); channel.queueBind("sms.direct", "mydirect", "sms.direct.routingkey"); //发送给邮件 String msg = "用户注册成功,开始发送邮件,邮件地址是:xxx@163.com"; channel.basicPublish("mydirect", "email.direct.routingkey", null, msg.getBytes()); //发送给短信 msg = "用户注册成功,开始发送短信,手机号码是:131......"; channel.basicPublish("mydirect", "sms.direct.routingkey", null, msg.getBytes()); channel.close(); connection.close(); }}
3.3.2 消费者(邮件和短信)
public class MyEmailConsumer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }; channel.basicConsume("email.direct", true, defaultConsumer); }}
public class MySmsConsumer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }; channel.basicConsume("sms.direct", true, defaultConsumer); }}
3.3.3 结果
生产者发送消息,邮件和短信只能收到各自消息
3.4 Topics(路由模式,常用)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6CH32xRd-1635666093917)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211028220745259.png)]
路由模式:
1、每个消费者监听自己的队列,并且设置带统配符的routingkey。
2、生产者将消息发给broker,由交换机根据routingkey来转发消息到指定的队列。
3.4.1 生产者
public class MyProducer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); //共同消息 String msg = "短信和邮箱相同的信息"; channel.basicPublish("mytopic", "inform.email.sms", null, msg.getBytes()); //邮箱独有 msg = "邮箱独有信息"; channel.basicPublish("mytopic", "inform.email", null, msg.getBytes()); //短信独有 msg = "短信独有信息"; channel.basicPublish("mytopic", "inform.sms", null, msg.getBytes()); }}
3.4.2 消费者(使用占位符)
public class MyEmailConsumer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); //发送方式选择(topic) channel.exchangeDeclare("mytopic", BuiltinExchangeType.TOPIC); channel.queueDeclare("email.topic", true, false, false, null); //路由使用占位符表示 channel.queueBind("email.topic", "mytopic", "inform.#.email.#"); DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }; channel.basicConsume("email.topic", true, defaultConsumer); }}
public class MySmsConsumer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); channel.exchangeDeclare("mytopic", BuiltinExchangeType.TOPIC); channel.queueDeclare("sms.topic", true, false, false, null); channel.queueBind("sms.topic", "mytopic", "inform.#.sms.#"); DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }; channel.basicConsume("sms.topic", true, defaultConsumer); }}
3.4.3 结果
生产者发送消息,邮件和短信可以通过占位符收到各自消息和共有的消息
注:
- 如果两个客户端连接一个队列,消息会采用轮询的方式
- 生产者和消费者只需要有一个创建交换器或队列即可,不过两个都创建也不错
3.5 Confirm(消息确认机制)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KPReQhzo-1635666093919)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029193116290.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qKyyOAx3-1635666093922)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029193136414.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8gL9DfQS-1635666093924)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029193155505.png)]
3.5.1 生产者
public class MyProducer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); //开启确认模式 channel.confirmSelect(); //添加监听应答 channel.addConfirmListener(new ConfirmListener() { //发送成功返回 public void handleAck(long l, boolean b) throws IOException { System.out.println("ack"); System.out.println(1); System.out.println(b); } //发送失败返回 public void handleNack(long l, boolean b) throws IOException { System.out.println("nack"); System.out.println(1); System.out.println(b); } }); String msg = "一条confirm信息"; channel.basicPublish("confirm.exchange", "confirm.routing", null, msg.getBytes()); }}
3.5.2 消费者
public class MyConsumer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); channel.exchangeDeclare("confirm.exchange", BuiltinExchangeType.TOPIC); channel.queueDeclare("confirm.queue", true, false, false, null); channel.queueBind("confirm.queue", "confirm.exchange", "confirm.#"); DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }; channel.basicConsume("confirm.queue", true, defaultConsumer); }}
注:生产者中不能写close关闭,要不就收不到应答
3.6 Return(处理不可路由的信息)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RXfG6NME-1635666093926)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029193845298.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gaujIzmq-1635666093928)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029193902888.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ATQ5G9G-1635666093930)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029193916918.png)]
3.6.1 生产者
public class MyProducer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); channel.confirmSelect(); //添加监听不可达消息 channel.addReturnListener(new ReturnListener() { public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException { //响应的状态码 System.out.println("replyCode:" + replyCode); //状态码对应的文本 System.out.println("replyText:" + replyText); //交换机名称 System.out.println("exchange:" + exchange); //路由的key System.out.println("routingKey:" + routingKey); //消息体内容 System.out.println(new String(bytes, "UTF-8")); } }); String msg = "一条return信息"; //mandatory 属性设置为true表示:要监听不可达的消息 //路由key设置一个不存在的 channel.basicPublish("return.exchange", "abc.routing", true, null, msg.getBytes()); }}
3.6.2 消费者
public class MyConsumer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); channel.exchangeDeclare("return.exchange", BuiltinExchangeType.TOPIC); channel.queueDeclare("return.queue", true, false, false, null); channel.queueBind("return.queue", "return.exchange", "return.#"); DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }; channel.basicConsume("return.queue", true, defaultConsumer); }}
3.6.3 结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dEDBTmlr-1635666093931)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029194929108.png)]
3.7 消费端限流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n5WfKOS9-1635666093933)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029195223292.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V02P9SiV-1635666093934)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029195248203.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Hk4FNgH-1635666093937)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029195309213.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fF9YRHaT-1635666093938)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029195328402.png)]
3.7.1 生产者
public class MyProducer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); //发送五条消息 for (int i = 0; i < 5; i++) { String msg = "一条confirm信息" + i; channel.basicPublish("qos.exchange", "qos.routing", null, msg.getBytes()); } }}
3.7.2 消费者
public class MyConsumer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); final Channel channel = conn.createChannel(); channel.exchangeDeclare("qos.exchange", BuiltinExchangeType.TOPIC); channel.queueDeclare("qos.queue", true, false, false, null); channel.queueBind("qos.queue", "qos.exchange", "qos.#"); //开启限流,包括消息处理的数量、消息接收的数量、是不是针对整个Connection的,false说明只是针对于这个Channel channel.basicQos(0, 2, false); DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); System.out.println("消费端手工ack到mq的服务器"); //开启手动处理,包括表示处理完毕、是否批量处理 channel.basicAck(envelope.getDeliveryTag(), false); } }; //ark设置为false说明不开启自动处理 channel.basicConsume("qos.queue", false, defaultConsumer); }}
3.7.3 结果
如果手动处理和自动处理不开,消费者会监听两台信息
3.8 消费端ACK与重回队列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TA5rNfLe-1635666093940)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029201444862.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5wvnNLqa-1635666093942)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029201508833.png)]
3.8.1 生产者
public class MyProducer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); for (int i = 0; i < 5; i++) { String msg = "一条ack信息" + i; Map<String, Object> map = new HashMap<String, Object>(); map.put("num", i+""); //设置参数 AMQP.BasicProperties properties = new AMQP.BasicProperties(). builder().deliveryMode(2).//消息是否持久化,1: 非持久化 2:持久化 contentEncoding("UTF-8").//字符集 headers(map)//数据 .expiration("5000").//有效期 build(); //添加参数 channel.basicPublish("ack.exchange", "ack.routing", properties, msg.getBytes()); } }}
3.8.2 消费者
public class MyConsumer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); final Channel channel = conn.createChannel(); channel.exchangeDeclare("ack.exchange", BuiltinExchangeType.TOPIC); channel.queueDeclare("ack.queue", true, false, false, null); channel.queueBind("ack.queue", "ack.exchange", "ack.#"); DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { //休眠2秒 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } //获取参数 Map<String, Object> headers = properties.getHeaders(); //获取指定参数 String str = headers.get("num").toString(); System.out.println(new String(body, "UTF-8")); //判断 if (str.equals("1")) { System.out.println("nack"); //不自动处理,包括处理完毕、是否批量处理、是否重回队列 channel.basicNack(envelope.getDeliveryTag(), false, true); } else { System.out.println("ack"); channel.basicAck(envelope.getDeliveryTag(), false); } } }; channel.basicConsume("ack.queue", false, defaultConsumer); }}
3.8.3 结果
在有效期时间内,满足判断的条件会一直被监听,直到过期
3.9 死信队列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XtStBdMP-1635666093944)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029202611641.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-orjKFFNm-1635666093946)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029202629013.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U7W6SVwG-1635666093948)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029202642829.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BCtLBhsq-1635666093950)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029202659039.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iMkSiPI6-1635666093952)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20211029202715357.png)]
3.9.1 生产者
public class MyProducer { public static void main(String[] args) throws Exception { Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); String msg = "一条one信息"; AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().deliveryMode(2).expiration("10000").build(); channel.basicPublish("one.exchange", "one.routing", properties, msg.getBytes()); }}
3.9.2 消费者
public class MyConsumer { public static void main(String[] args) throws Exception{ Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); channel.exchangeDeclare("one.exchange", BuiltinExchangeType.TOPIC); //创建一个map集合 Map<String,Object> param = new HashMap<String, Object>(); //绑定第二个交换机名称 param.put("x-dead-letter-exchange","two.exchange"); //第五个参数绑定第二个交换机或路由信息 channel.queueDeclare("one.queue",true,false,false,param); channel.queueBind("one.queue","one.exchange","one.#"); channel.exchangeDeclare("two.exchange", BuiltinExchangeType.TOPIC); channel.queueDeclare("two.queue",true,false,false,null); channel.queueBind("two.queue","two.exchange","#"); DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }; channel.basicConsume("one.queue", true, defaultConsumer); }}
public class MyConsumerTwo { public static void main(String[] args) throws Exception{ Connection conn = ConnectionUtil.createConn(); Channel channel = conn.createChannel(); DefaultConsumer defaultConsumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }; channel.basicConsume("two.queue", true, defaultConsumer); }}
3.9.3 结果
当第一个队列的在有效期过了之前依旧没有被消费,就会到第二个交换机来处理死信
四 Spring 整合RibbitMQ
4.1 模拟用户买东西,商品MQ异步减库存
4.1.1 搭建SpringBoot项目
创建订单和商品服务
分别加入rabbitmq依赖,和springboot依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId></dependency>
application.yml配置
spring: rabbitmq: host: 127.0.0.1 port: 5672 username: guest password: guest virtual-host: /
4.1.2 配置类
@Configurationpublic class RabbitMQConfig { //声明交换器名字 public static final String changeStockCountExchange = "changeStockCountExchange"; //声明队列名字 public static final String changeStockCountQueue = "changeStockCountQueue"; //创建交换器 @Bean(changeStockCountExchange) public Exchange createExchange(){ return ExchangeBuilder.topicExchange(changeStockCountExchange).durable(true).build(); } //创建队列 @Bean(changeStockCountQueue) public Queue createQueue(){ return new Queue(changeStockCountQueue); } //交换器和队列绑定 @Bean public Binding bindingChangeStockQueueExchange(@Qualifier(changeStockCountExchange)Exchange exchange ,@Qualifier(changeStockCountQueue)Queue queue){ return BindingBuilder.bind(queue).to(exchange).with("inform.#.change.#").noargs(); }}
4.1.3 订单发送消息
模式订单发送消息,告诉商品减库存
@Servicepublic class OrderService { //声明rabbitmq @Autowired private RabbitTemplate rabbitTemplate; public void createOrder(){ //添加编号和数量 HashMap<String, Object> map = new HashMap<>(); map.put("skuId","10001"); map.put("num","20"); //发送消息,包括交换机名字、路由key、消息数据 rabbitTemplate.convertAndSend("changeStockCountExchange","inform.change",map); }}
4.1.4 商品监听消息
监听消息,获取修改数据
@Servicepublic class GoodsService { //消费端监听,绑定队列名称 @RabbitListener(queues = "changeStockCountQueue") public void reviceChangeSocke(Map<String ,Object> map){ System.out.println(map); }}
4.2 模拟下单未支付,到时间自动订单自动撤销
这里我们使用死信队列来完成
4.2.1 配置类
@Configurationpublic class RabbitMQConfig { //声明两个交换机、队列、路由key public static final String oneExchange = "one.Exchange"; public static final String oneQueue = "one.queue"; public static final String oneRouting = "one.routing"; public static final String twoExchange = "two.Exchange"; public static final String twoQueue = "two.queue"; public static final String twoRouting = "two.routing"; @Bean(oneExchange) public Exchange createOneExchange(){ return ExchangeBuilder.topicExchange(oneExchange).durable(true).build(); } @Bean(oneQueue) public Queue createOneQueue(){ HashMap<String, Object> param = new HashMap<>(); //死信在转发的交换机 param.put("x-dead-letter-exchange",twoExchange); //死信在转发携带的路由key param.put("x-dead-letter-routing-key",twoRouting); //绑定死信后使用的交换机和路由key return new Queue(oneQueue,true,false,false,param); } @Bean public Binding bindingOneQueueExchange(@Qualifier(oneExchange)Exchange exchange ,@Qualifier(oneQueue)Queue queue){ return BindingBuilder.bind(queue).to(exchange).with("one.#").noargs(); } @Bean(twoExchange) public Exchange createTwoExchange(){ return ExchangeBuilder.topicExchange(twoExchange).durable(true).build(); } @Bean(twoQueue) public Queue createTwoQueue(){ return new Queue(twoQueue,true,false,false,null); } @Bean public Binding bindingTwoQueueExchange(@Qualifier(twoExchange)Exchange exchange ,@Qualifier(twoQueue)Queue queue){ return BindingBuilder.bind(queue).to(exchange).with("two.#").noargs(); }}
4.2.2 确认订单未支付
public void createOrder(){ //创建订单 Order order = new Order(); order.setId(1); order.setTitle("张三的订单"); order.setPrice(38.0); order.setStatus(1); //发送mq,绑定交换机、路由key、消息体、消息设置 rabbitTemplate.convertAndSend(RabbitMQConfig.oneExchange, RabbitMQConfig.oneRouting, order, new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { //设置消息的有效期 message.getMessageProperties().setExpiration(10000+""); return message; } }); System.out.println("MQ写入成功"); }
//监听@RabbitListener(queues = RabbitMQConfig.twoQueue) public void checkOrderStatus(Order order, Channel channel, Message message){ System.out.println(order); //根据订单编号查询数据库 //根据数据库返回的订单状态修改订单是否生效 }