参考文档:
https://www.kuangstudy.com/zl/rabbitmq#1365897801984241665
中间件和消息中间件
常见的中间件
对中间件的理解
为解决分布异构问题,人们提出了中间件(middleware)的概念。中间件是位于平台(硬件和操作系统)和应用之间的通用服务,如下图所示,这些服务具有标准的程序接口和协议。针对不同的操作系统和硬件平台,它们可以有符合接口和协议规范的多种实现。
简单说:中间件有个很大的特点,是脱离于具体设计目标,而具备提供普遍独立功能需求的模块。这使得中间件一定是可替换的。如果一个系统设计中,中间件是不可替换的,不是架构、框架设计有问题,那么就是这个中间件,在 别处可能是个中间件,在这个系统内是引擎。
消息中间件应用的场景
1:跨系统数据传递
2:高并发的流量削峰
3:数据的分发和异步处理
4:大数据分析与传递
5:分布式事务
比如你有一个数据要进行迁移或者请求并发过多的时候,比如你有10W的并发请求下订单,我们可以在这些订单入库之前,我们可以把订单请求堆积到消息队列中,让它稳健可靠的入库和执行。
消息中间件的核心组成部分
1:消息的协议
2:消息的持久化机制
3:消息的分发策略
4:消息的高可用,高可靠
5:消息的容错机制
消息队列协议
而消息中间件采用的并不是http协议,而常见的消息中间件协议有:OpenWire、AMQP、MQTT、Kafka,OpenMessage协议。
面试题:为什么消息中间件不直接使用http协议呢?
1: 因为http请求报文头和响应报文头是比较复杂的,包含了cookie,数据的加密解密,状态码,响应码等附加的功能,但是对于一个消息而言,我们并不需要这么复杂,也没有这个必要性,它其实就是负责数据传递,存储,分发就行,一定要追求的是高性能。尽量简洁,快速。
2:大部分情况下http大部分都是短链接,在实际的交互过程中,一个请求到响应很有可能会中断,中断以后就不会就行持久化,就会造成请求的丢失。这样就不利于消息中间件的业务场景,因为消息中间件可能是一个长期的获取消息的过程,出现问题和故障要对数据或消息就行持久化等,目的是为了保证消息和数据的高可靠和稳健的运行。
常用的消息队列协议
AMQP协议
AMQP:(全称:Advanced Message Queuing Protocol) 是高级消息队列协议。由摩根大通集团联合其他公司共同设计。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。
特性:
1:分布式事务支持。
2:消息的持久化支持。
3:高性能和高可靠的消息处理优势。
MQTT协议
OpenMessage协议
Kafka协议
安装rabbitMQ
需要安装erlang和rabbitmq(linux系统)
rabbitmq: https://www.rabbitmq.com/download.html
-
安装erlang
#下载 wget https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm rpm -Uvh erlang-solutions-1.0-1.noarch.rpm #安装 yum install -y erlang #安装成功 erl -v
-
安装socat
yum install -y socat
-
安装rabbitmq
> wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.9.7/rabbitmq-server-3.9.7-1.el7.noarch.rpm > rpm -Uvh rabbitmq-server-3.9.7-1.el7.noarch.rpm
注:rabbitmq和erlang版本强相关https://www.rabbitmq.com/which-erlang.html
使用rabbitMQ
启动rabbitmq服务器
# 启动服务
> systemctl start rabbitmq-server
# 查看服务状态
> systemctl status rabbitmq-server
# 停止服务
> systemctl stop rabbitmq-server
# 开机启动服务
> systemctl enable rabbitmq-server
启动可视化客户端工具
rabbitmq-plugins enable rabbitmq_management
说明:rabbitmq有一个默认账号和密码是:guest
默认情况只能在localhost本机下访问,所以需要添加一
个远程登录的用户。
授权账号和密码
新增用户
rabbitmqctl add_user admin admin
设置用户分配操作权限
rabbitmqctl set_user_tags admin administrator
设置用户分配操作权限
rabbitmqctl set_user_tags admin administrator
登录客户端
搭建常用使用案例
rabbitmq支持消息的模式:参考官网:https://www.rabbitmq.com/getstarted.html
simple案例
图解
- 创建一个maven工程,导入相关依赖
-
生产者模块
package rabbitmq.simple; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * @ClassName Producer * @Description TODO * @Author QiuYiping * @Date 2021/10/13 20:00 */ public class Producer { public static void main(String[] args) { //所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范 //ip port //1.创建连接工程 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.129.128"); connectionFactory.setPort(5672); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); connectionFactory.setVirtualHost("/"); Connection connection = null; Channel channel = null; //2.创建连接connection try { connection = connectionFactory.newConnection("生产者"); //3.通过连接获取通道channel channel = connection.createChannel(); //4.通过通道创建交换机、声明队列、绑定关系、路由key、发送消息和接受消息 String queueName = "queue1"; /* * @params1 队列的名称 * @params2 是否要持久化durable=true 所谓持久化消息是否存盘,如果false,则非持久化,如果true则持久化 * @params3 排他性 是否是独占独立 * @params4 是否自动删除,随着最后一个消费者消息完毕消以后是否把队列自动删除 * @params5 携带附属参数 * */ channel.queueDeclare(queueName, false, false, false, null); //5.准备消息内容 String message = "Hello xuexiangban"; //6.发送消息给队列queue channel.basicPublish("", queueName, null, message.getBytes()); System.out.println("消息发送成功"); } catch (Exception e) { e.printStackTrace(); } finally { //7.关闭通道 if (channel != null && channel.isOpen()) { try { channel.close(); } catch (Exception e) { e.printStackTrace(); } } //8.关闭连接 if (connection != null && connection.isOpen()) { try { connection.close(); } catch (Exception e) { e.printStackTrace(); } } } } }
-
消费者模块
package rabbitmq.simple; import com.rabbitmq.client.*; import java.io.IOException; /** * @ClassName Consumer * @Description TODO * @Author QiuYiping * @Date 2021/10/13 20:00 */ public class Consumer { public static void main(String[] args) { //所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范 //ip port //1.创建连接工程 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.129.128"); connectionFactory.setPort(5672); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); connectionFactory.setVirtualHost("/"); Connection connection = null; Channel channel = null; //2.创建连接connection try { connection = connectionFactory.newConnection("生产者"); //3.通过连接获取通道channel channel = connection.createChannel(); //4.通过通道创建交换机、声明队列、绑定关系、路由key、发送消息和接受消息 channel.basicConsume("queue1", true, new DeliverCallback() { public void handle(String consumerTag, Delivery message) throws IOException { System.out.println("收到消息是:" + new String(message.getBody(), "UTF-8")); } }, new CancelCallback() { public void handle(String s) throws IOException { System.out.println("接受失败了..."); } }); System.out.println("开始接受消息"); System.in.read(); } catch (Exception e) { e.printStackTrace(); } finally { //7.关闭通道 if (channel != null && channel.isOpen()) { try { channel.close(); } catch (Exception e) { e.printStackTrace(); } } //8.关闭连接 if (connection != null && connection.isOpen()) { try { connection.close(); } catch (Exception e) { e.printStackTrace(); } } } } }
运行结果:
- 持久化队列和非持久化队列
channel.queueDeclare(queueName, false, false, false, null);
@params2 是否要持久化durable=true 所谓持久化消息是否存盘,如果false,则非持久化,如果true则持久化
非持久化队列会随着服务器的重启而队列丢失
fanout模式-发布订阅模式
图解
-
生产者
package rabbitmq.fanout; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; /** * @ClassName Producer * @Description TODO * @Author QiuYiping * @Date 2022/4/4 11:59 */ public class Producer { public static void main(String[] args) { // 1: 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 2: 设置连接属性 connectionFactory.setHost("192.168.129.128"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); Connection connection = null; Channel channel = null; try { // 3: 从连接工厂中获取连接 connection = connectionFactory.newConnection("生产者"); // 4: 从连接中获取通道channel channel = connection.createChannel(); // 6: 准备发送消息的内容 String message = "你好,学相伴!!!"; String exchangeName = "fanout-exchange"; String routingKey = ""; // 7: 发送消息给中间件rabbitmq-server // @params1: 交换机exchange // @params2: 队列名称/routingkey // @params3: 属性配置 // @params4: 发送消息的内容 channel.basicPublish(exchangeName, routingKey, null, message.getBytes()); System.out.println("消息发送成功!"); } catch (Exception ex) { ex.printStackTrace(); System.out.println("发送消息出现异常..."); } finally { // 7: 释放连接关闭通道 if (channel != null && channel.isOpen()) { try { channel.close(); } catch (Exception ex) { ex.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (Exception ex) { ex.printStackTrace(); } } } } }
-
消费者
package rabbitmq.fanout; import com.rabbitmq.client.*; import java.io.IOException; /** * @ClassName Consumer * @Description TODO * @Author QiuYiping * @Date 2022/4/4 11:59 */ public class Consumer { static class Queue implements Runnable { public void run() { // 1: 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 2: 设置连接属性 connectionFactory.setHost("192.168.129.128"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); //获取队列的名称 final String queueName = Thread.currentThread().getName(); Connection connection = null; Channel channel = null; try { // 3: 从连接工厂中获取连接 connection = connectionFactory.newConnection("生产者"); // 4: 从连接中获取通道channel channel = connection.createChannel(); // 5: 申明队列queue存储消息 /* * 如果队列不存在,则会创建 * Rabbitmq不允许创建两个相同的队列名称,否则会报错。 * * @params1: queue 队列的名称 * @params2: durable 队列是否持久化 * @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭 * @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。 * @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。 * */ // 这里如果queue已经被创建过一次了,可以不需要定义 //channel.queueDeclare("queue1", false, false, false, null); // 6: 定义接受消息的回调 Channel finalChannel = channel; finalChannel.basicConsume(queueName, true, new DeliverCallback() { @Override public void handle(String s, Delivery delivery) throws IOException { System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8")); } }, new CancelCallback() { @Override public void handle(String s) throws IOException { } }); System.out.println(queueName + ":开始接受消息"); System.in.read(); } catch (Exception ex) { ex.printStackTrace(); System.out.println("发送消息出现异常..."); } finally { // 7: 释放连接关闭通道 if (channel != null && channel.isOpen()) { try { channel.close(); } catch (Exception ex) { ex.printStackTrace(); } } if (connection != null && connection.isOpen()) { try { connection.close(); } catch (Exception ex) { ex.printStackTrace(); } } } } } public static void main(String[] args) { // 启动三个线程去执行 new Thread(new Queue(), "queue-1").start(); new Thread(new Queue(), "queue-2").start(); new Thread(new Queue(), "queue-3").start(); } }
- 执行结果
direct模式-路由key模式
图解
注:Direct模式是fanout模式上的一种叠加,增加了路由RoutingKey的模式。
-
生产者
package rabbitmq.direct; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; /** * @ClassName Producer * @Description TODO * @Author QiuYiping * @Date 2022/4/4 12:26 */ public class Producer { public static void main(String[] args) { // 1: 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 2: 设置连接属性 connectionFactory.setHost("192.168.129.128"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); Connection connection = null; Channel channel = null; try { // 3: 从连接工厂中获取连接 connection = connectionFactory.newConnection("生产者"); // 4: 从连接中获取通道channel channel = connection.createChannel(); // 6: 准备发送消息的内容 String message = "你好,学习rabbitmq的direct!!!"; String exchangeName = "direct-exchange"; String routingKey1 = "testkey"; String routingKey2 = "testkey2"; // 7: 发送消息给中间件rabbitmq-server // @params1: 交换机exchange // @params2: 队列名称/routingkey // @params3: 属性配置 // @params4: 发送消息的内容 channel.basicPublish(exchangeName, routingKey1, null, message.getBytes()); channel.basicPublish(exchangeName, routingKey2, null, message.getBytes()); System.out.println("消息发送成功!"); } catch (Exception ex) { ex.printStackTrace(); System.out.println("发送消息出现异常..."); } finally { // 7: 释放连接关闭通道 if (channel != null && channel.isOpen()) { try { channel.close(); } catch (Exception ex) { ex.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (Exception ex) { ex.printStackTrace(); } } } } }
-
消费者
package rabbitmq.direct; import com.rabbitmq.client.*; import java.io.IOException; /** * @ClassName Consumer * @Description TODO * @Author QiuYiping * @Date 2022/4/4 12:26 */ public class Consumer { private static Runnable runnable = () -> { // 1: 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); // 2: 设置连接属性 connectionFactory.setHost("192.168.129.128"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); //获取队列的名称 final String queueName = Thread.currentThread().getName(); Connection connection = null; Channel channel = null; try { // 3: 从连接工厂中获取连接 connection = connectionFactory.newConnection("生产者"); // 4: 从连接中获取通道channel channel = connection.createChannel(); // 5: 申明队列queue存储消息 /* * 如果队列不存在,则会创建 * Rabbitmq不允许创建两个相同的队列名称,否则会报错。 * * @params1: queue 队列的名称 * @params2: durable 队列是否持久化 * @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭 * @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。 * @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。 * */ // 这里如果queue已经被创建过一次了,可以不需要定义 //channel.queueDeclare("queue1", false, false, false, null); // 6: 定义接受消息的回调 Channel finalChannel = channel; finalChannel.basicConsume(queueName, true, new DeliverCallback() { @Override public void handle(String s, Delivery delivery) throws IOException { System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8")); } }, new CancelCallback() { @Override public void handle(String s) throws IOException { } }); System.out.println(queueName + ":开始接受消息"); System.in.read(); } catch (Exception ex) { ex.printStackTrace(); System.out.println("发送消息出现异常..."); } finally { // 7: 释放连接关闭通道 if (channel != null && channel.isOpen()) { try { channel.close(); } catch (Exception ex) { ex.printStackTrace(); } } if (connection != null && connection.isOpen()) { try { connection.close(); } catch (Exception ex) { ex.printStackTrace(); } } } }; public static void main(String[] args) { // 启动三个线程去执行 new Thread(runnable, "queue-4").start(); new Thread(runnable, "queue-5").start(); new Thread(runnable, "queue-6").start(); } }
direct-exchange绑定的queue和routing(只有queue-4和queue-5满足条件)
运行结果:
topics模式-通配符来匹配
图解
通配符匹配规则:
#:代表0到多个字符
*****:只能代表一个字符
注:Topic模式是direct模式上的一种叠加,增加了模糊路由RoutingKey的模式
- 生产者
package rabbitmq.topics;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @ClassName Producer
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/4 12:47
*/
public class Producer {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("192.168.129.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 6: 准备发送消息的内容
String message = "你好,学习rabbitmq的topic模式!!!";
String exchangeName = "topic-exchange";
String routingKey1 = "com.course.order";//都可以收到 queue-1 queue-2
String routingKey2 = "com.order.user";//都可以收到 queue-1 queue-3
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routingkey
// @params3: 属性配置
// @params4: 发送消息的内容
channel.basicPublish(exchangeName, routingKey1, null, message.getBytes());
System.out.println("消息发送成功!");
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
- 消费者
package rabbitmq.topics;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @ClassName Consumer
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/4 12:47
*/
public class Consumer {
private static Runnable runnable = () -> {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("192.168.129.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
//获取队列的名称
final String queueName = Thread.currentThread().getName();
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
/*
* 如果队列不存在,则会创建
* Rabbitmq不允许创建两个相同的队列名称,否则会报错。
*
* @params1: queue 队列的名称
* @params2: durable 队列是否持久化
* @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
* @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
* @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
* */
// 这里如果queue已经被创建过一次了,可以不需要定义
//channel.queueDeclare("queue1", false, false, false, null);
// 6: 定义接受消息的回调
Channel finalChannel = channel;
finalChannel.basicConsume(queueName, true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println(queueName + ":开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
};
public static void main(String[] args) {
// 启动三个线程去执行
new Thread(runnable, "queue-7").start();
new Thread(runnable, "queue-8").start();
new Thread(runnable, "queue-9").start();
}
}
topic绑定的路由关系
运行结果:
work模式(轮询和公平fair-能者多劳)
图解
轮询模式和公平模式的区别:
轮询模式:一个消费者一条,按均分配
公平分发:根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配;
轮询模式
- 生产者
package rabbitmq.work.lunxun;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @ClassName Producer
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/4 13:39
*/
public class Producer {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("192.168.129.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 6: 准备发送消息的内容
//===============================end topic模式==================================
for (int i = 1; i <= 20; i++) {
//消息的内容
String msg = "学相伴:" + i;
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routingkey
// @params3: 属性配置
// @params4: 发送消息的内容
channel.basicPublish("", "queue1", null, msg.getBytes());
}
System.out.println("消息发送成功!");
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
- 消费者1
package rabbitmq.work.lunxun;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @ClassName Consumer
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/4 13:39
*/
public class Consumer1 {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("192.168.129.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者-Work1");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
/*
* 如果队列不存在,则会创建
* Rabbitmq不允许创建两个相同的队列名称,否则会报错。
*
* @params1: queue 队列的名称
* @params2: durable 队列是否持久化
* @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
* @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
* @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
* */
// 这里如果queue已经被创建过一次了,可以不需要定义
// channel.queueDeclare("queue1", false, false, false, null);
// 同一时刻,服务器只会推送一条消息给消费者
// 6: 定义接受消息的回调
Channel finalChannel = channel;
//每次从队列中读取一条数据
finalChannel.basicQos(1);
//自动应答改为true,这样就是轮询应答了
finalChannel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try{
System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(2000);
// finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}catch(Exception ex){
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println("Work1-开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
- 消费者2
package rabbitmq.work.lunxun;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @ClassName Consumer2
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/4 13:42
*/
public class Consumer2 {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("192.168.129.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者-Work2");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
/*
* 如果队列不存在,则会创建
* Rabbitmq不允许创建两个相同的队列名称,否则会报错。
*
* @params1: queue 队列的名称
* @params2: durable 队列是否持久化
* @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
* @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
* @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
* */
// 这里如果queue已经被创建过一次了,可以不需要定义
//channel.queueDeclare("queue1", false, true, false, null);
// 同一时刻,服务器只会推送一条消息给消费者
//channel.basicQos(1);
// 6: 定义接受消息的回调
Channel finalChannel = channel;
finalChannel.basicQos(1);
finalChannel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try{
System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(200);
// finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}catch(Exception ex){
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println("Work2-开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
运行结果:
注:轮询模式的交换机是默认类型的,不需要特别指定。其自动应答机制必须为true,否则就成了公平分发机制了
公平fair(能者多劳)
- 生产者
package rabbitmq.work.fair;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @ClassName Producer
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/4 13:39
*/
public class Producer {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("192.168.129.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 6: 准备发送消息的内容
//===============================end topic模式==================================
for (int i = 1; i <= 20; i++) {
//消息的内容
String msg = "学相伴:" + i;
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routingkey
// @params3: 属性配置
// @params4: 发送消息的内容
channel.basicPublish("", "queue2", null, msg.getBytes());
Thread.sleep(1000);
}
System.out.println("消息发送成功!");
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
- 消费者1
package rabbitmq.work.fair;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @ClassName Consumer
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/4 13:39
*/
public class Consumer1 {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("192.168.129.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者-Work1");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
/*
* 如果队列不存在,则会创建
* Rabbitmq不允许创建两个相同的队列名称,否则会报错。
*
* @params1: queue 队列的名称
* @params2: durable 队列是否持久化
* @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
* @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
* @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
* */
// 这里如果queue已经被创建过一次了,可以不需要定义
// channel.queueDeclare("queue1", false, false, false, null);
// 同一时刻,服务器只会推送一条消息给消费者
// 6: 定义接受消息的回调
Channel finalChannel = channel;
//每次从队列中读取一条数据
finalChannel.basicQos(1);
//自动应答改为false
finalChannel.basicConsume("queue2", false, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try{
System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(2000);
finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}catch(Exception ex){
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println("Work1-开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
- 消费者2
package rabbitmq.work.fair;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @ClassName Consumer2
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/4 13:42
*/
public class Consumer2 {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("192.168.129.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者-Work2");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
/*
* 如果队列不存在,则会创建
* Rabbitmq不允许创建两个相同的队列名称,否则会报错。
*
* @params1: queue 队列的名称
* @params2: durable 队列是否持久化
* @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
* @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
* @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
* */
// 这里如果queue已经被创建过一次了,可以不需要定义
//channel.queueDeclare("queue1", false, true, false, null);
// 同一时刻,服务器只会推送一条消息给消费者
//channel.basicQos(1);
// 6: 定义接受消息的回调
Channel finalChannel = channel;
finalChannel.basicQos(1);
finalChannel.basicConsume("queue2", false, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try{
System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(200);
finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}catch(Exception ex){
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println("Work2-开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
运行结果:
常用的消息中间件
-
activeMQ
-
rabbitMQ
-
kafka
-
rocketMQ
RabbitMQ的核心
AMQP模式
RabbitMQ使用场景
和springboot的整合
创建springboot和rabbitmq整合工程(fanout交换机模式)
创建生产者工程
- 创建springboot工程,引入rabbitmq的依赖
- yml文件中引入依赖
# 服务端口
server:
port: 8080
# 配置rabbitmq服务
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 192.168.129.128
port: 5672
- 完成交换机和队列的声明及绑定
@Configuration
public class RabbitMqConfiguration {
//1.声明注册fanoutm模式的交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("fanout_order_exchange", true, false);
}
//2.声明队列sms.fanout.queue, email.fanout.queue, duanxin.fanout.queue
@Bean
public Queue smsQueue(){
return new Queue("sms.fanout.queue", true);
}
@Bean
public Queue emailQueue(){
return new Queue("email.fanout.queue", true);
}
@Bean
public Queue duanxinQueue(){
return new Queue("duanxin.fanout.queue", true);
}
//3.声明队列和交换机的绑定
@Bean
public Binding smsBinding(){
return BindingBuilder.bind(smsQueue()).to(fanoutExchange());
}
//3.声明队列和交换机的绑定
@Bean
public Binding emailBinding(){
return BindingBuilder.bind(emailQueue()).to(fanoutExchange());
}
//3.声明队列和交换机的绑定
@Bean
public Binding duanxinBinding(){
return BindingBuilder.bind(duanxinQueue()).to(fanoutExchange());
}
}
- 发送消息
package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;
/**
* @ClassName OrderService
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/5 16:18
*/
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 功能描述
*
* @param
* @return
* @Description //TODO
* @Date
* @Author
*/
public void makeOrder(String userid, String productid, int num) {
//1.根据商品id查询库存是否充足
//2.保存订单
String orderId = UUID.randomUUID().toString();
System.out.println("订单生产成功:" + orderId);
//3.通过MQ来完成消息的分发
// 参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容
String exchangeName = "fanout_order_exchange";
String routingKey = "";
rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
}
}
- 测试消息发送
@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {
@Autowired
private OrderService orderService;
@Test
void contextLoads(){
orderService.makeOrder("1","1",12);
}
}
创建消费者工程
- 修改配置类
# 服务端口
server:
port: 8081
# 配置rabbitmq服务
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 192.168.129.128
port: 5672
- 绑定三个队列
@RabbitListener(queues = {"duanxin.fanout.queue"})
@Component
public class FanoutDuanxinConsumer {
@RabbitHandler
public void receiveMessage(String message){
System.out.println("duanxin fanout---接收到了订单信息是:->" + message);
}
}
@RabbitListener(queues = {"email.fanout.queue"})
@Component
public class FanoutEmailConsumer {
@RabbitHandler
public void receiveMessage(String message){
System.out.println("email fanout---接收到了订单信息是:->" + message);
}
}
@RabbitListener(queues = {"sms.fanout.queue"})
@Component
public class FanoutSMSConsumer {
@RabbitHandler
public void receiveMessage(String message){
System.out.println("sms fanout---接收到了订单信息是:->" + message);
}
}
- 测试
@SpringBootApplication
public class SpringbootOrderRabbitmqConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootOrderRabbitmqConsumerApplication.class, args);
}
}
运行结果 (消费者和生产者都需要启动)
direct模式
生产者工程
- config类中的fanoutExchange改成directExchange,同时在交换机和队列绑定时后面加with(“[路由名称]”)
package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName RabbitMqConfiguration
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/5 16:31
*/
@Configuration
public class DirectRabbitMqConfiguration {
//1.声明注册fanoutm模式的交换机
@Bean
public DirectExchange directExchange(){
return new DirectExchange("direct_order_exchange", true, false);
}
//2.声明队列sms.fanout.queue, email.fanout.queue, duanxin.fanout.queue
@Bean
public Queue smsQueue(){
return new Queue("sms.direct.queue", true);
}
@Bean
public Queue emailQueue(){
return new Queue("email.direct.queue", true);
}
@Bean
public Queue duanxinQueue(){
return new Queue("duanxin.direct.queue", true);
}
//3.声明队列和交换机的绑定
@Bean
public Binding smsBinding(){
return BindingBuilder.bind(smsQueue()).to(directExchange()).with("sms");
}
//3.声明队列和交换机的绑定
@Bean
public Binding emailBinding(){
return BindingBuilder.bind(emailQueue()).to(directExchange()).with("email");
}
//3.声明队列和交换机的绑定
@Bean
public Binding duanxinBinding(){
return BindingBuilder.bind(duanxinQueue()).to(directExchange()).with("duanxin");
}
}
- 测试类
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 功能描述
*
* @param
* @return
* @Description //TODO
* @Date
* @Author
*/
public void makeOrderDirect(String userid, String productid, int num) {
//1.根据商品id查询库存是否充足
//2.保存订单
String orderId = UUID.randomUUID().toString();
System.out.println("订单生产成功:" + orderId);
//3.通过MQ来完成消息的分发
// 参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容
String exchangeName = "direct_order_exchange";
String routingKey = "sms";
String routingKey2 = "email";
rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
rabbitTemplate.convertAndSend(exchangeName, routingKey2, orderId);
}
}
- 测试类
@Test
void contextLoads2(){
orderService.makeOrderDirect("1","1",12);
}
消费者工程
注:可以把交换机和队列的声明及绑定关系放到消费者工程中去,这样先启动消费者工程时就可以绑定交换机和队列关系
@RabbitListener(queues = {"duanxin.direct.queue"})
@Component
public class DirectDuanxinConsumer {
@RabbitHandler
public void receiveMessage(String message){
System.out.println("duanxin direct---接收到了订单信息是:->" + message);
}
}
@RabbitListener(queues = {"email.direct.queue"})
@Component
public class DirectEmailConsumer {
@RabbitHandler
public void receiveMessage(String message){
System.out.println("email direct---接收到了订单信息是:->" + message);
}
}
@RabbitListener(queues = {"sms.direct.queue"})
@Component
public class DirectSMSConsumer {
@RabbitHandler
public void receiveMessage(String message){
System.out.println("sms direct---接收到了订单信息是:->" + message);
}
}
- 测试结果(启动springboot的main方法):
- 页面绑定关系成立
- 消费者可以监听到消息
topics模式
生产者工程
package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;
/**
* @ClassName OrderService
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/5 16:18
*/
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void makeOrderTopic(String userid, String productid, int num){
//1.根据商品id查询库存是否充足
//2.保存订单
String orderId = UUID.randomUUID().toString();
System.out.println("订单生产成功:" + orderId);
//3.通过MQ来完成消息的分发
// 参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容
String exchangeName = "topic_order_exchange";
//#.duanxin.#
//com.#
//*.email.#
String routingKey = "com.duanxin";
rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
}
}
@Test
void contextLoads3(){
orderService.makeOrderTopic("1","1",12);
}
消费者工程
- 用注解的方式监听队列和绑定队列和交换机的关系
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.topic;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
/**
* @ClassName TopicDuanxinConsumer
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/6 9:05
*/
@Component
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "duanxin.topic.queue", durable = "true", autoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
key = "#.duanxin.#"
))
public class TopicDuanxinConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("duanxin topic---接收到了订单信息是:->" + message);
}
}
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.topic;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
/**
* @ClassName TopicDuanxinConsumer
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/6 9:05
*/
@Component
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "email.topic.queue", durable = "true", autoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
key = "*.email.#"
))
public class TopicEmailConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("email topic---接收到了订单信息是:->" + message);
}
}
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.topic;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
/**
* @ClassName TopicDuanxinConsumer
* @Descriptin TODO
* @Author QiuYiping
* @Date 2022/4/6 9:05
*/
@Component
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "sms.topic.queue", durable = "true", autoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
key = "com.#"
))
public class TopicSmsConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("sms topic---接收到了订单信息是:->" + message);
}
}
运行结果:
- 交换机和队列相互绑定
- sms和短信收到消息
ttl设置过期时间
设置队列的过期时间
- 消费者工程绑定交换机和队列,并设置队列的消息ttl
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
/**
* @ClassName TTLRabbitMqConfiguration
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/6 10:34
*/
@Configuration
public class TTLRabbitMqConfiguration {
//1.声明注册fanout模式的交换机
@Bean
public DirectExchange ttldirectExchange() {
return new DirectExchange("ttl_direct_order_exchange", true, false);
}
//2.声明ttl队列并设置队列过期时间
@Bean
public Queue directTtlQueue() {
HashMap<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 5000); //这里一定是int类型
return new Queue("ttl.direct.queue", true,false ,false ,args);
}
//3.声明队列和交换机的绑定
@Bean
public Binding ttlBinding() {
return BindingBuilder.bind(directTtlQueue()).to(ttldirectExchange()).with("ttl");
}
}
- 生产者工程
package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;
/**
* @ClassName OrderService
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/5 16:18
*/
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void makeOrderTtl(String userid, String productid, int num){
//1.根据商品id查询库存是否充足
//2.保存订单
String orderId = UUID.randomUUID().toString();
System.out.println("订单生产成功:" + orderId);
//3.通过MQ来完成消息的分发
// 参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容
String exchangeName = "ttl_direct_order_exchange";
//#.duanxin.#
//com.#
//*.email.#
String routingKey = "ttl";
rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
}
}
- 生产者测试
@Test
void contextLoads4(){
orderService.makeOrderTtl("1","1",12);
}
- 运行结果:
- 生成了ttl交换机和队列
- 设置队列的过期时间可以在页面上操作
- 启动生产者工程5秒后这个消息会自动消失
单独设置消息的过期时间
1.消费者工程
@Configuration
public class TTLRabbitMqConfiguration {
//1.声明注册direct模式的交换机
@Bean
public DirectExchange ttldirectExchange() {
return new DirectExchange("ttl_direct_order_exchange", true, false);
}
//2.声明ttl队列
@Bean
public Queue directTtlMessageQueue() {
return new Queue("ttl.message.queue", true);
}
//3.声明队列和交换机的绑定
@Bean
public Binding ttlMessageBinding() {
return BindingBuilder.bind(directTtlMessageQueue()).to(ttldirectExchange()).with("ttlmessage");
}
}
2.生产者工程
public void makeOrderTtlMessage(String userid, String productid, int num){
//1.根据商品id查询库存是否充足
//2.保存订单
String orderId = UUID.randomUUID().toString();
System.out.println("订单生产成功:" + orderId);
//3.通过MQ来完成消息的分发
// 参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容
String exchangeName = "ttl_direct_order_exchange";
String routingKey = "ttlmessage";
//给消息设置过期时间
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("5000");
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
//给发送的这条消息绑定过期时间的后置处理器
rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId, messagePostProcessor);
}
- 运行结果:
- 队列中的这条消息在5秒后自动消失
注:如果消息队列和此条消息同时设置了过期时间,则以最小的过期时间为准
死信队列
死信队列是当消息在一个队列因为下列原因:
-
消息被拒绝(basic.reject或basic.nack)并且requeue=false
-
消息TTL过期
-
队列达到最大长度(队列满了,数据无法添加到mq中)
变成了 “死信队列” 后被重新投递(publish)到另一个Exchange,然后重新消费。说白了就是没有被消费的消息换个地方重新被消费
建立方式:建立一个普通的direct队列,取名为死信队列,然后把死信队列和设置了过期时间的队列产生关联,这样一旦消息队列过期了就会自动转到死信队列中
1.生产者建立死信队列和交换机的绑定关系
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
/**
* @ClassName DeadRabbitMqConfiguration
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/6 12:15
*/
@Configuration
public class DeadRabbitMqConfiguration {
//1.声明注册direct模式的交换机
@Bean
public DirectExchange deadExchange() {
return new DirectExchange("dead_direct_order_exchange", true, false);
}
//2.声明死信队列
@Bean
public Queue deadQueue() {
return new Queue("dead.direct.queue", true);
}
//3.声明队列和交换机的绑定
@Bean
public Binding deadBindings() {
return BindingBuilder.bind(deadQueue()).to(deadExchange()).with("dead");
}
}
- 把设置了过期时间的消息队列和死信队列建立关联
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
/**
* @ClassName TTLRabbitMqConfiguration
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/6 10:34
*/
@Configuration
public class TTLRabbitMqConfiguration {
//1.声明注册fanout模式的交换机
@Bean
public DirectExchange ttldirectExchange() {
return new DirectExchange("ttl_direct_order_exchange", true, false);
}
//2.声明ttl队列并设置队列过期时间
@Bean
public Queue directTtlQueue() {
HashMap<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 5000);
//此处是关键,设置死信队列和过期队列的关联
args.put("x-dead-letter-exchange", "dead_direct_order_exchange");
args.put("x-dead-letter-routing-key", "dead");
return new Queue("ttl.direct.queue", true,false ,false ,args);
}
//3.声明队列和交换机的绑定
@Bean
public Binding ttlBinding() {
return BindingBuilder.bind(directTtlQueue()).to(ttldirectExchange()).with("ttl");
}
}
- 运行结果:
- 5秒后未消费自动转到死信队列中
- 页面上可以设置和获取这两个参数
内存和磁盘的阈值控制
内存阈值控制
命令:
rabbitmqctl set_vm_memory_high_watermark <fraction> [0.4为常规默认值]
rabbitmqctl set_vm_memory_high_watermark absolute 50MB
其含义是内存超过这个值(如50MB),则内存会爆红,且所有连接connection为阻塞状态,不能发送消息,需要调整内存
磁盘阈值控制
命令:
rabbitmqctl set_disk_free_limit <disk_limit> [100GB]
rabbitmqctl set_disk_free_limit memory_limit <fraction>
disk_limit:固定单位 KB MB GB
fraction :是相对阈值,建议范围在:1.0~2.0之间。(相对于内存)
如设置为100GB后会爆红,其含义为磁盘所剩容量低于这个值(100GB)则会报警(因目前只有15GB可用,所以会爆红),所有连接阻塞
恢复为正常值后可以连接
内存换页
即在内存达到一个比例时自动把其中的消息存盘到磁盘中,用磁盘换取内存空间
默认情况下,内存到达的阈值是50%时就会换页处理。
也就是说,在默认情况下该内存的阈值是0.4的情况下,当内存超过0.4*0.5=0.2时,会进行换页动作。
比如有1000MB内存,当内存的使用率达到了400MB,已经达到了极限,但是因为配置的换页内存0.5,这个时候会在达到极限400mb之前,会把内存中的200MB进行转移到磁盘中。从而达到稳健的运行。
可以通过设置 vm_memory_high_watermark_paging_ratio
来进行调整
vm_memory_high_watermark.relative = 0.4
vm_memory_high_watermark_paging_ratio = 0.7(设置小于1的值,默认为0.5)
集群
https://www.kuangstudy.com/zl/rabbitmq#1367869499746869249
分布式事务
系统与系统之间的分布式事务问题
案例展示
订单服务(order-service)
- 实体类Order
public class Order {
private String orderId;
private String userId;
private String orderContent;
}
- controller
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
public OrderService orderService;
@PostMapping("/create")
public String create(@RequestBody Order order) throws Exception {
orderService.createOrder(order);
return "success";
}
}
- service
@Service
public class OrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order orderInfo) throws Exception {
//1.订单信息--插入订单系统,订单数据库事务
saveOrder(orderInfo);
String result = dispatchHttpApi(orderInfo.getOrderId() + "");
if (!"success".equals(result)) {
throw new Exception("订单创建失败,原因是运单接口调用失败!");
}
}
private String dispatchHttpApi(String orderId) {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
// 链接超时>3秒
factory.setConnectTimeout(3000);
// 处理超时>2秒
factory.setReadTimeout(2000);
// 发送http请求
String url = "http://localhost:9000/dispatch/order?orderId=" + orderId;
RestTemplate restTemplate = new RestTemplate(factory); //异常
String result = restTemplate.getForObject(url, String.class);
return result;
}
public void saveOrder(Order order) throws Exception {
String sqlString = "insert into ksd_order(order_id,user_id,order_content,create_time)values(?,?,?,?)";
Timestamp curTime = new Timestamp(new Date().getTime());
int count = jdbcTemplate.update(sqlString, order.getOrderId(), order.getUserId(), order.getOrderContent(), curTime);
if (count != 1) {
throw new Exception("订单创建失败,原因[数据库操作失败]");
}
}
}
- 配置类(application.yml)
server:
port: 9001
spring:
datasource:
url: jdbc:mysql://localhost:3306/kuangstudy_order?useUnicode=true&characterEncoding=utf-8
username: qyp
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 192.168.179.128
port: 5672
listener:
simple:
acknowledge-mode: manual
retry:
enabled: true
max-attempts: 10
initial-interval: 2000ms
#logging:
# level:
# root: debug
- sql文件
-- Table structure for ksd_dispatcher
-- ----------------------------
DROP TABLE IF EXISTS `ksd_dispatcher`;
CREATE TABLE `ksd_dispatcher` (
`dispatch_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`order_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`status` tinyint(1) NULL DEFAULT NULL,
`user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`order_content` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`create_time` datetime NULL DEFAULT NULL,
PRIMARY KEY (`dispatch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
运单服务(dispatcher-service)
- controller类
@RestController
@RequestMapping("/dispatch")
public class DispatchController {
@Autowired
public DispatchService dispatchService;
@RequestMapping("/order")
public String lock(String orderId) throws Exception {
if(orderId.equals("1000001")){
Thread.sleep(3000L);
}
dispatchService.dispatch(orderId);
return "success";
}
}
- service类
@Service
public class DispatchService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void dispatch(String orderId) throws Exception {
//定义保存sql
String sqlString = "insert into ksd_dispatcher(order_id,dispatch_id,status,user_id,order_content,create_time)" +
"values (?,?,?,?,?)";
//添加运动记录
Timestamp timeStamp = new Timestamp(new Date().getTime());
int count = jdbcTemplate.update(sqlString, orderId, UUID.randomUUID().toString(), 0, "yp" + new Random().nextInt(100) + 1, "木子酱买了一包方便面",timeStamp);
if (count != 1) {
throw new Exception("订单创建失败,原因[数据库操作失败]");
}
}
}
- 配置类
server:
port: 9000
spring:
datasource:
url: jdbc:mysql://localhost:3306/kuangstudy_dispatcher?useUnicode=true&characterEncoding=utf-8
username: qyp
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 192.168.179.128
port: 5672
listener:
simple:
acknowledge-mode: manual
retry:
enabled: true
max-attempts: 10
initial-interval: 2000ms
#logging:
# level:
# root: debug
- sql文件
-- Table structure for ksd_order
-- ----------------------------
DROP TABLE IF EXISTS `ksd_order`;
CREATE TABLE `ksd_order` (
`order_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`order_content` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`create_time` datetime NULL DEFAULT NULL,
PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
正常场景测试成功(两个表都能够成功插入数据)
异常场景测试(order表读取超时,发生回退,dispatch表超时后继续处理,数据可以成功插入,出现数据库不一致)
order表没有插入数据
dispatch表插入该条数据
可靠生产(分布式事务解决方案)
订单工程:transaction-rabbitmq-order
操作原理如下:
- 写入订单信息到订单数据库,同时写入相关数据到订单回执数据库,此时回执数据的status为0
- 发送order信息到rabbitmq中(关联是orderId)
- 监听rabbitmq的回执信息,一旦成功收到该信息,则通过orderId把刚才的回执数据库status改为1
- 即代表该订单信息已经成功发送到rabbitmq,且rabbitmq服务可以正常运行
@Service
public class MQOrderService {
@Autowired
private OrderDataBaseService orderDataBaseService;
@Autowired
private OrderMQService orderMQService;
public void createOrder(Order order) throws Exception {
orderDataBaseService.saveOrder(order);
orderMQService.sendMessage(order);
}
}
@Service
public class OrderMQService {
@Autowired
public RabbitTemplate rabbitTemplate;
@Autowired
public JdbcTemplate jdbcTemplate;
//@PostConstruct注解好多人认为是spring提供的,其实是java自己的注解
/* Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)*/
@PostConstruct
public void regCallback() {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("cause:" + cause);
String orderId = correlationData.getId();
//如果ack为true代表消息已经收到
if (!ack) {
System.out.println("MQ队列应答失败,orderId是:" + orderId);
return;
}
try {
String updatesql = "update ksd_order_message set status = 1 where order_id = ?";
int count = jdbcTemplate.update(updatesql, orderId);
if (count == 1) {
System.out.println("本地消息状态修改成功,消息成功投递到消息队列中...");
}
} catch (Exception e) {
System.out.println("本地消息状态修改失败,出现异常:" + e.getMessage());
}
}
});
}
public void sendMessage(Order order) {
//通过MQ发送信息
rabbitTemplate.convertAndSend("order_fanout_exchange", "", JSONObject.toJSONString(order),
new CorrelationData(order.getOrderId()));
}
}
package com.rabbitmq.service;
import com.rabbitmq.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import java.sql.Timestamp;
import java.util.Date;
import java.util.UUID;
/**
* @ClassName OrderDataBaseService
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/7 18:24
*/
@Service
public class OrderDataBaseService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void saveOrder(Order order) throws Exception {
String sqlString = "insert into ksd_order(order_id,user_id,order_content,create_time)values(?,?,?,?)";
Timestamp curTime = new Timestamp(new Date().getTime());
int count = jdbcTemplate.update(sqlString, order.getOrderId(), order.getUserId(), order.getOrderContent(), curTime);
if (count != 1) {
throw new Exception("订单创建失败,原因[数据库操作失败]");
}
saveLocalMessage(order);
}
/**
* 保存信息到本地
* @Description //TODO
* @param
* @return
* @Date
* @Author
*/
private void saveLocalMessage(Order order) throws Exception {
String sqlString = "insert into ksd_order_message(order_id, order_content,status,unique_id)values(?,?,?,?)";
int count = jdbcTemplate.update(sqlString, order.getOrderId(), order.getOrderContent(), 0, UUID.randomUUID().toString());
if (count != 1) {
throw new Exception("订单状态表创建失败,原因[数据库操作失败]");
}
}
}
测试类:
@SpringBootTest
class TransactionRabbitmqOrderApplicationTests {
@Autowired
private MQOrderService mqOrderService;
@Test
public void orderCreatedMQ() throws Exception {
//订单生成
String orderId = "1000001";
Order order = new Order();
order.setOrderId(orderId);
order.setUserId("1");
order.setOrderContent("买了一个方便面");
mqOrderService.createOrder(order);
System.out.println("订单创建成功");
Thread.sleep(2000);
}
}
配置类:(需要开启publisher-confirm-type)
server:
port: 9001
spring:
datasource:
url: jdbc:mysql://localhost:3306/kuangstudy_order?useUnicode=true&characterEncoding=utf-8
username: qyp
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 192.168.179.128
port: 5672
listener:
simple:
acknowledge-mode: manual
retry:
enabled: true
max-attempts: 10
initial-interval: 2000ms
publisher-confirm-type: correlated
运行结果:
- 数据库订单回执表的status变成1,即成功投递到rabbitmq
- rabbitmq交换机的order.queue队列上有一条消息
可靠消费
派单工程:transaction-rabbitmq-dispatcher
rabbitmq消费方式
@Service
public class OrderMqConsumer {
@Autowired
private DispatchService dispatchService;
private int count = 1;
//解决消息重试的几种方案
//1.控制重发的次数(重试次数到了在自动ack下容易造成消息的丢失或者转移到死信队列)
//2.try+catch+手动ack
//3.try+catch+手动ack+死信队列处理+人工干预
@RabbitListener(queues = {"order.queue"})
public void messageconsumer(String ordermsg, Channel channel,
CorrelationData correlationData,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
System.out.println("收到MQ的消息是:" + ordermsg + ",count=" + count++);
//把ordermsg反序列化成order对象
Order order = JSONObject.parseObject(ordermsg, Order.class);
//获取订单id
String orderId = order.getOrderId();
System.out.println(1/0);
//派单处理
dispatchService.dispatch(orderId);
}
}
发送运单
@Service
public class DispatchService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void dispatch(String orderId) throws Exception {
//定义保存sql
String sqlString = "insert into ksd_dispatcher(order_id,dispatch_id,status,user_id,order_content,create_time)" +
"values (?,?,?,?,?,?)";
//添加运动记录
Timestamp timeStamp = new Timestamp(new Date().getTime());
int count = jdbcTemplate.update(sqlString, orderId, UUID.randomUUID().toString(), 0, "yp" + new Random().nextInt(100) + 1, "木子酱买了一包方便面",timeStamp);
if (count != 1) {
throw new Exception("订单创建失败,原因[数据库操作失败]");
}
}
}
配置文件:
server:
port: 9000
spring:
datasource:
url: jdbc:mysql://localhost:3306/kuangstudy_dispatcher?useUnicode=true&characterEncoding=utf-8
username: qyp
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 192.168.179.128
port: 5672
listener:
simple:
acknowledge-mode: manual
retry:
enabled: true #开启重试
max-attempts: 3 #最大重试次数
initial-interval: 2000ms #重试间隔时间
死信队列的配置类
package com.rabbitmq.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
/**
* @ClassName MqConfig
* @Description TODO
* @Author QiuYiping
* @Date 2022/4/8 13:16
*/
@Configuration
public class MqConfig {
//1.声明注册fanout模式的交换机
@Bean
public FanoutExchange deadExchange() {
return new FanoutExchange("dead_order_exchange", true, false);
}
//2.声明死信队列
@Bean
public Queue deadQueue() {
return new Queue("dead.order.queue", true);
}
//3.声明队列和交换机的绑定
@Bean
public Binding deadBindings() {
return BindingBuilder.bind(deadQueue()).to(deadExchange());
}
//1.声明注册fanout模式的交换机
@Bean
public FanoutExchange orderExchange() {
return new FanoutExchange("order_fanout_exchange", true, false);
}
//2.声明fanout队列
@Bean
public Queue orderQueue() {
HashMap<String, Object> args = new HashMap<>();
//此处是关键,设置死信队列和过期队列的关联
args.put("x-dead-letter-exchange", "dead_order_exchange");
return new Queue("order.queue", true,false ,false ,args);
}
//3.声明队列和交换机的绑定
@Bean
public Binding orderBindings() {
return BindingBuilder.bind(orderQueue()).to(orderExchange());
}
}
问题:
此处出现异常会造成消息无法消费,不停重试造成死循环,消耗服务器性能
方法一:application.yml中配置重试次数,重试次数完了直接结束。该条信息依然存在未丢失
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-08aidsBH-1649410211687)(image-20220408151650296.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WCwInhMi-1649410211688)(image-20220408151933061.png)]
方法二:try+catch+手动ack(出现异常不重试,即使配置了重试次数也无效)
注意
:该条消息因出现异常没有重试而丢失
try {
System.out.println("收到MQ的消息是:" + ordermsg + ",count=" + count++);
//把ordermsg反序列化成order对象
Order order = JSONObject.parseObject(ordermsg, Order.class);
//获取订单id
String orderId = order.getOrderId();
System.out.println(1 / 0); //出现异常
//派单处理
dispatchService.dispatch(orderId);
channel.basicAck(tag, false);
} catch (Exception e) {
// 如果出现异常
// 重发一次后,丢失还是日记,存库需要根据自己的业务场景去决定
// 参数1:消息的tag 参数2:false 多条处理 参数3:requeue 是否重发
// true 则该条信息需要重发,会造成死循环,且配置项中的重试次数会失效
// false 则该条信息不再重发,容易造成消息的丢失,可以采用死信队列接收
channel.basicNack(tag, false, false);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FiEAg6dy-1649410211690)(image-20220408152557426.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aLWFs4DV-1649410211691)(image-20220408152618058.png)]
方法三:try+catch+手动ack+死信队列处理+人工干预
上面出现异常未重试造成消息丢失,可以把出现异常的信息放到死信队列中,在死信队列中重新处理,如果死信队列中再出现异常,则采用人工干预
//把队列和死信队列进行绑定
@Bean
public Queue orderQueue() {
HashMap<String, Object> args = new HashMap<>();
//此处是关键,设置死信队列和过期队列的关联
args.put("x-dead-letter-exchange", "dead_order_exchange");
return new Queue("order.queue", true,false ,false ,args);
}
@Service
public class DeadMqConsumer {
@Autowired
private DispatchService dispatchService;
private int count = 1;
//解决消息重试的几种方案
//1.控制重发的次数(重试次数到了在自动ack下容易造成消息的丢失或者转移到死信队列)
//2.try+catch+手动ack
//3.try+catch+手动ack+死信队列处理+人工干预
@RabbitListener(queues = {"dead.order.queue"})
public void messageconsumer(String ordermsg, Channel channel,
CorrelationData correlationData,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
try {
System.out.println("死信队列收到MQ的消息是:" + ordermsg + ",count=" + count++);
//把ordermsg反序列化成order对象
Order order = JSONObject.parseObject(ordermsg, Order.class);
//获取订单id
String orderId = order.getOrderId();
//派单处理
dispatchService.dispatch(orderId);
channel.basicAck(tag, false);
} catch (Exception e) {
// 如果出现异常
// 重发一次后,丢失还是日记,存库需要根据自己的业务场景去决定
// 参数1:消息的tag 参数2:false 多条处理 参数3:requeue 是否重发
// true 则该条信息需要重发,会造成死循环,且配置项中的重试次数会失效
// false 则该条信息不再重发,容易造成消息的丢失,可以采用死信队列接收
System.out.println("人工干预");
System.out.println("发短信预警");
System.out.println("同时把消息转移到别的DB存储");
channel.basicNack(tag, false, false);
}
}
}
两阶段提交法(2PC)
待续
补偿事务TCC
待续
常见面试题
为什么rabbitMQ是基于channel去处理而不是基于连接?
可以存在没有交换机的队列吗?
不可能,虽然没有指定交换机但是会存在一个默认的交换机