RabbitMQ的五大常用模型介绍以及SpringAMQP的使用

MQ的介绍

MQ,MessageQuene即 消息队列,是程序与程序之间的异步通信一种方法。
为什么使用MQ?
MQ用来做程序间异步通信的,为什么使用MQ问题也是为什么要引入程序间异步通讯问题。
什么是同步通讯?
同步通讯简单理解就是下一行要执行的代码要等到上一行完全执行完毕才能够执行。例如:我要根据订单Id查询到用户信息并返回。此时就要先通过id调用订单系统查询到订单信息,在从订单信息中获取用户id,调用用户系统传递用户id去查询到用户信息并返回。
要想查询到用户信息就一定先查到订单信息,下一行代码执行依赖于上一行代码的执行结果,我们通常在代码中使用到就是同步通讯,从上到下是一步步执行的,不可以跳跃。就像当给axios加上async一样。
一个使用同步通讯不合理的例子:下单支付后,下单系统会等待支付完成,再调用仓储系统,进行扣除库存等操作,等到仓储系统全部执行成功后,在交给物流系统,等到物流系统全部执行成功后,再返回给用户下单成功信息。
同步通讯:各个系统像链一样执行,有一处断则全断,添加、删除就要修改
在这里插入图片描述
异步通讯:下一行代码不必等待上一行代码的执行结果,只要确保其被调用,最终会执行即可。类似于子线程、axios等
在这里插入图片描述
通过此例子中同步和异步通讯对比,就可以发现同步带来的问题?

  • 性能和吞吐能力下降:用户要等到这些系统一个个全部按顺序全部执行成功后,才能得到响应结果。使得用户的体验,大大的下降。就像去饭店点餐一样,只要顾客说出要吃的菜后,服务员就可以告诉顾客点菜成功(虽然没有做,但可以一定能够成功),而不是没有反应等到菜端上来后,菜告诉用户点菜成功。这样就会使得用户感觉点菜花的时间过长。回到上一个例子:在用户已经支付后,就已经能够确保下单一定成功了,返回给用户响应信息。其余的操作交给后台就可以了,只要保证成功即可,而不必让用户去等待。

  • 资源浪费:在用户支付成功后,不仅没有及时的返回用户结果,此线程还要参与调用其他系统,造成此线程资源的浪费。

  • 耦合度高:如果在仓储系统与物流系统中加上一个短信系统,那么这个下单系统的代码就要发生修改,在整个代码结果中塞进去一个短信系统。而使用异步通讯,只需要将一个独立的短信系统开发后,接入中间件,监听来自下单系统的统一发送的通知即可。
    调用者只负责将信息发送到队列中,被调用者只负责从消息队列中取出消息进行调用,二者通过这个中间人(消息队列)实现代码解耦。
    在这里插入图片描述
    级联失败:同步通讯中,在此过程如果发生一个错误,那么下面代码就不会执行,整个业务就全发生错误。但使用异步通讯,业务之间都是通过中间件获取消息的,彼此之间没有联系。即使有一个业务执行失败了,也不会影响到其他业务。

总结
使用异步通讯,能够快速的对用户操作做出反应(虽然没有执行完,但能确保最终执行成功),释放掉资源用给下一个用户。异步服务间相对独立,都统一受中间件的调用,要添加,删除某个服务十分的方便,同时,当有服务发生故障时,也不会影响到其他服务。

既然异步通信这么好,为什么还要使用同步通讯?
还是上一个例子,下单系统中一定要同步调用支付系统,因为后续的调用都在等在支付结果。如果要使用异步通讯,最后还要在代码的下面等待结果,就可有能还不如同步通讯调用这样有更高的时效性

怎样实现异步通讯?
如果是一个服务,可以开启一个子线程去调用即可,但这样只能使得要用户的响应速度得到提升。那如果是很多个服务呢?服务动态伸缩呢?这就需要一个消息队列。
通常就是将调用的消息发送到一个队列中,各个被调用者各自监听这个队列中的消息,队列中一有消息,就从消息队列中获取参数执行对应的方法。

