RabbitMQ笔记
来源:https://www.bilibili.com/video/BV15k4y1k7Ep/
1、基础
- MQ基本概念
- RabbitMQ快速入门
- RabbitMQ的工作模式
- Spring整合RabbitMQ
1.1 MQ概述
MQ全称Message Queue(消息队列),是在消息的传输过程中保存消息的容器,多用于分布式系统之间进行通信;
小结
- MQ,消息队列,存储消息的中间件;
- 分布式系统通信两种方式:直接远程调用和第三方完成间接通信;
- 发送方称为生产者,接收方称为消费者;
补充:本地调用通常指的是,进程内函数之间的相互调用;而远程调用,是进程间函数的相互调用,是一种进程间通信模式。
1.2 MQ的优势和劣势
优势:
- 应用解耦
- 异步提速
- 削峰填谷
劣势:
- 系统可用性降低:系统引入外部依赖越多,系统面临风险就越高。比如,对于第一个例子如果MQ挂掉,系统也会崩溃;
- 系统复杂度提高:需要考虑有没有重复消费,消息丢失,消息传递顺序等等;
- 一致性问题:对于场景2,1号系统处理完后成功,但对于2,3,4号系统,如果其中一个写库失败,就会导致数据不一致。
1.3 MQ的优势
1. 应用解耦
使用MQ是的应用间解耦,提升容错性和可维护性;
2. 异步提速
用户点击完下单按钮后,只需等待25ms就能得到下单响应(20+5 = 25ms)。
==提升用户的体验和系统吞吐量(单位时间内处理请求的数目)。
3. 削峰填谷
使用了MQ之后,限制消费消息的速度为1000,这样一来,高峰期产生的数据势必会被积压在MQ中,高峰就被“削”掉了,但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000,直到消费完积压的消息,这就叫做“填谷”。
使用MQ后,可以提高系统稳定性。
1.4 MQ的劣势
- 系统的可用性降低
系统引入的外部依赖越多,系统稳定性越差,一旦MQ宕机,就会对业务造成影响。如何保证MQ的高可用? - 系统复杂度提高
MQ的加入大大增加了系统的复杂度,以前系统间是同步的远程调调用,现在是通过
MQ进行异步调用,如何保证消息没有被重复消费?怎么处理消息丢失情况?如何保证消息传递的顺序性? - 一致性问题
A系统处理完业务,通过MQ给B、C、D三个系统发消息数据,如果B系统、C系统处理成功,D系统处理失败,如何保证消息数据处理的一致性?
小结:
既然MQ有优势也有劣势,那么使用MQ需要满足什么条件呢?
- 生产者不需要从消费者出获得反馈。引入消息队列之前的直接调用,其接口的返回值应该是空,这才让明明下层的动作还没做,上层却当成动作做完了继续往后走,即所谓异步成为了可能;
- 容忍数据短暂的不一致性;
- 确实是用了有效果,即解耦、提速、削峰这些方面的受益,超过加入MQ,管理MQ这些成本。
1. 5 常见的MQ产品
目前业界有许多的MQ产品,例如RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMQ等,也有直接使用redis充当消息队列的案例,而这些消息队列产品,各有侧重,在实际选型时,需要结合自身需求及MQ产品特征,综合考虑。
1.6 RabbitMQ简介
AMQP,即Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。2006年,AMQP规范发布,类比HTTP。
2007年,Rabbit技术公司基于AMQP标准开发的RabbitMQ采用Erlang语言开发。Erlang语言由Ericson设计,专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。
RabbitMQ基础架构如下图:
RabbitMQ中的相关概念:
-
Broker:接受和分发消息的应用,RabbitMQ Server就是Message Broker;
-
Virtual host:出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace概念。当多个不同的用户使用同一个RabbitMQ Server提供的服务时,可以划分出多个vhost,每个用户在自己的vhost创建exchange/queue等。
-
Connection:publish/consumer和broker之间的TCP连接;
-
Channel:如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也比较低。Channel是在connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的channel进行通讯,AMQP method包含了channel id帮助客户端和message broker识别channel,所以channel之间是完全隔离的。Channel作为轻量级的Connection极大减少了操作系统建立TCP connection的开销。
-
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中工作模式:简单模式、工作队列模式、发布订阅模式、路由模式、主题模式、RPC远程调用模式(远程调用,不太算MQ;暂不作介绍)
官网对应模式介绍:https://www.rabbitmq.com/getstarted.html
1.7 JMS
- JMS即Java消息服务(JavaMessage Service)应用程序接口,是一个Java平台中关于面向消息中间件的API;
- JMS是JavaEE规范中的一种,类比JDBC;
- 很多消息中间件都实现了JMS规范,例如:ActiveMQ、RabbitMQ官方没有提供JMS的实现包,但是开源社区有;
小结
- RabbitMQ是基于AMQP协议使用Erlang语言开发的一款消息队列产品;
- RabbitMQ提供了6种工作模式,我们学习5种;
- AMQP是协议,类比HTTP;
- JMS是API规范接口,类比JDBS;
2、 RabbitMQ的安装略
3、入门程序
需求:使用简单模式完成消息传递
步骤:
- 创建工程(生产者、消费者);
- 分别添加依赖;
- 编写生产者发送消息;
- 编写消费者接受消息;
原生纯Java方式;
4、模式介绍
4.0 简单模式
默认的exchange(Direct类型):如果用空字符串去声明一个exchange,那么系统就会使用””AMQP default”这个exchange,我们创建一个queue时,默认的都会有一个和新建queue同名的routingKey绑定到这个默认的exchange上去
4.1 Work Queues工作队列模式
在RabbitMQ中,生产者发送消息不会直接将消息投递到队列中,而是先将消息投递到交换机中, 在由交换机转发到具体的队列, 队列再将消息以推送或者拉取方式给消费者进行消费
1. 模式说明
- Work Queues:与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。(一个消息,要么被A消费,要么被B消费)
- 应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
4.2 发布订阅模式
1. 模式说明
在订阅模式中,多了一个Exchange角色,而且过程略有变化;
- P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X交换机;
- C:消费者,消息的接收这,会一直等待消息到来;
- Queue:消息队列,接受消息、缓存消息;
- Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。
- Exchange有常见以下3中类型:
- Fanout:广播,将消息交给所有绑定到交换机的队列;
- Direct:定向,把消息交给符合指定routing key的队列;
- Topic:通配符,把消息交给符合routing pattern(路由模式)的队列;
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列,那么消息会丢失!
4.3 Routing路由模式
1. 模式说明:
- 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key);
- 消息的发送方在向Exchange发送消息时,也必须指定消息的RoutingKey;
- Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routing Key与消息的Routing Key完全一致,才会接收到消息;
4.4 Topic通配符模式
1. 模式说明
图解:
- 红色Queue:绑定的是usa.#,因此凡是以usa开头的routing key都会被匹配到
- 黄色Queue:绑定的是#.news,因此凡是以.news结尾的routing key都会被匹配
小结:
Topic主题模式可以实现Pub/Sub发布与订阅模式和Routing路由模式的功能,只是Topic在配置routing key的时候可以使用通配符,显得更加灵活。
#表示一个或多个词,*表示一个词,多个字符需要用 “.” 连接;
5、SpringBoot整合RabbitMQ
生产者
1. 创建生产者SpringBoot工程;
2. 引入依赖坐标;
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>3.0.4</version>
</dependency>
- 编写properties配置,配置基本信息;
#http://192.168.121.129:15672/ 管理界面
spring.rabbitmq.host=192.168.121.129
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.virtual-host=/lq
#消息可靠投递 确认模式
spring.rabbitmq.publisher-confirm-type=correlated
#消息可靠投递 回退模式
spring.rabbitmq.publisher-returns=true
- 定义交换机,队列以及绑定关系的配置类;
- 注入RabbitTemplate,调用方法,完成消息发送;
消费者:
- 创建消费者SpringBoot工程;
- 引入start,依赖;
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>3.0.4</version>
</dependency>
- 编写properties配置,配置基本信息;
spring.rabbitmq.host=192.168.121.129
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.virtual-host=/lq
#消费端限流,每次接收5个
spring.rabbitmq.listener.simple.prefetch=5
- 定义监听类,使用@RabbitListener注解完成队列监听;
小结
- SpringBoot提供了快速整合RabbitMQ的方式;
- 基本信息在properties中配置,队列交换机以及绑定关系在配置类中使用Bean的方式配置;
- 生产端直接注入RabbitTemplate完成消息发送;
- 消费端直接使用@RabbitListener完成消息接收;
6、高级
RabbitMQ高级特性
消息可靠性投递
在使用RabbitMQ的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ为我们提供了两种模式来控制消息的可靠投递。
- confirm 确认模式
- return 回退模式
RabbitMQ整个消息投递的路径为:
producer–>rabbitmq broker–>exchange–>queue–>consumer
- 消息从producer到exchange则会返回一个confirmCallback;
- 消息从exchange–>queue投递失败则会返回一个returnCallback;
我们将利用这两个callback控制消息的可靠性投递;
消息可靠投递之确认模式
- 在properties中开启确认模式
#消息可靠投递 确认模式
spring.rabbitmq.publisher-confirm-type=correlated
- 在发送消息的时候,在rabbitTemplate中添加确认回调逻辑:
@ResponseBody
@RequestMapping("/sendConfirm")
public String sendConfirm(){
//设置确认模式(消息从生产者到交换机)
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println(JSON.toJSONString(correlationData));
if (ack){
System.out.println("消息已经成功传入交换机");
}
System.out.println(cause);
}
});
rabbitTemplate.convertAndSend(RabbitMQConfig.BASIC_EXCHANGE,"basic.hello","hello,confirm");
return "success";
}
消息可靠投递之回退模式
- 在properties中开启回退模式;
#消息可靠投递 回退模式
spring.rabbitmq.publisher-returns=true
- 在rabbitTemplate中添加回退逻辑;
@ResponseBody
@RequestMapping("/sendReturn")
public String sendReturn(){
rabbitTemplate.setMandatory(true);
//设置回退模式(消息从交换机到队列)
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returned) {
//正常的消息发送,不会执行此处的逻辑
System.out.println(JSON.toJSONString(returned));
}
});
rabbitTemplate.convertAndSend(RabbitMQConfig.BASIC_EXCHANGE,"basic.hello","hello,return");
return "success";
}
Consumer ACK
ack指Acknowledge,确认,表示消费端收到消息后的确认方式。
有三种确认方式:
- 自动确认:acknowledge=“none”
- 手动确认:acknowledge=“manual”
- 根据异常情况确认:acknowledge=“auto”,(这种方式使用麻烦,不作讲解)
其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应message从RabbitMQ的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。
Consumer Ack 小结
- 在@RabbitListener(queues = “basic-queue”,ackMode = “MANUAL”),设置ack方式(none:自动确认,manual:手动确认)
- 在服务端没有出现异常,则调用channel.basicAck(deliveryTag,false);方式确认签收消息;
- 如果出现异常,则在catch中调用basicNack或baxicReject,拒绝消息,让MQ重新发送消息;
消息可靠性总结
- 持久化
- exchange要持久化
- queue要持久化
- message要持久化
- 生产方确认Confirm
- 消费方确认Ack
- Broker高可用
补充小知识:
可以设置消费者basicNack之后的重试次数以及时间间隔;
https://blog.csdn.net/Hmj050117/article/details/121589463
消费端限流
具体操作:
- 在application.properties中配置:
spring.rabbitmq.listener.simple.prefetch=5
- 消息端的确认模式一定为手动确认,可以在消费者监听中配置如下:
@RabbitListener(queues = "basic-queue",ackMode = "MANUAL")
public void receiveMsg(Message msg, Channel channel) throws IOException {
....
}
// basic-queue为队列名
TTL
- TTL全称Time To Live(存活时间/过期时间);
- 当消息到达存活时间后,还没有被消费,会被自动清除;
- RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。
- 在RabbitMQConfig中设置整个队列中消息的过期时间:
@Bean("ttlQueue")
public Queue ttlQueue(){
//设置队列中消息的过期时间为10s
return QueueBuilder.durable("ttl-queue").ttl(10_000).build();
}
- 设置单个消息的过期时间:
rabbitTemplate.convertAndSend(RabbitMQConfig.BASIC_EXCHANGE, "ttl.hello", "hello,ttl",
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//单位毫秒
message.getMessageProperties().setExpiration("5000");
return null;
}
});
死信队列
死信队列,英文缩写:DLX,Dead Letter Exchange(死信交换机),当消息成为Dead messge后,可以被重新发送到另一个交换机,这个交换机就是DLX。
消息成为死信的三种情况:
- 队列消息长度达到限制;
- 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
- 原队列存在消息过期设置,消息到达超时时间未被消费;
队列绑定死信交换机:
给队列设置参数:
Spring:
x-dead-letter-exchange和x-dead-letter-routing-key
SpringBoot:
@Bean("ttlQueue1")
public Queue ttlQueue1(){
//设置队列中消息的过期时间为10s
return QueueBuilder.durable("ttl-queue1")
.deadLetterExchange("dead-exchange").deadLetterRoutingKey("deadInt.#").maxLength(6).build();
}
注意:死信队列与正常队列并无本质的区别,只是换了一种说法而已
延迟队列
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
TTL+死信队列即可完成;
需求:
1. 下单后,30分钟未支付,取消订单,回滚库存;
2. 新用户注册成功7天后,发送短信问候;
实现方式:
1. 定时器
2. 延迟队列
很可惜,在RabbitMQ中并未提供延迟队列功能。 但是可以使用:TTL+死信队列组合实现延迟队列的效果。
消息可靠性保障
消息幂等性处理
两种方式:
- 发送消息前,生产一个随机的key,保存到redis中,之后在处理消息的时候,先查看key是否存在。不存在,则不做任何处理,如果存在则进行逻辑操作,完成之后将key删除掉;
- 通过mysql的唯一索引,发送消息前,生产一个随机的key,在处理消息的时候将key插入到数据库中,该key对应的是mysql中的唯一索引列,如果插入成功,则执行逻辑操作;