RabbitMQ 从0到1
RabbitMQ消息中间件
01.同步通讯与异步通讯
在学习MQ之前需要了解一下同步通讯与异步通讯,在微服务之间通讯方式有两种一种是同步通讯另一种是异步通讯。
同步通讯:就像打电话,需要实时响应。
异步通讯:就像发邮件,不需要马上回复。
两种方式各有优劣,打电话可以立即得到响应,但是却不能跟多个人同时通话。发送邮件可以同时与多个人收发邮件,但是往往响应会有延迟。
之前学习的 Feign 调用就属于同步方式,虽然调用可以实时得到结果,但存在下面的问题:
同步调用的优点:
- 时效性较强,可以立即得到结果
同步调用的缺点:
- 耦合度高
- 性能和吞吐能力下降
- 有额外的资源消耗
- 有级联失败问题
异步调用则可以避免上述问题,以购买商品为例,用户支付后需要调用订单服务完成订单状态修改,调用物流服务,从仓库分配响应的库存并准备发货。在事件模式中,支付服务是事件发布者(publisher),在支付完成后只需要发布一个支付成功的事件(event),事件中带上订单id。订单服务和物流服务是事件订阅者(Consumer),订阅支付成功的事件,监听到事件后完成自己业务即可。
为了解除事件发布者与订阅者之间的耦合,两者并不是直接通信,而是有一个中间人(Broker)。发布者发布事件到Broker,不关心谁来订阅事件。订阅者从Broker订阅事件,不关心谁发来的消息。
Broker 是一个像数据总线一样的东西,所有的服务要接收数据和发送数据都发到这个总线上,这个总线就像协议一样,让服务间的通讯变得标准和可控。
异步调用好处:
- 吞吐量提升:无需等待订阅者处理完成,响应更快速
- 故障隔离:服务没有直接调用,不存在级联失败问题
- 调用间没有阻塞,不会造成无效的资源占用
- 耦合度极低,每个服务都可以灵活插拔,可替换
- 流量削峰:不管发布事件的流量波动多大,都由 Broker 接收,订阅者可以按照自己的速度去处理事件
异步调用缺点:
- 架构复杂了,业务没有明显的流程线,不好管理
- 需要依赖于 Broker 的可靠、安全、性能
02.认识MQ
MQ,中文是消息队列(MessageQueue),字面来看就是存放消息的队列,也就是事件驱动架构中的 Broker
比较常见的 MQ 实现:
- ActiveMQ
- RabbitMQ
- RocketMQ
- Kafka
几种常见MQ的对比:
RabbitMQ | ActiveMQ | RocketMQ | kafka | |
---|---|---|---|---|
公司/社区 | Rabbit | Apache | 阿里 | Apache |
开发语言 | Erlang | Java | Java | Scala&Java |
协议支持 | AMQP、XMPP、SMTP、STOMP | OpenWire、STOMP、REST、XMPP、AMQP | 自定义协议 | 自定义协议 |
可用性 | 高 | 一般 | 高 | 高 |
单机吞吐量 | 一般 | 差 | 高 | 非常高 |
消息延迟 | 微秒级 | 毫秒级 | 毫秒级 | 毫秒以内 |
消息可靠性 | 高 | 一般 | 高 | 一般 |
03.使用RabbitMq
01.安装RabbitMq
这里使用docker来进行安装
docker-rabbitmq官方网站https://hub.docker.com/_/rabbitmq
下载镜像
sudo docker pull rabbitmq:3.10.6-management #下载镜像命令
启动容器命令解释
#安装启动RabbitMq命令
#docker run命令
sudo docker run \
#容器名称
--name rabbit \
#服务名称(做集群用的)
--hostname rabbit \
#访问控制台用户名
-e RABBITMQ_DEFAULT_USER=test \
#访问控制台密码
-e RABBITMQ_DEFAULT_PASS=123456 \
#访问控制台端口号
-p 15672:15672 \
#通讯端口号
-p 5672:5672 \
#后台启动,指定镜像和版本
-d rabbitmq:3.10.6-management
完整命令
sudo docker run --name rabbit --hostname rabbit01 -e RABBITMQ_DEFAULT_USER=test -e RABBITMQ_DEFAULT_PASS=123456 -p 15672:15672 -p 5672:5672 -d rabbitmq:3.10.6-management
02.使用教程
安装启动完成后查看一下是否启动成功
sudo docker ps
启动成功后访问 IP:15672 进行登录
- Overview : 总预览界面 查看rabbit节点状态(集群)
- Connections : 连接 客户端和mq之间会产生连接
- Channels : 通道 建立连接后需要创建通道然后生产者和消费者才可以进行接收或发送消息
- Exchanges :交换机
- Queues : 队列
- Admin : 管理 除了可以管理用户以外还可以管理虚拟主机
RabbitMQ 中的一些角色
- publisher:生产者
- consumer:消费者
- exchange:交换机,负责消息路由
- queue:队列,存储消息
- virtualHost:虚拟主机,隔离不同租户的 exchange、queue、消息的隔离
MQ 的基本结构
04.RabbitMq入门
案例代码
发送端
https://github.com/codeXYW/Microservices/blob/main/mq/publisher/src/main/java/top/co4/publisher/service/BaseControllerService.java
消费端
https://github.com/codeXYW/Microservices/blob/main/mq/consumer/src/main/java/top/co4/consumer/service/BaseControllerService.java
RabbitMQ 官方提供了 5 个不同的 Demo 示例,对应了不同的消息模型。
网站: https://www.rabbitmq.com/getstarted.html
01.基本消息队列
三个角色:
- publisher:消息发布者,将消息发送到队列queue
- queue:消息队列,负责接受并缓存消息
- consumer:订阅队列,处理队列中的消息
02.工作消息队列
三个角色(有多个订阅队列):
- publisher:消息发布者,将消息发送到队列queue
- queue:消息队列,负责接受并缓存消息
- consumer:订阅队列,处理队列中的消息
03.发布订阅模式
四个角色(新增交换机角色实现一个信息多个消息队列订阅进行消费):
- Publisher:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给 exchange(交换机)
- Consumer:消费者,与以前一样,订阅队列,没有变化
- Queue:消息队列也与以前一样,接收消息、缓存消息
- Exchange:交换机,一方面,接收生产者发送的消息;另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于 Exchange 的类型。Exchange 有以下3种类型:
- Fanout:广播,将消息交给所有绑定到交换机的队列
- Direct:定向,把消息交给符合指定 routing key 的队列
- Topic:通配符,把消息交给符合 routing pattern(路由模式) 的队列
03.01 交换机三种类型的区别
**fanout:**可以理解为分发的意思,也就是说你只需要将队列绑定到交换机上,一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上,就像广播。
**direct:**可以理解为1对1定向发送,需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。
topic:讲路由键和某种模式进行匹配。如"#“匹配一个或者多个词,”*“匹配一个词。如“topic.#”可以匹配到"topic.A.all”,"topic.*“只能匹配到"topic.A”
04.拓展知识点
rabbitmq是如何保证消息可靠不丢失不重复的
rabbitMq是通过信道(虚拟TCP模式)传递消息的,在发送端,设置发送者确认模式,通过信道发送消息(生成唯一的id),当发送到指定的队列后,给生产者发送一个ack指令说明消息已发送出去。若此过程中出现服务器异常,信道会给生产者发送一个nack指令,由于是异步的,生产者会再次发送消息出去
在接受端,设置为接收者确认模式,当消费者接收到消息时并确认后,mq会删除掉队列中的数据。当在确认之前出现服务器异常,mq会认为该消息没有被分发出去,重新下发给另一个消费者。
在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列;在消息消费时,要求消息体中必须要有一个bizId(对于同一业务全局唯一,如支付ID、订单ID、帖子ID等)作为去重和幂等的依据,避免同一条消息被重复消费。
05.SpringAMQP
01.什么是SpringAMQP
SpringAMQP 是基于 RabbitMQ 封装的一套模板,并且还利用 SpringBoot 对其实现了自动装配,使用起来非常方便。
SpringAMQP 的官方地址:https://spring.io/projects/spring-amqp
![img](https://szkelian.oss-cn-hangzhou.aliyuncs.com/NoteImage202207121754973.png)
![img](https://szkelian.oss-cn-hangzhou.aliyuncs.com/NoteImage202207121754791.png)
SpringAMQP 提供了三个功能:
- 自动声明队列、交换机及其绑定关系
- 基于注解的监听器模式,异步接收消息
- 封装了 RabbitTemplate 工具,用于发送消息
在SpringAMQP中订阅和发布非常简单,发布消息订阅时会有RabbitTemplate来使用,接收则只需要使用注解定义@RabbitListener来定义即可,需要绑定的Exchange也只需要声明即可
三种消息队列模式通过SpringAMQP来实现案例代码:
发送端代码:
https://github.com/codeXYW/Microservices/blob/main/mq/publisher/src/main/java/top/co4/publisher/service/AmqpControllerService.java
接收端:
https://github.com/codeXYW/Microservices/blob/main/mq/consumer/src/main/java/top/co4/consumer/config/SpringRabbitListener.java
05.拓展知识点
01.关于多个消费者消费信息是处理能力问题
消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。个消费者共同处理消息处理,速度能大大提高,消息是平均分配给每个消费者,并没有考虑到消费者的处理能力。这是因为 RabbitMQ 默认有一个消息预取机制,显然这不是我们想要的结果,我们需要的是能者多劳,所以去限制每次只能取一条消息,可以解决这个问题。
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
02.关于rabbit中队列不存在问题
在SpringAMQP中如果订阅的队列不存在就会启动错误所以给出一下解决方案将所有需要订阅的队列通过声明bean的方式在服务启动时创建即可
案例代码:
https://github.com/codeXYW/Microservices/blob/main/mq/consumer/src/main/java/top/co4/consumer/config/QueueConfig.java
03.关于服务端和客户端谁来负责交换机和消息队列之间的绑定
在SpringAMQP使用有一种思维,发送端只要注意是发送给交换机还是队列,发送给交换机时不管交换机是否和队列进行绑定,只管发送(交换机和队列),至于队列和交换机绑定的事情就交全权交由订阅端来做,这样一来即可实现无限制拓展客户端,在客户端需要时主动找到交换机进行消息队列绑定即可,当然这只是我一家之言
04.关于信息序列化问题(消息转换器)
Spring 会把你发送的消息序列化为字节发送给 MQ,接收消息的时候,还会把字节反序列化为 Java 对象。**默认情况下 Spring 采用的序列化方式是 JDK 序列化。**众所周知,JDK序列化存在下列问题:
- 数据体积过大
- 有安全漏洞
- 可读性差
我们推荐可以使用 JSON 来序列化
在 publisher 和 consumer 两个服务中都引入依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.10</version>
</dependency>
配置消息转换器。
在各自的启动类中添加一个 Bean 即可
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
效果
![image-20220713151250825](https://szkelian.oss-cn-hangzhou.aliyuncs.com/NoteImageimage-20220713151250825.png)
接收端:
https://github.com/codeXYW/Microservices/blob/main/mq/consumer/src/main/java/top/co4/consumer/config/SpringRabbitListener.java