使用MQ的好处

解耦:服务调用之间可以达到插拔式效果,各个服务间彼此隔离,互不影响
异步:无需等待后续处理,提升了系统吞吐量,提升响应速度,减少阻塞
削峰:大流量由消息队列先挡在各个服务的前面,消息可以在队列中留存作为缓冲,队列以合适的速度去调用
就像一个水坝一样,当有大的洪水时,水坝可以将洪水留在蓄水池中,然后以合适的速度下放,起到一个缓冲的作用。

使用MQ的坏处

  • 系统复杂度提高:像重复消费问题,顺序问题,消息可靠性问题,死信问题等等又多了一系列问题。业务流程相比于同步调用较为混乱。
  • 可用性问题:引入了MQ,就要保证MQ能够正常可用。异步调用依赖于MQ的性能,安全性,可用性。 多了一个风险
  • 一致性问题

注意:不是MQ去调用各个服务的,是各个服务监听MQ的消息通过回调去调用对应的方法
思考

  • 服务间的调用不是通过服务注册中心吗?通过服务中心发起Ribbon调用是同步调用
  • 使用MQ怎样达到一个服务某个实例的调用?多个服务某个实例的调用?

技术对比

常见的MQ有Rabbit MQ,Rocket MQ,Active MQ,Kafka。这些技术里Active MQ较弱

RabbitMQ ActiveMQ RocketMQ Kafka
公司/社区 Rabbit Apache 阿里 Apache
开发语言 Erlang Java Java Scala&Java
协议支持 AMQP,XMPP,SMTP,STOMP OpenWire,STOMP,REST,XMPP,AMQP 自定义协议 自定义协议
可用性 一般
单机吞吐量 一般 非常高
消息延迟 微秒级 毫秒级 毫秒级 毫秒以内
消息可靠性 一般 一般

RabbitMQ和RocketMQ是消息延迟吞吐量的取舍,kafka最大亮点高吞吐,通常都是在大数据中使用的。

RabbitMQ的使用

Docker启动

使用命令

docker run \
 -e RABBITMQ_DEFAULT_USER=用户名 \
 -e RABBITMQ_DEFAULT_PASS=密码 \
 --name mq \
 --hostname myhost \
 -p 15672:15672 \
 -p 5672:5672 \
 -d \
 Rabbit镜像id

RabbitMQ暴露两个端口,15672是用来供浏览器访问提供可视化管理的,5672是用来被各个服务连接的。
通过浏览器访问15672,输入配置的用户名,密码即可来到管理页面
在这里插入图片描述

管理面板有很多的功能,不止可以用于查看,例如:向队列中添加消息,查看队列消息,添加队列,管理队列,添加交换机,管理交换机等。

MQ角色介绍

在这里插入图片描述
MQ最基本的就是图中四大个角色
publisher:消息发布者,就是各个服务的调用者,就是它将消息发送到消息队列,其他服务(下面的消费者)监听回调
exchange:交换机,负责将消息广播到各个队列。给我的感觉就像凹透镜发散光一样,将消息发布者的消息广播到订阅的队列中,注意是同一个消息送到指定队列。如果发布者只将消息发送到一个队列中,则无需交换机。有交换机就与队列存在发布与订阅关系。交换机不能缓存数据,发送到队列过程失败,消息直接丢失。
quene:消息队列,有推送消息到消费者和暂存消息的功能。队列中的消息只能被一个消费者消费。
consumer:消息消费者,消费者一定是要从队列中获取消息的,然后在其内部监听队列中消息,监听的队列中一有消息就进行回调,达到调用服务中的方法目的。

一个服务使用一个队列,服务中的各个实例作为一个队列下的消费者。

五大常用消息模型

