中间件RabbitMQ
目录
一、MQ基本概念
1、概述
MQ全程Message Queue (消息队列),是在消息的传输过程汇总保存消息的容器。多用于分布式系统之间进行通信
分布式系统:是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。(分散压力)
微服务系统:核心就是为了解决应用微服务化之后的服务治理问题。(分散能力)
分布式一定是微服务系统,但是微服务系统,因为微服务系统可能一个模块只使用一台服务器。
总结:
-
MQ,消息队列,存储消息的中间件
-
分布式系统通信两种方式:直接远程调用和借助第三方完成间接通信
-
发送发称为生成者,接收方称为消费者
2、MQ的优势和劣势
优势:
-
解耦合:提高系统的容错性和可维护性
-
异步提速:只需要保存主要业务订单系统操作数据库时间和发送到mq的时间即可,其他业务系统慢慢消费即可
提高用户的体验感和吞吐量(单位时间处理请求的数量)
-
削峰填谷:使用MQ之后限制消费消息的速度为1000,这样一来,高峰期产生的数据势必会别挤压在MQ中,高峰就会被消掉,但是因为消息挤压,在高峰期过后一段时间内,消费消息的速度还是会维持在1000,直到消费完积压的消息,这就叫做填谷
劣势
-
可用性降低
系统引入的外部依赖越多,系统稳定性越差。一旦MQ宕机,就会对业务造成影响。如何保障MQ的高可用?
-
复杂度提高
MQ的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过MQ进行异步调用。如何保证消息没有被重复消费?怎么处理消息丢失情况?确保消息传递顺序问题?
-
一致性问题
A系统处理完业务,通过MQ给B、C、D是三个系统发送消息数据,如果B系统、C系统处理成功,D系统处理失败。如何保证消息数据处理的一致性?
总结
既然MQ有优势也有劣势,那么使用MQ需要满足什么条件呢?
-
生产者不需要从消费者出获得反馈。引入消息队列之前的直接调用,其接口的返回值为空或者没有返回值,这才让明明下层动作还没做,上传确当动作做完了继续往后走,即异步称为可能
-
容许短暂的不一致性
-
确定用了有效果。即解耦、提速、削峰这些方面的收益,超过加入MQ,管理MQ这些成本。
3、常见的MQ产品
4、RabbitMQ的简介
AMQP协议
AMQP即Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受客户端、中间件不同产品,不同的开发语言等条件的限制,2006年,AMQP规范发布。类比HTTP
RabbitMQ
2007年,Rabbit技术公司基于AMQP标准开发的RabbitMQ1.0发布。RabbitMQ采用Erlang语言开发。 Erlang语言由Ericson设计,专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。
RabbitMQ基础架构如下图:
-
Broker:接收和分发消息的应用,RabbitMQ Server 就是Message Broker
-
Virtual host:出于多租户和安全因素设计的,把AMQP的基本组件换分到一个虚拟的分组中,类似于网络中的namespace概念。当多个不同的用户使用同一个RabbitMQ server提供的服务时,可以换分出多个vhost每个用户在自己的vhost创建exchange/queue等
-
Connection:发起者和消费者两者同broker之间的TCP连接
-
Channel:如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也较低。Channel是在connection内部建立的逻辑连接,如果应用程序支持多线程,通过每个thread创建单独的channel进行通讯,AMQP method 包含了channel id帮助客户端和message broker 识别channel,所以channel之间是完全隔离的。Channel作为轻量级的Conneciton极大减少了操作系统建立TCP conneciton的开销
-
Exchange:message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发消息到queue中去。常见的类型有 direct(point-to-point)路由,topic(publish-subscribe)通配符 and fanout(multicast)广播
-
Queue:消息最终被送到这里等待consumer取走
-
Binding:exchange和Queue之间的虚拟连接,binding中可以包含routing key。Binding信息被保存到exchange中的查询表中,用于message的分发依据
工作模式
RabbitMQ提供了6种工作模式:简单模式、work queues、.Publish/Subscribe发布与订阅模式、Routing 路由模式、Topics主题模式、RPC远程调用模式(远程调用,不太算MQ;暂不作介绍)。
JMS
JMS即java消息服务(JavaMessage Service)应用程序接口,是一个Java平台中关于面向消息中间件的API
总结
-
RabbitMQ是基于AMQP协议使用Erlang语言开发的一款消息队列产品
-
RabbitMQ提供了六种工作模式
-
AMQP是协议,类比HTTP
-
JMS是API规范接口,类比JDBC
二、RabbitMQ的安装
使用docker进行安装
1、拉取镜像
docker pull rabbitmq
2、运行
docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management
3、图形化界面
ip地址:端口号 进入图形化界面
默认账号密码 guest guest
三、RabbitMQ的入门程序
注意:下面虚拟机我写的/admin,大家将那一行改成/
工作模式中第一个简单工作模式--作为入门程序
生产者:
pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>rabbitMQ-producer</artifactId>
<version>1.0-SNAPSHOT</version>
<description>生产者</description>
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
代码:
package com.sofwin;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @packageName: com.sofwin
* @author: winter
* @date: 2023/3/26 19:33
* @version: 1.0
* @email [email protected]
* @description: rabbitMQ的简单入门 生产者
*/
public class RabbitProducer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.119.120");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/admin");
//3.创建Connection
Connection connection = connectionFactory.newConnection();
//这里因为是简单模式,使用默认交换exchange
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
queue:队列名称
durable:是否持久化
exclusive:是否独占,只能有一个消费者监听这队列
当connection关闭时,是否删除队列
autoDelete:是否自动删除
arguments:参数
*/
//如果没有hello_rabbitMQ的队列就自动创建
channel.queueDeclare("hello_rabbitMQ", true, false, false, null);
//6.发送消息
/*
basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
exchange:交换器名称
routingKey:路由键
mandatory:
immediate:内部的 一般都是false
props:配置信息
body:发送内容
*/
String str = "hello rabbitMQ";
channel.basicPublish("","hello_rabbitMQ",false,false,null,str.getBytes());
//关闭资源
channel.close();
connection.close();
}
}
消费者:
pom同上
代码:
package com.sofwin;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @packageName: com.sofwin
* @author: winter
* @date: 2023/3/26 19:33
* @version: 1.0
* @email [email protected]
* @description: rabbitMQ的简单入门 消费者
*/
public class RabbitProducer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.119.120");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/admin");
//3.创建Connection
Connection connection = connectionFactory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建回调
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//回调一些消息
System.out.println("接收到消息:"+new String(body));
}
};
//6.本地消费
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
queue:队列名称
autoAck:自动确定
callback:回调
*/
channel.basicConsume("hello_rabbitMQ",true,consumer);
}
}
四、RabbitMQ的工作模式
1、Work queues
-
Work queues 与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息(采用轮循的方式 )
-
应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
代码:
生成者:
package com.sofwin;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @packageName: com.sofwin
* @author: winter
* @date: 2023/3/26 19:33
* @version: 1.0
* @email [email protected]
* @description: rabbitMQ的简单入门 生产者
*/
public class RabbitProducer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.119.120");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/admin");
//3.创建Connection
Connection connection = connectionFactory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
queue:队列名称
durable:是否持久化
exclusive:是否独占,只能有一个消费者监听这队列
当connection关闭时,是否删除队列
autoDelete:是否自动删除
arguments:参数
*/
//如果没有hello_rabbitMQ的队列就自动创建
channel.queueDeclare("workqueues_rabbitMQ", true, false, false, null);
//6.发送消息
/*
basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
exchange:交换器名称
routingKey:路由键
mandatory:
immediate:内部的 一般都是false
props:配置信息
body:发送内容
*/
for (int i = 1; i < 10; i++) {
String str = i+"hello workqueues_rabbitMQ";
channel.basicPublish("","workqueues_rabbitMQ",false,false,null,str.getBytes());
}
//关闭资源
channel.close();
connection.close();
}
}
消费者:代码跟简单模式的消费者一样,只不过是开启了多个消费者(消费者之间采用轮训的方式竞争)
小结
-
在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争关系
-
Work queues 对于任务过重或任务较多的情使用工作队列可以提高任务处理的速度。例如:短信服务部署多个,只需要一个节点成功发送即可
2、Pub/Sub 订阅模式
在订阅模型中,多个一个exchange角色,并且过程略有变化
-
p:生产者,也是就要发送消息的程序,但是不再发送到队列中,而是发给X 交换机
-
C:消费者,消息的接受者,会一直等待消息到来
-
Queue:消息队列,接收消息,缓存消息
-
exchange(x) 交换机,一方面接收生产者发送的消息。另一方面,知道如果处理消息,例如递交给某个特别队列,递交给所有队列,或是将消息丢弃。到底如何操作,取决于exchange的类型
-
exchange有常见以下三种类型:
-
fanout:广播,将消息交给所有绑定到交换机的队列
-
direct:定向,把消息交给符号这栋routing key的队列
-
topic:通配符,把消息交个符合routing pattern 的队列
-
exchange交换会只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
Pub/Sub 订阅模式使用的类型是 fonout类型 2个或者多个消费者会将生成者的消息全部接收
生产者代码:
package com.sofwin;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @packageName: com.sofwin
* @author: winter
* @date: 2023/3/26 19:33
* @version: 1.0
* @email [email protected]
* @description: rabbitMQ的简单入门 生产者
*/
public class RabbitProducer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.119.120");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/admin");
//3.创建Connection
Connection connection = connectionFactory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建交换机
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
exchange:交换机名称
type:类型 枚举类 有四种类型 fanout广播 direct定向 topic通配符
durable:是否持久化
autoDelete:自动删除
internal:内部的
arguments:参数
*/
channel.exchangeDeclare("fanout_exchange", BuiltinExchangeType.FANOUT,true,false,false,null);
//6.创建队列
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
queue:队列名称
durable:是否持久化
exclusive:是否独占,只能有一个消费者监听这队列
当connection关闭时,是否删除队列
autoDelete:是否自动删除
arguments:参数
*/
//如果没有hello_rabbitMQ的队列就自动创建
channel.queueDeclare("fanout_rabbitMQ1", true, false, false, null);
channel.queueDeclare("fanout_rabbitMQ2", true, false, false, null);
//7.进行绑定
/*
queueBind(String queue, String exchange, String routingKey)
queue:队列名称
exchange:交换机名称
routingKey:路由键
当类型为fanout的时候 routingKey为""
*/
channel.queueBind("fanout_rabbitMQ1","fanout_exchange","");
channel.queueBind("fanout_rabbitMQ2","fanout_exchange","");
//6.发送消息
/*
basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
exchange:交换器名称
routingKey:路由键
mandatory:
immediate:内部的 一般都是false
props:配置信息
body:发送内容
*/
String str = "hello fonout";
channel.basicPublish("fanout_exchange","",false,false,null,str.getBytes());
//关闭资源
channel.close();
connection.close();
}
}
消费者代码:
跟最开始没有边,就是将队列名称修改一下
3、Routing模式
-
队列与交换机的绑定,不是任意绑定了,而是指定一个RoutingKey 路由键
-
消息的发送方在想Exchange发送消息时,也必须指定消息的RoutingKey
-
exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing key 进行判断,只有队列的Routing key与消息的Routing key 完全一致,才会接收到消息
生产者代码:
package com.sofwin;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @packageName: com.sofwin
* @author: winter
* @date: 2023/3/26 19:33
* @version: 1.0
* @email [email protected]
* @description: rabbitMQ的简单入门 生产者
*/
public class RabbitProducer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.119.120");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/admin");
//3.创建Connection
Connection connection = connectionFactory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建交换机
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
exchange:交换机名称
type:类型 枚举类 有四种类型 fanout广播 direct定向 topic通配符
durable:是否持久化
autoDelete:自动删除
internal:内部的
arguments:参数
*/
channel.exchangeDeclare("direct_exchange", BuiltinExchangeType.DIRECT,true,false,false,null);
//6.创建队列
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
queue:队列名称
durable:是否持久化
exclusive:是否独占,只能有一个消费者监听这队列
当connection关闭时,是否删除队列
autoDelete:是否自动删除
arguments:参数
*/
//如果没有hello_rabbitMQ的队列就自动创建
channel.queueDeclare("direct_rabbitMQ1", true, false, false, null);
channel.queueDeclare("direct_rabbitMQ2", true, false, false, null);
//7.进行绑定
/*
queueBind(String queue, String exchange, String routingKey)
queue:队列名称
exchange:交换机名称
routingKey:路由键
当类型为fanout的时候 routingKey为""
*/
channel.queueBind("direct_rabbitMQ1","direct_exchange","error");
channel.queueBind("direct_rabbitMQ2","direct_exchange","info");
channel.queueBind("direct_rabbitMQ2","direct_exchange","error");
channel.queueBind("direct_rabbitMQ2","direct_exchange","winter");
//6.发送消息
/*
basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
exchange:交换器名称
routingKey:路由键
mandatory:
immediate:内部的 一般都是false
props:配置信息
body:发送内容
*/
String str = "hello direct";
// channel.basicPublish("direct_exchange","error",false,false,null,str.getBytes());
channel.basicPublish("direct_exchange","info",false,false,null,str.getBytes());
//关闭资源
channel.close();
connection.close();
}
}
消费者代码:
将队列名称修改即可
小结:routing模式要求队列在绑定交换机时需要指定routing key,消息会转发到符合routing key 的队列
4、Topics 通配符模式
最为强大的模式,最常见
注意:*代表一个单词 #代表0个或者多个
例如 *.info win.info可以 但是win.win.info就不行了
#.info win.info、win.win.info 都可以
生产者代码:
package com.sofwin;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @packageName: com.sofwin
* @author: winter
* @date: 2023/3/26 19:33
* @version: 1.0
* @email [email protected]
* @description: rabbitMQ的简单入门 生产者
*/
public class RabbitProducer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.119.120");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/admin");
//3.创建Connection
Connection connection = connectionFactory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建交换机
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
exchange:交换机名称
type:类型 枚举类 有四种类型 fanout广播 direct定向 topic通配符
durable:是否持久化
autoDelete:自动删除
internal:内部的
arguments:参数
*/
channel.exchangeDeclare("topic_exchange", BuiltinExchangeType.TOPIC,true,false,false,null);
//6.创建队列
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
queue:队列名称
durable:是否持久化
exclusive:是否独占,只能有一个消费者监听这队列
当connection关闭时,是否删除队列
autoDelete:是否自动删除
arguments:参数
*/
//如果没有hello_rabbitMQ的队列就自动创建
channel.queueDeclare("topic_rabbitMQ1", true, false, false, null);
channel.queueDeclare("topic_rabbitMQ2", true, false, false, null);
//7.进行绑定
/*
queueBind(String queue, String exchange, String routingKey)
queue:队列名称
exchange:交换机名称
routingKey:路由键
当类型为fanout的时候 routingKey为""
*/
channel.queueBind("topic_rabbitMQ1","topic_exchange","*.orange.*");
channel.queueBind("topic_rabbitMQ2","topic_exchange","*.*.rabbite");
channel.queueBind("topic_rabbitMQ2","topic_exchange","Lazy.#");
//6.发送消息
/*
basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
exchange:交换器名称
routingKey:路由键
mandatory:
immediate:内部的 一般都是false
props:配置信息
body:发送内容
*/
String str = "hello topic";
// channel.basicPublish("direct_exchange","error",false,false,null,str.getBytes());
channel.basicPublish("topic_exchange","error.info.rabbite",false,false,null,str.getBytes());
//关闭资源
channel.close();
connection.close();
}
}
消费者代码:
修改队列名称即可
小结:
topic主体模式可以实现pub/sub发布与订阅模式和Routing路由模式的功能,只是topic在配置routing key的时候可以使用通配符,显得更加灵活
五、Spring整合RabbitMQ
1、生产者
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring_rabbitMQ</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
properties配置文件:
rabbitmq.host=192.168.119.120
rabbitmq.port=5672
rabbitmq.username=admin
rabbitmq.password=admin
rabbitmq.virtual-host=/admin
spring配置文件:
包含简单模式、fanout模式、topic通配符模式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机
默认交换机类型为direct,名字为:"",路由键为队列的名称
-->
<rabbit:queue id="spring_queue" name="spring_queue" auto-declare="true"/>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~广播;所有队列都能收到消息~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>
<!--定义广播类型交换机;并绑定上述两个队列-->
<rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="spring_fanout_queue_1"/>
<rabbit:binding queue="spring_fanout_queue_2"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~通配符;*匹配一个单词,#匹配多个单词 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_star" name="spring_topic_queue_star" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well" name="spring_topic_queue_well" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well2" name="spring_topic_queue_well2" auto-declare="true"/>
<rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding pattern="heima.*" queue="spring_topic_queue_star"/>
<rabbit:binding pattern="heima.#" queue="spring_topic_queue_well"/>
<rabbit:binding pattern="itcast.#" queue="spring_topic_queue_well2"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
测试:
package com.sofwin;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @packageName: com.sofwin
* @author: winter
* @date: 2023/3/26 22:25
* @version: 1.0
* @email [email protected]
* @description: TODO
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class ProducerTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testRabbit() {
rabbitTemplate.convertAndSend("","spring_queue","helloword----");
}
@Test
public void testRabbit2() {
rabbitTemplate.convertAndSend("spring_fanout_exchange","","helloword fanout----");
}
@Test
public void testRabbit3() {
rabbitTemplate.convertAndSend("spring_topic_exchange","heima.a.a","helloword fanout----");
}
}
2、消费者
pom、properties跟生产者一样
spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<bean id="springQueueListener" class="com.itheima.rabbitmq.listener.SpringQueueListener"/>
<!-- <bean id="fanoutListener1" class="com.itheima.rabbitmq.listener.FanoutListener1"/>-->
<!-- <bean id="fanoutListener2" class="com.itheima.rabbitmq.listener.FanoutListener2"/>-->
<!-- <bean id="topicListenerStar" class="com.itheima.rabbitmq.listener.TopicListenerStar"/>-->
<!-- <bean id="topicListenerWell" class="com.itheima.rabbitmq.listener.TopicListenerWell"/>-->
<!-- <bean id="topicListenerWell2" class="com.itheima.rabbitmq.listener.TopicListenerWell2"/>-->
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
<!-- <rabbit:listener ref="fanoutListener1" queue-names="spring_fanout_queue_1"/>-->
<!-- <rabbit:listener ref="fanoutListener2" queue-names="spring_fanout_queue_2"/>-->
<!-- <rabbit:listener ref="topicListenerStar" queue-names="spring_topic_queue_star"/>-->
<!-- <rabbit:listener ref="topicListenerWell" queue-names="spring_topic_queue_well"/>-->
<!-- <rabbit:listener ref="topicListenerWell2" queue-names="spring_topic_queue_well2"/>-->
</rabbit:listener-container>
</beans>
监听者代码:
package com.itheima.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
/**
* @packageName: com.itheima.rabbitmq.listener
* @author: winter
* @date: 2023/3/26 22:41
* @version: 1.0
* @email [email protected]
* @description: TODO
*/
public class SpringQueueListener implements MessageListener {
@Override
public void onMessage(Message message) {
System.out.println(new String(message.getBody()));
}
}
测试:
保证运行,持续监听
package com.itheima.rabbitmq.listener;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @packageName: com.sofwin
* @author: winter
* @date: 2023/3/26 22:25
* @version: 1.0
* @email [email protected]
* @description: TODO
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class ProducerTest {
@Test
public void testRabbit() {
while (true) {
}
}
}
六、SpringBoot整合RabbitMQ
1、生产者
pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springboot-rabbitMQ-producer</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
yml配置文件:
spring:
rabbitmq:
host: 192.168.119.120
port: 5672
username: admin
password: admin
# 虚拟机 不写默认/
virtual-host: /admin
配置类:
package com.sofwin.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;
/**
* @packageName: com.sofwin.config
* @author: winter
* @date: 2023/3/26 23:05
* @version: 1.0
* @email [email protected]
* @description: TODO
*/
@Configuration
public class RabbitMQConfig {
@Bean("bootExchange")
public Exchange getExchange() {
return ExchangeBuilder.topicExchange("topic_exchange").durable(true).build();
}
@Bean("bootQueues")
public Queue getQueues() {
return QueueBuilder.durable("topic_queue").build();
}
@Bean("bootQueues2")
public Queue getQueues2() {
return QueueBuilder.durable("topic_queue2").build();
}
@Bean
public Binding getBinding(@Qualifier("bootExchange") Exchange exchange, @Qualifier("bootQueues") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with("*.rout").noargs();
}
@Bean
public Binding getBinding2(@Qualifier("bootExchange") Exchange exchange, @Qualifier("bootQueues2") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with("*.wqq").noargs();
}
@Bean
public Binding getBinding3(@Qualifier("bootExchange") Exchange exchange, @Qualifier("bootQueues2") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with("*.rout.#").noargs();
}
}
测试类:
package com.sofwin;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @packageName: com.sofiwin
* @author: winter
* @date: 2023/3/26 23:14
* @version: 1.0
* @email [email protected]
* @description: TODO
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class PorducerTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void test01() {
rabbitTemplate.convertAndSend("topic_exchange","ww.rout","你好啊 销毁欧巴");
}
}
2、消费者
pom文件、yml文件同上
监听者:
package com.sofwin.rabbitmq01consumer.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @packageName: com.sofwin.rabbitmq01consumer.listener
* @author: winter
* @date: 2023/3/26 23:34
* @version: 1.0
* @email [email protected]
* @description: TODO
*/
@Component
public class RabbitMQListener {
@RabbitListener(queues ="topic_queue" )
public void ListenerQueue(Message message) {
System.out.println(new String(message.getBody()));
}
}
启动启动类就可以监听到消息
3、小结
-
springboot提供了快速整合RabbitMQ的方式
-
基本信息在yml中配置,队列、交换机、以及绑定关系在配置类中使用@Bean的方式配置
-
生产端直接注入RabbitTempate完成消息发送
-
消费端使用@RabbitListener进行消息监听,接收消息
七、RabbitMQ高级特性
1、生产方消息的可靠性
在使用RabbitMQ的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败的场景.RabbitMQ为我们提供了两种方式用来控制消息的投递的可靠性模式
-
confirm 确认模式
-
return 退回模式
rabbitMQ整个消息投递的路径为:
producer----->rabbitMQ broker ----->exchange ----->queue ----->consumer
-
消息从producer到exchange则会发挥一个confirmCallback
-
消息从exchange ----->queue投递失败则会返回一个returnCallBack
利用这两个callback控制消息的可靠性投递
1.1 confirmCallback
-
在yml中设置publisher-confirms等于true
spring: rabbitmq: # 开启confirm 返回回到函数 从producer到exchange之间 publisher-confirms: true
-
使用rabbitTemplate.setCoonfirmCallBack设置回调函数。当交换收到消息过后,调用confirm方法。在方法中判断ack。如果为true,则发送成功,如果为false,则发送失败,并且包含有失败原因cause
springboot代码:
-
yml配置开启confirmCallback
spring: rabbitmq: host: 192.168.119.120 port: 5672 username: admin password: admin virtual-host: /admin # 开启confirm 返回回到函数 从producer到exchange之间 publisher-confirms: true
-
编写测试代码,设置confirmCallback处理方案
package com.sofwin;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @packageName: com.sofiwin
* @author: winter
* @date: 2023/3/26 23:14
* @version: 1.0
* @email [email protected]
* @description: TODO
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class PorducerTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void test01() {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 配置相关信息
* @param ack exchange是否成功收到消息 true 收到 ,false 没有收到
* @param cause 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
System.out.println("执行成功"+cause);
System.out.println(new String(correlationData.getReturnedMessage().getBody()));
}else {
System.out.println("执行失败,失败原因:"+cause);
}
}
});
//测试加入 CorrelationData参数
// CorrelationData correlationData = new CorrelationData();
// byte[] bytes = "correlationData 测试".getBytes();
// Message message = new Message(bytes,null);
// correlationData.setReturnedMessage(message);
// rabbitTemplate.convertAndSend("topic_exchange","ww.rout"
// ,"你好啊 销毁欧巴", correlationData);
rabbitTemplate.convertAndSend("topic_exchange","ww.rout"
,"你好啊 销毁欧巴");
//测试 发送失败写法
// rabbitTemplate.convertAndSend("topic_exchange1111","ww.rout","你好啊 销毁欧巴");
}
}
1.2 returnCallBack
-
在yml中设置publisher-returns为true
spring: rabbitmq: # 开启confirm 返回回到函数 从producer到exchange之间 publisher-confirms: true # 开启returns 判断exchange 到Queues之间 发送情况 publisher-returns: true
-
使用rabbitTemplate.setReturnCallback设置回调函数,当消息从exchange路由到queue失败后,如果设置了rabbitTemplage.setMandatory(true)参数,则会将消息退回给producer,并执行回调函数returnedMessage
代码如下:
yml配置:
spring: rabbitmq: host: 192.168.119.120 port: 5672 username: admin password: admin virtual-host: /admin # 开启confirm 返回回到函数 从producer到exchange之间 publisher-confirms: true # 开启returns 判断exchange 到Queues之间 发送情况 publisher-returns: true
测试代码:
@Test
public void test3() {
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
*
* @param message 消息
* @param replyCode 回复代码
* @param replyText 回复文本
* @param exchange 交换机
* @param routingKey 路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println(message);
System.out.println(replyCode);
System.out.println(replyText);
System.out.println(exchange);
System.out.println(routingKey);
}
});
//设置进行托管
rabbitTemplate.setMandatory(true);
//键值错误 ,无法发送,因为设置了托管,所以就调用returnedMessage方法
rabbitTemplate.convertAndSend("topic_exchange","ww.rout.www","你好啊 销毁欧巴");
}
1.3 事务
在RabbitMQ中也提供了事务机制,但是性能较差,此处不做讲解
使用channel下列方法,完成事务控制:
txSelect()用于将channel设置成transaction模式
txCommit() 用于提交事务
txRollback() 用于回滚事务
2、消费方消息的可靠性
Consumer Ack
ack指Acknowledge,确认,表示消费端收到消息后的确认方式。
有三种确认方式:
自动确认:acknowledge-mode: "none"
收到确认:acknowledge-mode: "manual"
根据异常情况确认:acknowledge-mode: "auto" (这种方式使用麻烦,不作讲解)
其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应message从RabbitMQ的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel。basicNack()方法,让其自动发送消息
代码:
yml配置:
spring: rabbitmq: host: 192.168.119.120 port: 5672 username: admin password: admin virtual-host: /admin listener: simple: acknowledge-mode: manual
监听器:
package com.sofwin.rabbitmq01consumer.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
/**
* @packageName: com.sofwin.rabbitmq01consumer.listener
* @author: winter
* @date: 2023/3/26 23:34
* @version: 1.0
* @email [email protected]
* @description: TODO
*/
@Component
public class RabbitMQListener implements ChannelAwareMessageListener {
@RabbitListener(queues ="topic_queue" )
@Override
public void onMessage(Message message, Channel channel) throws Exception {
try {
System.out.println(new String(message.getBody()));
System.out.println("业务逻辑");
/*basicAck(long deliveryTag, boolean multiple)
deliveryTag:标签
multiple:可以同时签收
*/
int i = 1 / 0;
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
} catch (Exception e) {
/*
basicNack(long deliveryTag, boolean multiple, boolean requeue)
deliveryTag:标签
multiple:同时处理
requeue:是否重新发送
*/
channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,
true
);
}
}
}
小结:
-
在yml配置文件中设置:acknowledge-mode: manual 手动模式
-
监听器实现ChannelAwareMessageListener接口,重写onMessage方法
-
如果在消费端没有出现异常,则调用channel.basicAck(),进行签收,如果不签收是不会进行消费消息的(系统关闭后,消息还在)
-
如果出现异常,则在catch中调用channel.basicNack,拒绝消息,如果最后参数是true则重发消息,false则丢弃
消息可靠性总结:
-
持久化
-
exchange持久化
-
queue持久化
-
message持久化
设置durable属性为true
-
-
生产方确认Confirm、Return
-
消费方确认Ack
-
Broker高可用(集群)
3、消费端限流
设置acknowledge为手动提交 即 manual
设置yml中属性prefetch 限流多少
代码:
spring: rabbitmq: host: 192.168.119.120 port: 5672 username: admin password: admin virtual-host: /admin listener: simple: # ack手动提交 acknowledge-mode: manual # 消费端一次取的次数 1次 prefetch: 1
4、TTL
-
TTL全程Time To Live (存活时间/过期时间)
-
当消息到达存活时间后,还没有被消费,会自动被清除。
-
RabbitMQ可以对消息设置过期时间,也可以对整个队列设置过期时间
4.1 队列设置过期时间
x-message-ttl 参数设置过期时间
package com.sofwin.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;
/**
* @packageName: com.sofwin.config
* @author: winter
* @date: 2023/3/26 23:05
* @version: 1.0
* @email [email protected]
* @description: TODO
*/
@Configuration
public class RabbitMQConfig {
@Bean("topic_exchange")
public Exchange getExchange() {
return ExchangeBuilder.topicExchange("topic_exchange").durable(true).build();
}
@Bean("topic_queue")
public Queue getQueues() {
return QueueBuilder.durable("topic_queue").withArgument("x-message-ttl",10000).build();
}
@Bean
public Binding getBinding(@Qualifier("topic_exchange") Exchange exchange, @Qualifier("topic_queue") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with("*.rout").noargs();
}
}
4.2 消息设置过期时间
创建MessagePostProcessor对象重写postProcessMessage方法,设置message中messageProperties属性expiration过期时间
然后在convertAndSend方法加入MessagePostProcessor对象
@Test
public void test04() {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 配置相关信息
* @param ack exchange是否成功收到消息 true 收到 ,false 没有收到
* @param cause 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
System.out.println("执行成功"+cause);
System.out.println(new String(correlationData.getReturnedMessage().getBody()));
}else {
System.out.println("执行失败,失败原因:"+cause);
}
}
});
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
return message;
}
};
rabbitTemplate.convertAndSend("topic_exchange","ww.rout"
,"你好啊 qqq",messagePostProcessor);
}
注意:
-
如果消息和队列都设置了过期时间,以时间短为准
-
队列过期后,会将队列所有消息全部溢出
-
消息过期后,只有消息在队列顶端,才会判断其是否过期
5、DLX 死信队列
死信队列,英文缩写DLX,Dead Letter Exchange (死信交换机),当消息称为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX
5.1、消息称为死信的三种情况:
-
队列消息长度到达限制 x-max-length
-
消费者拒接消费消息,basicNack,并且不把消息重新放入原目标队列,requeue=false
-
原队列存在消息过期设置,消息大大超时时间未被消费
5.2 队列绑定死信交换机
给队列设置参数:x-dead-letter-exchange 和x-dead-letter-routing-key
步骤:
-
声明正常队列和交换机
-
声明死信队列和死信交换机
-
正常队列绑定私信交换机
设置两个参数:x-dead-letter-exchange:死信交换机名称
x-dead-letter-routing-key:发送给死信交换机的routingkey
package com.sofwin.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;
/**
* @packageName: com.sofwin.config
* @author: winter
* @date: 2023/3/26 23:05
* @version: 1.0
* @email [email protected]
* @description: TODO
*/
@Configuration
public class RabbitMQConfig {
@Bean("topic_exchange")
public Exchange getExchange() {
return ExchangeBuilder.topicExchange("topic_exchange").durable(true).build();
}
@Bean("topic_queue")
public Queue getQueues() {
Map<String,Object> map = new HashMap<>();
map.put("x-dead-letter-exchange","dlx_topic_exchange");
map.put("x-dead-letter-routing-key","ww.dlx");
map.put("x-message-ttl",10000);
return QueueBuilder.durable("topic_queue").withArguments(map).build();
}
@Bean
public Binding getBinding(@Qualifier("topic_exchange") Exchange exchange, @Qualifier("topic_queue") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with("*.rout").noargs();
}
//死信交换机和队列
@Bean("dlx_topic_exchange")
public Exchange getExchange2() {
return ExchangeBuilder.topicExchange("dlx_topic_exchange").durable(true).build();
}
@Bean("dlx_topic_queue")
public Queue getQueues2() {
return QueueBuilder.durable("dlx_topic_queue").build();
}
@Bean
public Binding getBinding2(@Qualifier("dlx_topic_exchange") Exchange exchange, @Qualifier("dlx_topic_queue") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with("*.dlx").noargs();
}
}
注意:
-
死信交换机和死信队列和普通的没有区别
-
当消息称为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列
6、延迟队列
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费
需求:
-
下单后,30分钟未支付,取消订单,回滚库存。
-
新用户注册成功七天后,发送短信问候
实现方式:
-
定时器
每隔一段时间进行轮训,但是对数据库压力比较大,并且有误差(不推荐)
-
延迟队列(推荐)
但是RabbitMQ未提供延迟队列,但是可以使用TTL和DLX配合完成
7、 日志与监控
7.1 RabbitMQ日志
RabbitMQ默认日志存储路径: /var/log/rabbitmq/[email protected] xxx是主机名 (我用docker安装的日志是
docker logs -f 容器ID
)
7.2 RabbitMQ监控命令
进入docker exec -it rabbitmq /bin/bash
然后进行输入命令
查看队列
rabbitmgctl list_queues查看环境变量
rabbitmgctl environment
查看exchanges
rabbitmgctl list_exchanges查看未被确认的队列
rabbitmgctl list_queues name messages_unacknowledged查看用户
rabbitmqctl list_users查看单个队列的内存使用
rabbitmgctl list_queues name memory
查看连接
rabbitmgctl list connections查看准备就绪的队列
rabbitmgctl list_queues name messages_ready
查看消费者信息
rabbitmgctl list cosumers
8、消息追踪
在使用任何消息中间件的过程中,难免会出现某条消息异常丢失的情况。对于RabbitMQ而言,可能是因此生产者或消费者与RabbitMQ断开了连接,而他们与RabbitMQ有采用不同的确认机制;也可能是因为交换机与队列之间不同的转发策略;甚至是交换器并没有任何队列进行绑定,生产者有不感知或者没有采取响应的措施;另外RabbitMQ本身的集群策略也可能导致消息的丢失。这个时候就需要有一个较好的机制跟踪记录消息的投递过程,以此协助开发和运维人员进行问题的定位。
RabbitMQ中可以使用Firehose和rabbitmq_tracing插件功能来实现消息追踪
8.1 消息追踪-Firehose
firehose的机制将生产者投递给rabbitmq的消息,按照指定的格式发送到默认的exchange上,这个默认的exchange的名称为amq.rabbitmq.trace,它是一个topic类型的exchange,发送到这个exchange消息的routing key为publish.exchangename 和deliver.queuename, 其中exchangename和queuename分别是实现exchange和queue的名称
注意:打开trace会影响消息写入功能,适当打开后请关闭
rabbitmqctl trace_on :开启
rabbitmqctl trace_off :关闭
实现:
1、创建一个队列 test_queue
2、test_queue队列和默认的交换机amq.rabbitmq.trace进行了绑定,并且开启了rabbitmqctl trace_on
docker exec -it rabbitmq /var/log/rabbitmq //指定虚拟机是/admin 不写默认是/的虚拟机 即:rabbitmqctl trace_on rabbitmqctl trace_on -p /admin
3.使用之前的topic_exchange交换机绑定test_queue队列,并且 给test_queue发送一个消息
然后得到test_queue就得到3条消息
8.2 消息最终-rabbitmq_tracing
rabbitmq_tracing和Firehose在实现上如出一辙,只不过rabbitmq_tracing的方式比Firehose:多了一 层GU的包装,更容易使用和管理。 启用插件:rabbitmq-plugins enable rabbitmq_tracing
docker exec -it rabbit /bin/bash abbitmq-plugins enable rabbitmq_tracing
注意:
默认的虚拟机 / ,就不需要写入账号密码
八、RabbitMQ应用问题
-
消息可靠性保障
-
消息补偿机制
-
-
消息幂等性保障
-
乐观锁解决方案
-
1、消息补偿机制
2、消息幂等性保障
幂等性指一次和多次请求某一资源,对于资源本身应该具有同样的结果。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
在MQ中指,消费多条相同的消息,得到与消费该消息一次相同的结果。
通过乐观锁进行解决---即version字段