文章目录
1 初识MQ
微服务一旦拆分,必然涉及到服务之间的相互调用,目前我们服务之间调用采用的都是基于 OpenFeign 的调用。这种调用中,调用者发起请求后需要等待服务提供者执行业务返回结果后,才能继续执行后面的业务。也就是说调用者在调用过程中处于阻塞状态,因此我们成这种调用方式为同步调用,也可以叫同步通讯。但在很多场景下,我们可能需要采用异步通讯的方式,为什么呢?
我们先来看看什么是同步通讯和异步通讯。如图
- 同步通讯:就如同打视频电话,双方的交互都是实时的。因此同一时刻你只能跟一个人打视频电话。
- 异步通讯:就如同发微信聊天,双方的交互不是实时的,你不需要立刻给对方回应。因此你可以多线操作,同时跟多人聊天。
两种方式各有优劣,打电话可以立即得到响应,但是你却不能跟多个人同时通话。发微信可以同时与多个人收发微信,但是往往对方回复不及时导致响应延迟。
所以
- 如果我们的业务需要实时得到服务提供方的响应,则应该选择同步通讯(同步调用)
- 如果我们追求更高的效率,并且不需要实时响应,则应该选择异步通讯(异步调用)
1.1 同步调用
1.1.1 同步调用的优势
- 时效性强
等待到结果才返回
1.1.2 同步调用的缺点
-
拓展性差
一旦有功能变更,需要修改业务代码 -
性能下降
调用链路越长,那同步阻塞等待就会导致性能变差,微服务调用越多,性能越差 -
级联失败问题
一个服务挂了,这个链路上的服务全部会出现问题
基于 OpenFeign 的调用属于是同步调用,这种方式存在的问题如【余额支付功能】
首先看下整个流程,如下图所示⬇️
以上流程采用的是基于 OpenFeign 的同步调用,业务执行流程如下⬇️
1️⃣ 支付服务需要先调用用户服务完成余额扣减
2️⃣ 然后支付服务自己要更新支付流水单的状态
3️⃣ 然后支付服务调用交易服务,更新业务订单状态为已支付
三个步骤依次同步执行,这样就存在3个问题
第一,拓展性差
目前上述的业务相对简单,但是随着业务规模扩大,产品的功能也在不断完善。在大多数电商业务中,用户支付成功后都会以短信或者其它方式通知用户,告知支付成功。假如后期产品经理提出这样新的需求,你怎么办?是不是要在上述业务中再加入通知用户的业务?
某些电商项目中,还会有积分或金币的概念。假如产品经理提出需求,用户支付成功后,给用户以积分奖励或者返还金币,你怎么办?是不是要在上述业务中再加入积分业务、返还金币业务?
最终你的支付业务会越来越臃肿,如下⬇️
每当有新需求时,现有支付逻辑都要跟着变化,代码需要改动,不符合开闭原则(对扩展开放,对修改关闭),拓展性不好。
第二,性能下降
由于我们采用了同步调用,调用者需要等待服务提供者执行完返回结果后,才能继续向下执行,也就是说每次远程调用,调用者都是阻塞等待状态。最终整个业务的响应时长就是每次远程调用的执行时长之和
假如每个微服务的执行时长都是 50ms,则最终整个业务的耗时可能高达 300ms,性能太差了。
第三,级联失败
由于我们是基于 OpenFeign 调用交易服务、通知服务。当交易服务、通知服务出现故障时,整个事务都会回滚,交易失败。这其实就是同步调用的级联失败问题。
总不能因为短信通知、更新订单状态失败、积分增长失败等后续的业务逻辑执行失败而回滚整个事务
综上,同步调用的方式存在下列问题
- 拓展性差
- 性能下降
- 级联失败
而要解决这些问题,我们就必须用异步调用的方式来代替同步调用
1.2 异步调用
1.2.1 异步调用的角色
异步调用方式其实就是基于消息通知的方式,一般包含以下三个角色
1️⃣ 消息发送者:投递消息的人,就是原来同步服务的调用方(生产者)
2️⃣ 消息 Broker:管理、暂存、转发消息,你可以把它理解成微信服务器(消息代理)
3️⃣ 消息接收者:接收和处理消息的人,就是原来的同步服务的提供方(消费者)
在异步调用中,发送者不再直接同步调用接收者的业务接口,而是发送一条消息投递给消息 Broker。然后接收者根据自己的需求从消息 Broker 那里订阅消息。每当发送方发送消息后,接受者都能获取消息并处理。
这样,发送消息的人和接收消息的人就完全解耦了
还是以余额支付业务为例,除了扣减余额、更新支付流水单状态以外,其它同步调用逻辑全部取消。而是改为发送一条消息到 Broker。而相关的微服务都可以订阅消息通知,一旦消息到达 Broker,则会分发给每一个订阅了的微服务,处理各自的业务。
假如产品经理冒出了新的想法,给你提出了新的需求,比如要添加积分系统,即要在支付成功后更新用户积分。此时支付代码完全不用变更,而仅仅是让积分服务也订阅消息即可,如下⬇️
不管后期增加了多少消息订阅者,作为支付服务来讲,执行问扣减余额、更新支付流水状态后,发送消息即可。业务耗时仅仅是这三部分业务耗时,仅仅 100ms,大大提高了业务性能。
另外,不管是交易服务、通知服务,还是积分服务,他们的业务与支付关联度低。现在采用了异步调用,解除了耦合,他们即便执行过程中出现了故障,也不会影响到支付服务,只要保证最终一致性就行。
1.2.2 异步调用的优势
综上,异步调用的优势如下
-
解除耦合,拓展性强
只需要发消息到 Broker,后续如果要添加业务,其它业务只需要自行订阅相关消息即可 -
无需等待,性能好
发完消息,直接服务结束,无需等待异步调用服务的执行时间 -
故障隔离,避免级联失败
异步调用服务失败抛异常不影响消息发送者 -
缓存消息,流量削峰填谷
流量削峰填谷是在高并发场景下平滑系统负载,避免因瞬间高流量导致系统崩溃的技术策略。这种策略广泛应用于电商秒杀、大规模促销活动等场景,能够有效平衡上下游系统的负载差异,提高系统的稳定性和可靠性。
1.2.3 异步调用的缺点
当然,异步通信也并非完美无缺,存在下列缺点⬇️
- 不能立即得到调用结果,时效性差
- 不确定下游业务执行是否成功
- 业务完全依赖于 Broker 的可靠性、安全性和性能
- 架构复杂,后期维护和调试麻烦
1.2.4 异步调用的场景
- 对异步调用的结果不关心,如发送通知、记录日志、执行后台任务等操作。
- 调用链非常长的业务中,一般会改造成异步调用的方式
1.3 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 | 自定义协议 | 自定义协议 |
可用性 | 高(主从架构) | 高(主从架构) | 非常高(分布式架构) | 非常高(分布式架构) |
单机吞吐量 | 一般(万级) | 差 (万级) | 高 (十万级) | 非常高(百万级) |
消息延迟 | 微秒级(us级) | 毫秒级(ms级) | 毫秒级(ms级) | 毫秒以内(ms级以内) |
消息可靠性 | 高 | 一般 | 高 | 一般 |
功能特性 | 基于Erlang开发,所以并发能力很强,性能极其好,延时很低;管理界面较丰富 | 成熟的产品,在很多公司得到应用,有较多的文档;各种协议支持较好 | MQ功能比较完备,扩展性佳 | 只支持主要的MQ功能,像一些消息查询、消息回溯等功能没有提供,是为大数据准备的,在大数据领域应用广 |
- 追求可用性:Kafka、 RocketMQ 、RabbitMQ
- 追求可靠性:RabbitMQ、RocketMQ
- 追求吞吐能力:RocketMQ、Kafka
- 追求消息低延迟:RabbitMQ、Kafka
RabbitMQ 是基于 Erlang 语言开发的开源消息通信中间件,性能好,Erlang 语言是面向并发的语言;协议支持丰富,符合微服务理念,Spring 官方默认支持 RabbitMQ ;支持集群,可用性高;单机吞吐量(并发能力)十万二十万左右的样子,但已经满足大多数企业级应用需求;消息延迟在毫秒级;需要消息确认,消息可靠性高。
Kafka 适用于吞吐量需求很高的场景中,如日志搜集,但由于其消息不可靠,可能存在数据丢失的情况。
据统计,大厂基本上是使用自研,而中小型企业消息队列使用最多的是 RabbitMQ,因为其各方面都比较均衡,稳定性也好。至于 RocketMQ 由于是阿里的产品,而阿里每年向外输出大量的人才,这些人才流入到中小型企业中,会优先选择去使用 RocketMQ ,但具数据统计 RabbitMQ 在国内还是更受欢迎。
2 RabbitMQ
RabbitMQ的官网地址:Messaging that just works — RabbitMQ
2.1 安装
本文是基于 Docker 来安装 RabbitMQ
2.1.1 资源准备
如果是内网中开发或拉取镜像困难,请准备好以下资源,如果您的设备可以连接互联网,则可以直接开始安装
链接:https://pan.baidu.com/s/1tBRud60ExkPXcOBsr7R_rA?pwd=sm4u
提取码:sm4u
将mq.tar
上传至root
目录下,执行以下命令加载镜像
cd /root
docker load -i mq.tar
2.1.2 安装步骤
执行以下命令进行安装
docker run \
-e RABBITMQ_DEFAULT_USER=ray \
-e RABBITMQ_DEFAULT_PASS=123456 \
-v mq-plugins:/plugins \
--name mq \
--hostname mq \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3.8-management
-
-e RABBITMQ_DEFAULT_USER=ray
设置 RabbitMQ 用户名为:ray -
-e RABBITMQ_DEFAULT_PASS=123456
设置 RabbitMQ 密码为:123456 -
-v mq-plugins:/plugins
挂载数据卷 mq-plugins 对应容器内目录 plugins -
--name mq
容器名为:mq -
--hostname mq
主机名为:mq -
-p 15672:15672
端口映射,表示将本机的 15672 端口映射到 RabbitMQ 镜像的 15672 端口 -
-p 5672:5672
端口映射,表示将本机的 5672 端口映射到 RabbitMQ 镜像的 5672 端口 -
docker run -d
创建并运行一个容器,-d
则是让容器以后台进程运行
在安装命令中有两个映射的端口
- 15672:RabbitMQ 提供的管理控制台的端口
- 5672:RabbitMQ 的消息发送处理接口
如上图所示,安装完成后,访问 http://192.168.133.131:15672
即可看到管理控制台(记得 IP 要替换为你虚拟机的 IP)
首次访问需要登录,默认的用户名和密码在 docker run 中指定了,博主的用户名为:ray,密码为:123456
登录成功后即可看到管理控制台总览页面
2.2 RabbitMQ架构
RabbitMQ 对应的架构如下图所示
其中包含几个概念:
publisher
:生产者,也就是发送消息的一方consumer
:消费者,也就是消费消息的一方queue
:队列,存储消息。生产者投递的消息会暂存在消息队列中,等待消费者处理exchange
:交换机,负责消息路由。生产者发送的消息由交换机决定投递到哪个队列。virtual host
:虚拟主机,起到数据隔离的作用。每个虚拟主机相互独立,有各自的exchange、queue
上述这些东西都可以在 RabbitMQ 的管理控制台来管理。
2.3 RabbitMQ管理控制台收发消息
【需求】在 RabbitMQ 的控制台完成下列操作
2.3.1 创建队列
- 新建队列 hello.queue1 和 hello.queue2
1️⃣ 点击导航栏中的Queues
,选择Add a new queue
,在Name
中输入 hello.queue1,点击Add queue
即可添加队列,hello.queue2 同理添加
2️⃣ 添加成功后如下图
2.3.2 交换机绑定队列★
- 将刚刚创建的 hello.queue1 和 hello.queue2 队列绑定到默认的 amp.fanout 交换机
1️⃣ 点击Exchanges
,鼠标单击选中amp.fanout
交换机
2️⃣ 点击Bindings
,在To queue
中输入或选择 hello.queue1 ,点击Bind
进行绑定,hello.queue2 同理绑定
3️⃣ 绑定完成后如下图所示
4️⃣ 到Queues
中查看Bindings
中是否绑定了交换机amp.fanout
2.3.3 交换机发送消息
- 向默认的 amp.fanout 交换机发送一条消息
1️⃣ 点击Exchanges
,鼠标单击选中amp.fanout
交换机进入交换机详情页面,然后点击Publish message
,在Payload
中输入要发送的消息,如“hello every queue!”,然后点击Publish message
,会有提示框提示“Message published”,点击Close
即可
2️⃣ 在Overview
中查看消息发送情况,如下图所示
2.3.4 查看消息接收
- 查看消息是否到达 hello.queue1 和 hello.queue2
1️⃣ 点击导航栏中的Queues
,查看队列消息接收情况,如下图所示
2️⃣ 选中 hellp.queue1 ,进入队列管理界面,展开Get messages
,点击Get Message(s)
按钮,查看交换机发送的消息内容
2.4 数据隔离
在 RabbitMQ 中存在 virtual host 即虚拟主机的概念,交换机和队列都有自己所属的虚拟主机,以此实现数据隔离的效果。
2.4.1 用户管理
点击Admin
选项卡,会看到 RabbitMQ 控制台的用户管理界面 Users
这些用户都是 RabbitMQ 的管理或运维人员。目前只有安装 RabbitMQ 时添加的ray
这个用户。用户表格中的字段,如下:
Name
:ray
,也就是用户名Tags
:administrator
,说明ray
用户是超级管理员,拥有所有权限Can access virtual host
:/
,可以访问的virtual host
,这里的/
是默认的virtual host
对于小型企业而言,出于成本考虑,通常只会搭建一套 MQ 集群,公司内的多个不同项目同时使用。这个时候为了避免互相干扰, 会利用virtual host
的隔离特性,将不同项目隔离。一般会做两件事情:
- 给每个项目创建独立的运维账号,将管理权限分离。
- 给每个项目创建不同的
virtual host
,将每个项目的数据隔离。
比如给黑马商城项目创建一个新的用户,名为hmall
,密码为123456
,标签选择admin
即administrator
超级管理员权限
此时会发现 hmall 用户没有任何virtual host
的访问权限
别急,接下来我们就来为 hmall 进行授权操作
2.4.2 virtual host 用户授权
1️⃣ 先点击页面右上角的Log out
退出登录
2️⃣ 切换到刚刚创建的 hmall 用户登录,在Admin
标签页中点击Virtual Hosts
菜单,进入virtual host
管理页
可以看到目前只有一个默认的virtual host
,名字为 /
3️⃣ 给黑马商城项目创建一个单独的virtual host
,而不是使用默认的/
,展开Add a new virtual host
4️⃣ 创建完成后如下图所示
由于我们是登录hmall
账户后创建的virtual host
,因此回到users
菜单,你会发现当前用户已经具备了对/hmall
这个virtual host
的访问权限了
5️⃣ 点击页面右上角的virtual host
下拉菜单,切换virtual host
为 /hmall
6️⃣ 切换virtual host
为 /hmall
后,查看Queues
选项卡,会发现之前的队列已经看不到了
这就是基于virtual host
的隔离效果
如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~