生产者也可以有多个,但在模型中,一个和多个只要以相同的消息格式往一个队列里发是一样的,为了简化用1个实例去代表
没有交换机
没有交换机,适用于消费者是一个服务下有多或一个实例,各个实例间争抢消费同一个消息。

  • 1、基本消息队列:一个队列一个消费者,相当于一个服务下只有一个实例
    在这里插入图片描述
  • 2、工作消息队列:一个队列对应多个消费者,相当于一个服务下有多个实例
    在这里插入图片描述

有交换机
交换机可以将一个消息发送到各个队列中,用于将调用信息发送到各个服务对应的某个实例下
但那些队列能够获取交换机的消息呢?常见的有以下三种

  • 广播 Fanout:只要是在一个主机下的所有队列都将获取到此交换机的信息,相当于调用此主机下所有服务中某个实例
    在这里插入图片描述

  • 路由 Direct:只有完美匹配对应上的队列才能够接收到消息
    在这里插入图片描述

  • 主题 Topic:只有匹配对应上的队列才能够接收到消息,与上一个不同的是,匹配的不要求一一对应,可以是一个范围,一个.级别

在这里插入图片描述

思考:当一个RabbitMQ服务器中不止这一组服务发布者与消费者怎么办?也就是说,多个相同的不同业务队列模型怎样进行隔离呢?
RabbitMQ中引入了一个VirtualHost,通过这个达到多租户效果,即一个RabbitMQ在逻辑上被分为多个小RabbitMQ,VirtualHost之间做到完全隔离。
因此,在连接时不仅要指定RabbitMQ的ip和端口,还要具体到是哪一个VirtualHost
在这里插入图片描述

使用SpringAMQP模板

什么是AMQP :AMQP,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。

