一、MQ概念
消息队列(MQ)是一种通信机制,用于在不同系统或应用程序之间传递消息。它通过将消息存储在队列中来实现异步通信,使得发送者和接收者可以在不同时间操作
其中RabbitMQ也是企业开发中应用非常广泛的“高性能的异步通讯组件”
常见的MQ框架有:Kafka RabbitMQ RocketMQ等等,后续Kafka和RocketMQ也会出的
1.1、异步通讯和同步通讯
通俗来说的话:
同步通信好比A给B打电话,A和B电话打通了,也就是A和B之间建立了同步通讯,A说的话这时候B可以立马听到,A做的什么事B也可以看到,这就是一种实时的通信。也就是说同一时刻,你只能和一个人通话,这时候C和D也想和你打电话,但是打不通了。但是异步就不一样了,可以同时和好几个人发,并发操作
异步通讯就好比A给B发一个微信信息,B这个时候忙,或者在做别的事,没有回复A,所以A这个时候没有收到B的回复,过了一段时间B看见了,回复你了,你才可以看见消息,也就是说异步的这种通讯,不可能实时得到结果,所以时效性有点差
然后将这个场景映射到具体的业务代码中就是:
之前我们微服务之间业务的调用就是使用的Feign调用的,那openFeign调用就是一种同步调用,也就是说,我发送一个请求需要实时的等待你返回结果,那这个等待过程就是阻塞的,企业中的登录不是简单的,其实是登录完之后还需要很多事要做,比如记录登录信息,发送短信等等。。。等这些一系列操作完成才算是登录完成,而这个过程,用户需要等很长时间,这样的话性能就比较低,在高并发情况下可能就应对不了了,所以就使用异步通讯方式(MQ)
用户登录成功之后,先不去做后面的时候(记录登录信息,发送短信什么的等等),而是先发一条消息到MQ中,也就是把登录信息发送到MQ中,告诉MQ用户已经登录成功了,这个时候登录业务就结束了,也个时候,登录这个业务就会变得很短,并发能力就会大大提高,然后后面的其他操作就回去监听MQ的消息,收到消息之后,就去做各自的事
下面就是专业术语了
同步通讯指的是发送方在发送请求后,需要等待接收方处理完请求并返回响应,然后才能继续执行其他操作。这种模式确保了请求和响应之间的顺序性和一致性。同步通讯的特点包括:
- 等待时间:发送方在等待接收方处理请求的过程中,可能会处于阻塞状态。
- 实时性:通讯过程实时进行,通常用于需要立即得到响应的场景。
- 例子:HTTP 请求/响应、函数调用等。
优点:
- 确保请求和响应的顺序。
- 实现简单,逻辑清晰。
缺点:
- 发送方需要等待接收方完成处理,可能会造成延迟。
- 不适合高延迟或长时间处理的操作。
异步通讯则指发送方发送请求后,不必等待接收方处理完请求再继续执行其他操作。发送方通常会立即收到一个确认消息(如任务已接收),然后继续处理其他任务。接收方会在完成处理后,将结果返回给发送方。异步通讯的特点包括:
- 非阻塞:发送方可以继续执行其他任务,不会因等待响应而被阻塞。
- 解耦:发送方和接收方可以独立地处理请求和响应,适合于长时间或不确定的处理时间。
- 例子:消息队列(如 RabbitMQ)、回调函数、事件驱动编程等。
优点:
- 提高系统的并发能力和效率。
- 避免长时间等待导致的阻塞,适合处理长时间或不确定性任务。
缺点:
- 实现较复杂,涉及消息的存储、状态管理等。
- 可能需要额外的机制来确保消息的顺序和一致性。
应用场景
- 同步通讯适用于需要快速响应且处理时间较短的场景,如实时数据请求和响应。
- 异步通讯适用于处理长时间操作、需要高吞吐量或需要解耦的场景,如任务调度和日志处理。
1.2、同步调用和异步调用
1.2.1、同步调用
在同步调用中,调用者发起请求后会等待直到操作完成并返回结果。这意味着调用者在等待过程中会被阻塞,直到获得结果或超时。这种方式简单,但可能导致性能瓶颈,特别是在网络延迟或长时间操作时
同步调用的优势是什么?
时效性强,等待到结果后才返回
同步调用的问题是什么?扩展性差
性能下降
级联失败问题
拿支付做一个案例:用户去做支付时,会调用支付服务,这个服务要干三件事,第一件事就是去扣减用户的余额,只有当余额充足的时候,才会去干接下来的事;余额不充足的时候,就没必要往下做了,所以这个调用我觉得采用同步好像还可以,因为我要扣余额,我是不是要立刻知道你的结果,你告诉我是成功了还是失败了,如果是失败了,就没必要往下走了,如果说我这里采用异步我去扣钱,扣完不管三七二十一,我去把支付状态改为成功,这显然是不合理的,所以这个调用用同步是没有问题的,我等待你的执行结果,执行完了,我再执行接下来的操作,执行更新支付状态,更新订单状态等待,但是这样做会存在一系列的问题
第一个问题就是业务耦合的问题,那什么是耦合呐,就是说:比如咱这个支付服务,你来支付最重要的操作就是扣减余额和更新支付状态,但是后续的操作比如更新业务订单状态就是就是多余的额外的操作,和支付服务本身是没有什么关系的,但是你的状态要修改所以我不得不调用你;但是调就调呗,不就是加一行代码就调用了呗,一看还行,但是可以思考一下,业务是不是会变化的呀,但是中途产品经理说不行,这个支付服务应该加一个逻辑,当用户登录成功了,你应该发一个短信通知一下,这个时候你的支付服务就要加一个逻辑,给用户发送短信通知,支付业务就要改代码,就要加东西,调用短信服务,发送短信;当又有新的需求来了,就又要在支付业务中修改逻辑,修改代码;;只要需求在变更,你的支付业务的代码也在一直变,这显然是不行的,违反原则的,,,这就是代码的耦合导致的,导致拓展性很差,我们都知道java代码是开闭原则的,《面向修改关闭,面向拓展开放》,你现在就一直在修改就是有问题的,这是第一点!!!!!第二个问题就是性能上也有问题,这种基于openfeign调用的都是同步的,当你嗲用扣减余额的时候,你的支付功能只能等待,啥也干不了,直到用户服务执行完,然后执行下一步,又要等,然后在下一步,继续等,你在等待的过程中CPU也在等待,线程也被占用着。。比如每次执行都是耗时50ms,最终的话可能耗时好几百毫秒,甚至更多,用户一点这个操作,用户卡半天,不就影响用户体验了吗,这就是第二个问题,性能太差,甚至有时候微服务项目还不如单体项目,性能会下降
第三个问题就是级联失败,同步调用之间会有影响,当其中一个执行完没问题才会继续往下执行,比如现在交易服务出现了故障,抛出异常,那么这整个业务就会全部失败,💴都扣完了,又给人家还回去,显然是不合适的,要是这个问题一直没有解决,就会影响拖累支付服务,导致支付服务资源耗尽,也出现故障,出现级联失败
但是问题这么多为什么还要使用同步调用,其实有一些业务不得不使用,必须使用同步调用,就是说我下面的操作要依赖上面的结果(也就是扣减余额是否成功,才可以执行下面的操作,但是下面的操作之间没有关系,没有必要同步了,后面的业务慢慢做就行了,跟用户服务没有一点关系,所以没有必要同步和等待了)
1.2.2、异步调用
在异步调用中,调用者发起请求后立即返回,不会等待操作完成。操作在后台进行,调用者可以继续执行其他任务。这种方式更灵活,可以提高系统的并发性能,但实现起来可能会更复杂
采用异步调用可以解决上述的问题
支付服务不再同步调用业务关联度低的服务,而是发送消息通知到Broker
之前我们都是同步调用的,现在就没有必要的,使用异步就行,首先第一步,扣余额一定是同步的,更新支付状态自己服务更新就行属于支付服务里本身的调用,没必要异步,直接执行就行,所以前两个同步就行了,但是从更新订单状态就没必要同步了,这都属于跨服务调用了,需要等待,,,,所以这个时候就需要发一个消息到消息代理,到这里支付服务就完全结束了,不用往下走了,下面的就是自己服务的事了,比如交易服务监听消息当收到消息之后就自己去更新消息,这个时候产品经经理再来增加需求,比如发送短信,增加积分,这些服务本来就有的,这个时候就让短信服务和积分服务自己去监听消息,而支付服务已经结束了,和我没关系了,不需要再发送消息了,支付服务本身的代码也不需要修改了,你小子提再多需要我也不该代码,我只发送消息!!!然后那两个接收到消息后就自己去执行短信发送和积分增加等操作,拓展性就提高了,性能也变好了,当我支付服务完成,你后续的订单呀,短信呀,什么的耗时再长也和我支付服务没有一点关系,出现故障也和我支付没关系,异常就隔离开了。。。。。。也能保证最终数据一致性(这个先不讲了)
1.3、消息队列的作用
1.3.1、流量削峰/限流
举个例子来说:如果订单系统最多能处理一万次订单,这个处理能力应付正常时段的下单是绰绰有余的,正常时段我们下单一秒后就能返回结果。但是在高峰期,如果有两万次下单操作,系统是处理不了的,只能限制订单超过一万后不允许用户下单。但是使用消息队列做缓冲,我们就可以取消这个限制,把一秒内下的订单分散成一段时间来处理,这时有些用户可能在下单十几秒后才能收到成功的操作,但是比不能下单的体验要好。
我们再来一个场景,现在我们每个月要搞一次大促,大促期间的并发可能会很高的,比如每秒3000个请求。假设我们现在有两台机器处理请求,并且每台机器只能每次处理1000个请求。然后多出来的1000个请求,可能就会把我们整个系统给搞崩了,所以我们可以写到消息队列中:
系统B和系统C根据自己能够处理的请求数去消息队列中拿数据,这样即便每秒有8000个请求,只用把请求放在消息队列中,去拿消息队列的消息由系统自己去控制,这样就不会把整个系统给搞崩
1.3.2、 应用解耦
以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。当转变成基于消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来维修。在这几分钟的时间里,物流系统要处理的内存被缓存在消息队列中,用户下单操作可以正常完成。当物流系统恢复后,继续处理订单信息即可,订单用户感受不到物流系统的故障,提升系统的可用性。
再来一个场景:现在我有一个系统A,系统A可以产生一个
userId
然后,现在有系统B和系统C都需要这个userId去做相关的操作
过了几天,系统B的负责人告诉系统A的负责人,现在系统B的
某某
这个接口不再使用了,让系统A别去调它了,系统A的负责人说"行行行,你小子,那我就不调用你了。",于是就把调用系统B接口的代码给删掉了
又过了几天,系统D的负责人接了个需求,也需要系统A的userId,于是就去找系统A的负责人说:“A老哥,我需要用你的userId,你到时候调一下我的接口呗”,系统A说:“没问题,都是小事啦,这就搞!!!”
接下来的日子
又过了几天,系统E的负责人过来了,告诉系统A,需要userId。
又过了几天,系统B的负责人过来了,告诉系统A,还是重新调那个接口吧。
又过了几天,系统F的负责人过来了,告诉系统A,需要userId。
于是系统A的负责人,每天都被这给骚扰着,改来改去,改来改去…….
还有另外一个问题,调用系统C的时候,如果系统C挂了,系统A还得想办法处理。如果调用系统D时,由于网络延迟,请求超时了,那系统A是反馈
失败呐
还是重试呐??最后,系统A的负责人,觉得隔一段时间就改来改去,没意思,于是就跑路了。
然后,公司招来一个大佬,大佬经过几天熟悉,上来就说:将系统A的userId写到消息队列中,这样系统A就不用经常改动了。为什么呢?
系统A将userId写到消息队列中,系统C和系统D从消息队列中拿数据。这样有什么好处?
系统A只负责把数据写到队列中,谁想要或不想要这个数据(消息),系统A一点都不关心。
即便现在系统D不想要userId这个数据了,系统B又突然想要userId这个数据了,都跟系统A无关,系统A一点代码都不用改。想要自己去拿,别来找我
这样一来,系统A与系统B、C、D都解耦了
系统D拿userId不再经过系统A,而是从消息队列里边拿。系统D即便挂了或者请求超时,都跟系统A无关,只跟消息队列有关。
1.3.3、异步处理
和上面异步调用基本一样
有些服务间调用时异步的,例如A调用B,B需要花很长时间执行,但是A需要知道B什么时候可以执行完,以前一般有两种方式,A过一段时间去调用B的查询api查询。或者A提供一个callback api,B执行完之后调用api通知A服务。这两种方式都不是很优雅,但是使用消息总先,可以很方便的解决这个问题:A调用B服务后,只需要监听B处理完成的消息,当B处理完成后,会发送一条消息给MQ,MQ会将此消息转发给A服务。这样A服务既不用循环调用B的查询api,也不用给B提供callback api。同样B服务也不用做这些操作。A服务还能及时的得到异步处理成功的消息
1.4、消息队列的两种模式
1.4.1、点对点模式
也就是一对一,消费者主动拉取数据,消息收到后消息清除
生产者生产消息发送到Queue中,然后消费者从Queue中取出并且消费消息,消息被消费以后,queue中不再有存储,所以消费者不可能消费到已经被消费的消息。Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费
1.4.2、发布/订阅模式
也就是一对多,消费者消费数据之后不会清除消息
生产者(发布)将消息发布到消息队列中,同时有多个消费者(订阅)消费该消息。和点对点方式不同,发布到消息队列中的消息会被所有订阅者消费
二、RabbitMQ
2.1、简介
RabbitMQ 是一个流行的开源消息代理(Message Broker),用于在不同的应用程序或系统之间异步传递消息。它基于 AMQP(Advanced Message Queuing Protocol)协议,提供了高可靠性、高可用性和灵活的消息传递机制
2.2、特征
异步消息:
支持多种消息传递协议、消息队列(queue1-8)、传递确认、灵活的队列路由* #、多种交换类型(direct topic fanout)。
支持多种开发语言:
例如:Java、.NET、PHP、Python、JavaScript、Ruby、Go等
支持分布式:
部署为集群以获得高可用性和吞吐量;
支持插件:
RabbitMQ提供了许多插件,可以通过插件进行扩展,也可以编写自己的插件。
可视化管理界面:
具有用于管理和监控RabbitMQ 的 UI
2.3、AMQP标准及模型简介
官网:amqp-concepts
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制
模型简介:
消息发布到Exchange(交换机),这通常被比作邮局或邮箱。然后,交换使用称为 绑定的规则将消息副本分发(路由)到队列。然后代理要么将消息传递(推送)给订阅队列的消费者,要么消费者按需从队列中获取/拉取消息
三、Rabbitmq架构图及核心概念
3.1、名词解释
Producer(生产者):生成消息并将其发送到RaabitMQ交换机(Exchange)的应用程序和或服务
Connection(连接):代表到 RabbitMQ 服务器的物理连接。它是客户端应用程序与 RabbitMQ 服务器之间的通信桥梁,通常基于 TCP/IP 协议。一个连接可以包含多个频道。连接的建立和关闭通常会消耗相对较多的资源,因此应该尽量减少连接的数量
Channel(通信通道):建立在一个连接之上的逻辑通信通道。一个连接可以包含多个频道,每个频道负责处理一组消息传递操作(如发布消息、声明队列、绑定交换机等)。频道是 RabbitMQ 的主要操作单元,提供了对消息和队列的访问,频道的创建和关闭比连接要轻量得多,建议在单个连接内复用多个频道以提高效率
Broker:消息中间件的核心组件,负责接收、存储和转发消息。在 RabbitMQ 中,Broker 就是指 RabbitMQ 服务器本身。它处理消息的路由、队列管理和传递过程,确保消息从生产者正确地传递到消费者。简而言之,Broker 是整个消息传递系统的中心处理单元Virtual Host(虚拟主机):RabbitMQ 实例内的逻辑隔离区域,用于将不同的应用程序或环境的数据分开管理。每个虚拟主机拥有独立的队列、交换机和绑定
Exchange(交换机):接收生产者发送的消息并根据其路由规则将消息转发到一个或多个队列。交换机有不同类型(如 Direct、Fanout和Topic),每种类型具有不同的路由策略
Binding(绑定):定义交换机与队列之间的关系,确定消息如何从交换机路由到队列。绑定包括路由键和条件,用于控制消息的传递
Queue(队列):存储消息的缓冲区,消费者从队列中读取消息进行处理。队列保持消息直到消费者处理完成或消息过期
Consumer(消费者):从 RabbitMQ 队列中接收并处理消息的应用程序或服务
3.2、框架图流程
Producer发送消息流程
(1) 生产者连接到RabbitMQ Broker , 建立一个连接( Connection) ,开启一个信道(Channel)
(2) 生产者声明一个交换器,并设置相关属性,比如交换机类型、是否持久化等
(3) 生产者声明一个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除等
(4) 生产者通过路由键将交换器和队列绑定起来
(5) 生产者发送消息至RabbitMQ Broker,其中包含路由键、交换器等信息
(6) 相应的交换器根据接收到的路由键查找相匹配的队列
(7) 如果找到,则将从生产者发送过来的消息存入相应的队列中
(8) 如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者
(9) 关闭信道
(10) 关闭连接
Consumer接收消息流程
(1)消费者连接到RabbitMQ Broker ,建立一个连接(Connection ) ,开启一个信道(Channel)
(2) 消费者向RabbitMQ Broker 请求消费相应队列中的消息,可能会设置相应的回调函数,
以及做一些准备工作(3)等待RabbitMQ Broker 回应并投递相应队列中的消息, 消费者接收消息
(4) 消费者确认( ack) 接收到的消息
( 5) RabbitMQ 从队列中删除相应己经被确认的消息
( 6) 关闭信道
( 7) 关闭连接
四、RabbitMQ单节点部署
相关资源包的准备
比如我们都把资源安装在/software这个文件夹下
erlang:erlang-23.2.1-1.el7.x86_64.rpm
rabbitmq:rabbitmq-server-3.8.30-1.el7.noarch.rpm
socat:使用yum安装即可,关于yum可用源,去我上一个作品看就行
socat是一个多功能的网络工具,名字来由是Socket CAT,socat支持多协议,用于协议处理,端口转发,rabbitmq依赖于socat,因此在安装rabbitmq前要安装
4.1、安装工作
4.1.1、安装socat
yum install socat -y
4.1.2、安装erlang
rpm -ivh /software/erlang-23.2.1-1.el7.x86_64.rpm
4.1.3、安装RabbitMQ
rpm -ivh /software/rabbitmq-server-3.8.30-1.el7.noarch.rpm
4.1.4、测试时候安装成功
以此执行下面的命令
service rabbitmq-server status
service rabbitmq-server start
service rabbitmq-server status
s