Springboot整合RabbitMQ
MQ
简介
消息队列(Message Queue
,简称MQ
),只保存消息的一个容器,本质上是一个容器。
简单来说,消息队列就是一个存储消息的容器,它接受消息发布者发布的消息,然后将消息存储在容器中等待之后的消费者到容器里消费消息。
这就是最简单的一个消息队列的模型
Producer
:消息生产者,负责产生和发布消息到Broker
中
Broker
:消息处理中心,负责消息的存储,确认等等,一般其中会包含多个queue
Consumer
:消息消费者,负责从Broker
中获取消息,并进行相关处理(消费)
功能
MQ
的存在提供了三个非常重要的功能
解耦
![](https://i-blog.csdnimg.cn/blog_migrate/8c12e1edbee54eeb0f44b0e675faaf26.png)
例如上图,我们有A,B,C,D,E
五个系统,原先我A
系统只要发送数据到B,C,D
三个系统,但是后来需求增加,现在E
也需要A
系统的数据,所以只能在A
系统中添加相关功能。这只是简单的描述,实际开发中系统要比这复杂的很多,所以就会导致系统耦合度极高。
这时候我们引入MQ
![](https://i-blog.csdnimg.cn/blog_migrate/92b17ffd12421de6579ac70cc3407f62.png)
系统A
只需要讲数据发送到MQ
中,完全不用管哪些系统会消费数据,是否消费成功等等,而需要这个数据的系统直接从MQ
中拿就好了,也并不需要知道发布者是谁,更不用与发布者产生关系。
可以看到,通过添加一层消息队列,整个系统的耦合度大大降低。
异步
![](https://i-blog.csdnimg.cn/blog_migrate/3c21a7f24cab3e5a3f3cc396f541d405.png)
我们继续看这个场景,用户对A
发送请求,花费3+300+450+200= 953ms
,这还是只是简单的几个接口调用,具体开发场景下会有数十个或者上百个接口,如果同步执行,那耗时必然会非常的多。
那么我们使用MQ
![](https://i-blog.csdnimg.cn/blog_migrate/07edd42af67eda080ee8f655193c8117.png)
我们使用消息队列之后,A
系统只要将数据发送到MQ
中即可返回,这样对于用户来说,只需要5ms
即可实现响应,虽然后续操作并没有在5ms
内完成。而且总的时间也大大降低了,类似于多线程处理(多线程只能在本地上实现,而消息队列可以在不同项目中,不同服务器中运用)
削峰
削峰简单来说就是能够缓解高峰期的峰值并发请求
![](https://i-blog.csdnimg.cn/blog_migrate/0e002672dfc932ee9d8351180c01c649.png)
假设我们有这样一个系统,平时请求量很少,我们的服务器和数据库完全能应付,但是某个时间段,例如整点秒杀活动,突然涌入5k的请求,而我们的数据库最多只能处理2k的并发请求,这样不进行削峰我们数据库就会直接瘫痪挂掉。
而使用MQ
之后
![](https://i-blog.csdnimg.cn/blog_migrate/e52beb1aa51f9760c6a0efa6fc2bb4f7.png)
虽然每秒有5k个请求,但是不会直接涌入我的服务器或者数据库,而是进入消息队列中等待我系统的拉取,这样这个系统都是在我的可控范围之内,不会出现服务器或者数据库瘫痪现象。
缺陷
可用性
![](https://i-blog.csdnimg.cn/blog_migrate/92b17ffd12421de6579ac70cc3407f62.png)
就拿这个场景来看,所有的要获取这个数据的系统都连接MQ
等待消费数据,但是当MQ
出现故障宕机了,所有的相关系统也就都不能获取数据。这是非常严重的,所以我们要保证消息队列的高可用性(例如集群模式,镜像模式,设置监测节点和备用节点等等)
复杂度增加
通过添加MQ
确实能解决很多问题,但是可能会导致有些逻辑更加复杂。例如如何保证消息没有被重复消费(保证幂等性),保证消息传递顺序等等
事务性
![](https://i-blog.csdnimg.cn/blog_migrate/07edd42af67eda080ee8f655193c8117.png)
上面场景我们虽然能实现用户在5ms内就能收到反馈,但是具体逻辑还在运行,倘若C
系统执行失败怎么办?
所以我们可以将整个逻辑看作事务进行处理,要么全部成功要么全部失败。
RabbitMQ
RabbitMq
其实是AMQP
协议的一个开源体现,内部就是AMQP
的实现
AMQP
与我们熟悉的消息队列有一点点差别,其中增加了binding
和exchange
![](https://i-blog.csdnimg.cn/blog_migrate/42aa2c19aa926c08d44e312b4976979f.png)
这就是一个经典的AMQP
模型
producer
生产者,发布者
Exchange
交换机,根据Binding
中设置的路由规则将消息发送到相应的Queue
中
Binding
绑定,设置路由匹配规则
Queues
队列
Consumer
消费者
Exchange
上面的大部分元素我们已经非常熟悉了,这里就重点看一下交换机Exchange
交换机根据路由规则不同,又分为三种不同的交换机:
DirectExchange
顾名思义,直连交换机,简单来说就是单播
![](https://i-blog.csdnimg.cn/blog_migrate/faf5905cf127b39cf78fcc9ca6fff6d0.png)
消息中的路由键Routing key
与和binding
中设置的binding key
完全一致,交换机就会将消息发送到对应的队列中
FanoutExchange
扇形交换机,简单来说就是广播
每个发送到扇形交换机的消息都会被发送到与其绑定的所有队列中,不需要路由键
TopicExchange
消息订阅交换机
消息订阅模式就是当消息携带的路由键值符合设定的路由规则时,将符合的消息发送到绑定的队列中
例如图上红色队列设定的路由键为usa.#
,表示匹配所有以usa.
开头的路由键,所以可以收到usa.news
和usa.weather
的消息
#
匹配1个或多个字符,可以使用binding key=#
匹配任意字符
*
匹配1个
所以RabbitMq
使用非常简单,我们只需要设置交换机,队列,以及使用一定的路由规则将他们绑定在一起即可。
Springboot 使用 RabbitMQ
由于要使用消息队列需要消息发布者和消费者,所以本项目分为两个子项目,publisher
和consumer
配置
两个项目的配置都是一样的
pom.xml
<!-- RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
application.yml
server:
# consumer端口设置为8080
port: 8090
spring:
# 配置rabbitmq
rabbitmq:
host: 192.168.56.129
# rabbitmq主服务端口为5672,管理页面端口为15672
port: 5672
username: rabbitadmin
password: rabbitpwd
# 设置回调函数
# confirm-type: 默认为None即不启用,
# correlated: 发布消息成功或失败到达交换机时触发回调方法
# simple: 具有两个功能
# 1.与correlated相同
# 2.可以使用rabbittemplate调用waitforconfirm或waitForConfirmsOrDie
# 等待broker节点返回发送结果,根据结果来判定下一步逻辑
publisher-confirm-type: correlated
publisher-returns: true
回调确认函数我们在后面会详细说明
我们将通过实现三种不同的交换机来展示rabbitmq
在springboot
中的大概使用方法
DirectExchange
publisher
@Configuration
public class DirectRabbitConfig {
/**
* 设置队列
* Queue(name,durable,exclusive,autoDelete)
* name: 队列名称
* durable: 是否持久化,即是否会被存储到磁盘上,当消息代理重启时仍存在,默认为true
* exclusive:只能被当前创建的连接使用,而且当连接关闭队列后立即删除,默认为false
* autoDelete: 是否自动删除,当没有生产者或者消费者使用此队列时自动删除,默认为false
* @return
*/
@Bean
public Queue directQueue(){
return new Queue("DirectQueue",true);
}
/**
* 设置交换机
* DirectExchange(name,durable,autoDelete)
* @return
*/
@Bean
public DirectExchange directExchange(){
return new DirectExchange("DirectExchange");
}
/**
* 设置binding,即路由规则
* bind(Queue()).to(Exchange()).with(routingkey)
* @return
*/
@Bean
public Binding bindDirect(){
return BindingBuilder.bind(directQueue()).to(directExchange()).with("DirectRouting");
}
}
这里我们定义一个配置类,主要内容就是:
-
设置队列
return new Queue(name,durable,exclusive,autoDelete)
name
:队列名称,后续用于消费者获取消息durable
:是否持久化,即是否会被存储到磁盘上,当消息代理重启时仍存在,默认为true
exclusive
:只能被当前创建的连接使用,而且当连接关闭队列后立即删除,默认为false
autoDelete
:是否自动删除,当没有生产者或者消费者使用此队列时自动删除,默认为false
-
设置交换机
return new DirectExchange(name,durable,autoDelete)
name
:交换机名称,后续用于消息发布者发布消息durable
:是否持久化,即是否会被存储到磁盘上,当消息代理重启时仍存在,默认为true
autoDelete
:是否自动删除,当没有生产者或者队列使用该交换机时自动删除,默认为false
-
绑定交换机和队列
return BindingBuilder.bind(Queue()).to(Exchange()).with(routingkey)
Queue()
:为需要绑定的队列Exchange()
:为需要绑定的交换机routingKey
:设置路由键,用于交换机将消息发送到队列中
定义好了这些配置之后我们就可以开始发送数据了
我们使用springboot
提供的RabbitTemplate
进行消息的发送
RabbitTemplate
提供了对于rabbitmq
非常多的操作,包括设置交换机,设置路由键,设置队列等等
@PostMapping("/direct")
public String sendDirectMessage(@RequestParam("message")String message){
String messageId = String.valueOf(UUID.randomUUID());
String messageBody = message;
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String,Object