SpringAMQP 在代码中简化RabbitMQ的使用

  1. 要在生产者与服务者之间引入依赖
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  1. 在配置文件中指明RabbitMQ服务的ip和端口以及对应的VirtualHost(默认为/
spring:
  rabbitmq:
    host: 192.168.83.100 
    port: 5672
    virtual-host: /
    username: yan
    password: 1234

在模拟演示过程,消费者端将监听到的消息直接进行输出,在实际应用中,是根据消息作为参数利用回调调用此服务方法实现异步调用的。

简单队列模型

在简单消息模型中,发送一个消息需要知道 队列名字,消费者根据队列名字来确定监听这个队列

  • 生产者

  • 生产者通常会在service业务中向队列中发送消息,controller调用service。这里在为了简便直接在controller中编写业务
    在这里插入图片描述

  • 消费者
    在这里插入图片描述

值得注意的是,这种方式需要声明队列
在这里插入图片描述

工作队列模型

即一个队列,多个消费者

在这里插入图片描述
那么一个队列中有多个消费者,消息是如何分配的呢?一个消费者消费完消息另一个会不会再次消费呢?
例:当队列中有10个消息,有消费者A,B都监听这个队列。消费者是先将消息平均取出,再进行消费的。一个消息被取出,其他消费者就不能消费此消息。
先平均取出再进行消费就存在一个问题:,性能差的和性能好的取出的一样多,但一样数量的消息消费起来时间就不一样了,就会发生性能好的消费者空余,性能差的消费者处理不过来。整体的服务处理效率就会降低,应该让他们按处理能力去取消息。
如果使其按处理能力取出呢?
就好比吃自助餐,饭量小的和饭量大的拿的一样多,要使其按饭量去取,就要让他们吃完再去取。消费队列也是一样,每次每个消费者只能拿1个消息(可以配置个数),处理过后,再去取下一个消息,虽然取的次数变多了,但能者多劳,分配的更加合理,整体的处理效率就提高了。
修改配置:
在这里插入图片描述

广播模型 Fanout

引入了交换机向各个队列中扩散消息
在这里插入图片描述
消费者依然指定队列名从队列中获取消息,但与生产者直接相连的不在是队列,而是交换机。发布者向交换机中发送信息,交换机在给绑定的队列广播信息
因此在生产者端就要声明交换机,声明队列,绑定交换机与队列最后将消息发送给交换机

这里使用名字为fan1的交换机 使用队列名为q1,q2的两个队列与此交换机进行绑定,每个队列下都有一个消费者。我向交换机中发送一条消息,两个队列下的消费者都会接收到消息

方式一:使用@Bean方式 将队列与交换机进行绑定


消费者端
在这里插入图片描述

依旧是从队列中获取消息
在这里插入图片描述
生产者端
生产者依旧是向队列发送消息
在这里插入图片描述

流程总结
在这里插入图片描述
方式二:使用注解的方式
生产者依旧是将消息发送给交换机,但它不必去管理队列和绑定交换机与队列
消费者在监听队列时,指明其交换机并且绑定队列与交换机的关系

在这里插入图片描述

交换机总结

  1. 有了交换机后,生产者是要将消息发送给交换机的
  2. 交换机要与队列进行绑定

注意
3. 交换机不能缓存数据,交换机向队列广播消息过程失败则消息丢失

路由模式 Direct

与广播模式不同的是,并不是所有与交换机绑定的队列就一定能够获取交换机推送的消息,还要在此基础上,口令(routing Key)与交换机当前消息的口令(routing Key)完全匹配。这种模式能够使得交换机将消息广播到与它绑定的队列中,指定的某些队列上。
例如:我有在订单系统本次发送消息只想调用仓储系统,但与交换机绑定的还有物流系统,这就需要在消息中指明routingKey,只有与之匹配的队列才能够获取,使得调用更加灵活。

使用方式在Faout模式下,生产者端在发送消息时指定routingKey
在这里插入图片描述

消费者端在绑定队列和交换机时指定routingKey
在这里插入图片描述

在DIRCT模式中,队列要想获取交换机中的数据满足的条件

  1. 与交换机进行绑定
  2. 队列的routingKey中包含与之绑定交换机的此次消息的routingKey

即使routingKey相同,监听队列绑定的交换机不同,也无法获取数据
Direct模式当各个队列的routingKey都含有此消息的routingKey,那么就达到了Faout模式

主题模式 Topic

这种模式相较于Direct模式,routingKey可以使用通配符。每一个.代表隔着级别,*代表一个级别,#代表任意级别
例如:a.b.c =a.*.c=a.#a.b.c!=a.*因为*只能代表一级(一个单词)
注意,这里*代表的是少的,只能代表一个

这种通配符既可以在生产者的消息中指定,又可以在消费者绑定队列中指定
生产者
在这里插入图片描述
消费者
在这里插入图片描述
也可以使用@Bean的方式声明和绑定,这里只演示一个
在这里插入图片描述

注意:不仅可以向队列发送String类型的消息,所有类型的均支持。在发送对象作为消息时,由于对象的传输一定需要进行序列化。

那么怎样通过消息队列传递对象数据呢?
可以发送方可以将对象转为JSON字符串格式将对象序列化,消费者端可以将JSON字符串进行反序列化转为对象。

springAMQP虽然能够自动创建交换机,队列,但生产者向队列,交换机发送前前一定要确保已经存在。声明可以在消费者端,也可以在生产者端,但应该在消费者端,这样当有业务增减时对生产者可以实现无感知的。队列、交换机以及他们绑定关系的声明可以通过注解这种方式声明,也可以通过向容器中注入bean的方式声明

RabbitMQ中一但创建了交换机指定了类型是不允许修改的,除非删掉这个虚拟机
例如:我创建了一个交换机名为a1,指定为faout类型,但我想要将a1更换为direct,这时是不成功的,a1交换机仍为faout类型,而且会报这样的错误inequivalent arg 'type' for exchange 'e1' in vhost '/': received 'direct' but current is 'fanout'
解决方式要么换一个交换机的名字,要么删除重新创建新的类型。

猜你喜欢

转载自blog.csdn.net/m0_52889702/article/details/128443290