- 一个进程内能够创建的线程数量是有限的。
所有中间件的目的:
- 性能效率上的一个提升
- 代理:帮你去完成一些额外的事情
消息中间件属于分布式架构中的一个子系统,关注于数据的发送和接收 => 异步消息传递机制
MQ本身是不产生数据的,MQ它不生产消息,它只是消息的搬运工!
通信方式介绍
- 分布式系统架构思想下,将复杂系统拆分为多个独立的子模块,例如微服务模块,此时就需要考虑子模块间的远程通信,典型的通信模式分为以下两种:一种是同步的RPC远程调用;一种是基于中间件代理的异步通信方式,该中间件代理一般就是消息中间件。
为什么需要MQ?
- 微服务一旦拆分,就必然涉及到服务之间的相互调用,目前我们服务之间调用采用的都是基于OpenFeign的调用,这种调用中,调用者发起请求后需要等待服务提供提供者执行业务返回结果后,才能继续执行后面的业务,也就是说调用者在调用过程中处于阻塞状态,因此我们将这种调用方式称为同步调用,也可以叫同步通讯,但在很多场景下,我们可能需要采用异步通讯的方式,为什么呢?
我们先来看看什么是同步通讯和异步通讯。
同步通讯 & 异步通讯
微服务间通讯有同步和异步两种方式:
- 同步通讯:就像打视频电话,双方的交互都是实时的,需要实时响应
- 异步通讯:就像发邮件、微信聊天,双方的交互不是实时的,不需要马上回复
两种方式各有优劣:
- 打电话可以立即得到响应,但是你却不能跟多个人同时通话,只能同一时刻跟一个人聊天。
- 发微信可以同时与多个人收发微信,但是往往响应会有延迟。
所以,如果我们的业务需要实时得到服务提供方的响应,则应该选择同步通讯(同步调用),而如果我们追求更高的效率,并且不需要实时响应,则应该选择异步通讯(异步调用)。
同步调用的方式我们已经学过了,之前的微服务基于OpenFeign调用就是同步调用,但是:
- 异步调用又该如何实现?
- 哪些业务适合用异步调用来实现呢?
同步通讯
- 直接调用彼此来通信,比如RPC远程过程调用的技术。
我们之前所学习的微服务基于Feign调用或者OpenFeign调用就属于同步方式,虽然时效性较强,调用可以实时得到结果,但存在下面的问题:
- 耦合度高,拓展性差:每次有新的需求,都要修改原来的代码,代码经常变动,不符合开闭原则(对扩展开放,对修改关闭),拓展性差。
- 性能和吞吐能力下降:由于我们采用了同步调用,调用者需要等待服务提供者执行完返回结果后,才能继续向下执行,也就是说每次远程调用,调用者都是阻塞等待状态,调用者需要等待服务提供者响应,如果调用链过长则最终整个业务的响应时间等于每次远程调用的执行时间之和。
- 浪费系统资源:调用链中的每个服务在等待响应过程中,不能释放请求占用的资源,高并发场景下会极度浪费系统资源。
- 有级联失败问题:如果服务提供者出现问题,所有调用方都会跟着出问题,从而迅速导致整个微服务群故障
而要解决这些问题,我们就必须用异步调用的方式来代替同步调用。
异步调用
- 异步调用的常见实现就是事件驱动模式
异步调用方式其实就是基于消息通知的方式,一般包含三个角色:
- 消息发送者(事件发布者 - Publisher):投递消息的人,就是原来的服务调用方
- 消息代理Broker(事件代理者):负责管理、暂存、转发消息,你可以把它理解成微信服务器
- 消息接收者(事件订阅者 - Consumer): 接收和处理消息的人,就是原来的服务提供方
Broker:代理,经纪人
在异步调用中,服务消费者 / 服务调用方不再直接同步调用服务提供者的业务接口,而是发送一条消息投递给消息Broker,然后服务提供者根据自己的需求从消息Broker那里去订阅消息。
这样,服务消费者与服务调用者就完全解耦了。
异步调用的优势:
- 服务解耦,耦合度极低,每个服务都可以灵活插拔,可替换
- 性能提升,吞吐量提高:服务调用间没有阻塞,无需等待服务提供者处理完成,响应更迅速,不会造成无效的资源占用
- 故障隔离:服务间没有直接调用(服务没有强依赖),不存在级联失败问题
- 流量削峰:不管发布事件的流量波动多大,都由Broker接收,订阅者可以按照自己的速度去处理事件
异步调用的缺点:
- 架构变得更加复杂,业务没有明显的流程线,不好追踪管理
- 完全依赖于Broker的可靠性、安全性和性能,对Broker的要求更高
- 阻塞就是耦合,非阻塞就是解耦合~!
- 异步是思想!
- 建议单体应用就不要用MQ了!
消息Broker,目前常见的实现方式就是消息队列(MessageQueue),简称为MQ,说白了就是存放消息的队列,也就是事件驱动架构中的Broker。
MQ介绍
MQ概述
- MQ全称Message Queue(消息队列),是在消息的传输过程中保存消息的容器,多用于分布式系统之间进行通信,它是一种应用程序对应用程序的通信方法。
- 通过MQ实现的松耦合架构设计可以提高系统的可用性和可扩展性,是适用于现代应用的最佳设计方案。
- 在计算机领域,消息指的就是数据(跟业务整合的)!
- 消息队列是应用程序和应用程序之间的通信方法,主要应用在多个工程之间,相互的传递消息,消息由生产者发送到MQ进行排队,然后按原来的顺序由消费者从MQ当中来拉取消息进行处理。
- 队列:数据结构的一种,特征为"先进先出" - FIFO,意思是有序的。
- 消息队列本质上就是一个上下游通信的队列,MQ(Message Queue),无非就是发消息,存消息,消费消息
为什么使用MQ?
-
在项目中,可将一些无需即时返回且耗时的操作提取出来,进行异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高系统的吞吐量。
Tomcat的并发量大约是500个请求左右/s!(500个请求就意味着有500个线程)
MQ的优势和劣势
优势:
- 应用解耦
- 异步提速
- 削峰填谷
劣势:
- 系统的可用性降低
- 系统复杂度提高
- 一致性问题
1. 应用解耦 - 提供系统容错性和可维护性:消费者存活与否不影响生产方
- 系统的耦合性越高,容错性就越低!
应用解耦意思就是消费者无论挂掉没有,都不影响生产者在这里发消息,MQ就相当于是一个中介,生产者通过MQ与消费者进行交互!
应用解耦 / 使用MQ使得应用间解耦 可以提高系统的容错性和可维护性!
- 一个程序和另一个程序它们的耦合度要降低!(高内聚,低耦合)
- 系统的耦合度越高,容错性就越低,可维护性就越低!
2. 异步提速 - 提升用户体验和系统吞吐量:生产方发完消息,可以继续进行下一步的业务逻辑
同步:一个一个来/走,你走完了我才能走 ⇒ 一个动作做完以后才能进行下一个动作。
异步:各来/走各的 ⇒ 两个动作可以同时做。
- 将不需要同步处理并且耗时长的操作由消息队列来通知消息接收方进行异步处理,从而提高应用程序的响应时间,提升系统的吞吐量。
- 异步调用能够提高系统的响应速度以及吞吐量。
3. 削峰填谷 - 提高系统稳定性
- TPS:每秒事务数
- 削峰填谷指的是通过消息队列平滑、缓和的处理高峰期突发产生的高并发流量,从而减少对后端服务的冲击,避免因突发流量而导致应用崩溃或系统瘫痪。
- 这种技术的实现方式是将突发的请求放入到消息队列当中,这样一来,高峰期产生的数据就会被积压在MQ当中,高峰就被"削"掉了,然后通过异步或定时任务的方式,从消息队列当中取出消息,并以匀速的方式发送到后端服务中,直到消费完积压的消息,这就叫做"填谷",这样就可以平滑的处理高并发流量,在避免请求过多、并发过高的同时,保障了后端服务的正常工作。
- 削峰填谷技术在一些高并发的场景下被广泛应用,比如秒杀、抢购等大规模的并发场景。
- 削峰填谷后,可以提高系统的稳定性。
4. 数据分发
- 通过消息队列可以让数据在多个系统之间进行流通,消息生产者不需要关心谁来使用数据,只需要将数据发送到消息队列,消息的消费者(数据使用方)在消息队列中直接获取数据进行消费即可。
MQ的劣势
1. 系统的可用性降低(降低系统的可用性)
系统引入的外部依赖越多,系统稳定性越差。
- 因为你在系统当中引入了一个MQ这样的组件,你只要引入组件的话,这个组件万一坏了,我们整个系统不就全崩掉了,所以说只要是引入了新的一些组件,那么必然会造成系统的可用性降低!
- 系统引入的外部依赖越多,系统稳定性越差,一旦MQ宕机,就会对业务造成影响。
如何保证MQ的高可用?
2. 系统复杂度提高
- MQ的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过MQ进行异步调用。
如何保证消息没有被重复消费?如何处理消息丢失情况?如何保证消息传递的顺序性(顺序消费)呢?
3. 一致性问题
- A系统处理完业务,通过MQ给B、C、D三个系统发消息数据,B、C、D都要处理完才算事务的一致,如果B系统、C系统处理成功,D系统处理失败,那么整个事务的处理也算是失败的!
所以如何保证消息数据处理的一致性呢?
MQ产品介绍
- RocketMQ和Kafka支持的协议种类会比较少,支持的协议越多,将来能干的事情也就会越多,并且AMQP协议它是跨语言、跨平台的,也就意味着利用RabbitMQ可以实现在各种不同语言之间的消息的交互,因此它所支持的客户端种类也会比较多。
- Kafka的单机的并发吞吐能力可以达到百万级!
- 高吞吐量意味着会牺牲消息的延迟(从发送到接收中间经历的事件)以及消息可靠性!
- Kafka的单机吞吐能力最强,但稳定性较差,可靠性较低,所以更适合海量数据的这种传输,并且对于数据安全要求不高的,比如说日志数据传输;RabbitMQ的消息延迟最低!
1. ActiveMQ
- 纯Java语言实现,并发量较低,万级数据吞吐量,处理速度比较慢,处理速度达到毫秒ms级,成熟度高,主从架构
2. RabbitMQ
- 底层由erlang语言实现,erlang语言比较底层,直接和物理硬件进行交互,因此处理速度会非常快,处理速度是微秒us级别的,主从架构
3. RocketMQ
- Java语言实现,高吞吐,万亿级吞吐保证,处理速度毫秒级别,分布式架构,功能强大,扩展性强,分布式架构
4. Kafka
- scala语言实现,scala是运行在我们的Java虚拟机上的一门语言,并且它也可以调用Java的API,吞吐量高,处理速度也比较快,处理速度达到毫秒级,但是功能比较单一,它就是做大批量数据的一个迁移,应用于日志分析、大数据采集较多,所以在大数据的技术领域当中,用Kafka是最多的,分布式架构
RocketMQ其实就是Kafka的升级版本。
Kafka的缺陷:
- 功能单一,你做Kafka的话,你就一个发消息和收消息,什么死信消息也没有,所以它只适合于做日志的收集,它的好处就是并发量比较大。
RabbitMQ它的性能比较低,单机吞吐量较低,其次它是基于Erlang语言编写的,你报错了也看不懂啊!
Kafka很流行的原因是它是吃了Java生态圈的红利!
RocketMQ概述
-
RocketMQ是阿里开源的一款非常优秀的中间件产品,脱胎于阿里的另一款队列技术MetaQ,后捐赠给Apache基金会作为一款孵化技术,仅仅经历了一年多的时候就成为了Apache基金会的顶级项目,它现在已经在阿里内部被广泛的应用,并且经受住了多次双十一的这种极致场景的压力!(2017年的双十一,RocketMQ流转的消息量达到了万亿级,峰值TPS达到了5600万)
RocketMQ解决了MQ的所有缺点~!
RocketMQ的工作原理
- 生产者集群来进行发消息
- Broker:部署着RocketMQ的机器就叫一台Broker(Broker要保证高可用,所以一定部署的是集群 => 双主双从集群搭建)。
- 生产者发消息到RocketMQ当中,然后RocketMQ还能返回一个接收结果。
- 消息的消费者就会从RocketMQ当中来拉取消息。
我们的消息达到了RocketMQ之后,我们的消费者它怎么就知道消息队列当中就有了消息了呢?消息队列有哪两种常见的模式?消息消费模型?
主要分为以下两种消息消费模型:消费端获取消息有两种模式
Consumer从Broker拉取消息还是Broker将消息推送到Consumer,也就是Pull还是Push??
1. 拉取模式(拉取消费)- Pull:消息不是主动推送给Consumer消息消费者的,而是要由消息消费者从队列中主动请求获取的:消费者这里可以起一个线程之类的,每隔一秒钟问一下RocketMQ,队列当中有消息吗?
- 拉取模式很不好,很占用我们消费者的资源,因为每秒钟它都要发一次请求去拉取数据。
但实际上RocketMQ都是Pull模式,RocketMQ是基于长连接,不断的去进行轮训来实现消息的拉取Pull => Broker会去轮询对应请求的长连接:定时任务,这个定时任务会在Broker启动的时候去开启。
![](https://i-blog.csdnimg.cn/blog_migrate/09e27bb4287842d15a5e3bc1f3f1d3c6.png)
拉模式有什么优缺点?
拉模式的主要优点:
- 针对性较强,由Consumer根据自己的消费能力来自主决定消费速率,能满足客户端的个性化需求,可以批量拉取,也可以单条拉取
- 服务端压力较小,服务端只是被动接收请求。
拉模式的缺点:
- 实时性较差,无法及时收到服务端实时更新的消息。
- Pull有个缺点是:如果Broker没有可供消费的消息,将导致Consumer不断的在循环中轮询(会导致Conumer空轮询,消耗资源),直到新消息到达
2. 监听器模式或推送模式(推送消费 - 常用)- Push:我们的Consumer消费者和RocketMQ的服务端(Broker)建立一个长连接,建立好了长连接之后,一旦RocketMQ队列当中有了消息并且有某一个消费者监听着某一个队列,那么RocketMQ就会把这个消息推送给消息的消费者,这就是一个推送模式或者叫我们的消费者起一个监听器,所以说最常用的一定是监听器模式或者说推送模式。
这就是最基础的消息的发送与接收!
推模式其实是基于拉模式来去完成的,即Consumer会先发起一个pull request请求过来,如果没有数据,我就将你这个请求给保存起来,我们将这个请求去保存起来的这样一个专业名词叫hold住,这就是一个长连接不是吗???
推模式有什么优缺点?
主要优点:消息实时性强,能及时向客户端推送最新的消息,不会导致Consumer循环等待。
推模式的缺点:
- 不能确保发送成功:推模式采用的是广播方式,只有服务端与客户端在同一个频道上推模式才 能发送成功。
- 不能跟踪发送状态:推模式采用了开环控制技术,消息推送后的状态无从得知,并且推送的消息可能不满足客户端的要求,针对性较差。
- 由Broker决定消息推送的速率,对于不同消费速率的Consumer就不太好处理了,消息系统都致力于让Consumer以最大的速率最快速的消费消息,但不幸的是,Push模式下,当Broker推送的速率远大于Consumer消费的速率时,Consumer恐怕就要崩溃了,忽略了Consumer的消费能力
- NameSever;服务注册中心,NameServer命名服务器它就是专门来存储一些元数据,它会存储Broker,即MQ机器的IP。
所以说,真实的流程其实是这个样子的:
- 生产者想要往Broker发消息,生产者发消息的时候其实是首先给我们的命名服务器集群先发一个消息,说命名服务器啊,我想要发消息了,你告诉我Broker的一些信息吧 ,我应该往哪发啊,来获取Broker的一些信息,然后我们的命名服务器才选一个恰当的、适当的某一台Broker的地址给到了生产者这里,然后我们的生产者才会拿着这个真实的Broker的IP往那儿发去。
- 同理,消费者说我想要起一个监听器,监听某一个队列,那到底我应该监听哪一个IP,所以我们的消费者一启动,首先问命名服务器集群说,命名服务器啊,你告诉我一些Broker的地址吧,我们的命名服务器就会把Broker的IP地址给到消息的消费者,然后消息的消费者才真正去和某一台Broker去建立长连接的!
消息的生产者、消费者以及Broker一启动就会将自身的信息注册到NameServer命名服务器当中。
通过发心跳,发HTTP请求来证明自己还活着!
- JMS:就是消息领域的JDBC
- RocketMQ安装启动
- 消息发送
- 消息类别
互联网的四大法宝:分布式 & 缓存 & 多线程 & 异步(MQ)
所有解决异步的场景都可以用MQ来解决!