消息队列原理和应用场景总结

同步架构和异步架构

了解消息队列之前,相信你应该了解了同步架构异步架构的区别,这里再次提出一下。

1.同步架构和异步架构的区别

1.1 同步调用

是指从请求的发起一直到最终的处理完成期间,请求的调用方一直在同步阻塞等待调用的处理完成。
在这里插入图片描述
如图,在这个例子中客户端代码ClientCode,需要执行发送邮件sendEmail这样一个操作,它会调用EmailService进行发送,而EmailService会调用SmtpEmailAdapter这样一个类来进行处理,而这个类会调用远程的一个服务,通过SMTP和TCP协议把请求发送给它。而远程服务器收到消息以后会对消息进行一系列的操作,然后将邮件发送出去,再进行返回。Adapter收到返回后,再返回给EmailService,EmailService收到返回后再把返回结果返回给Clientcode。
在这里插入图片描述

ClientCode在sendEmail发出请求后,就一直都阻塞在这里,等待最终调用结果的返回,是成功还是失败。因为这个过程是阻塞等待的,所以这个过程也就是同步调用。

1.2 异步调用

是指在请求发起的处理过程中,客户端的代码已经返回了,它可以继续进行自己的后续操作,而不需要等待调用处理完成,这就叫做异步调用。
在这里插入图片描述

异步调用过程,同样看刚刚发送邮件的例子,用户Clientcode调用EmailService以后,EmailService会把这个调用请求发送给消息队列,然后就立即返回了。Clientcode收到返回以后继续向下处理,不会继续阻塞等待。实际上消息发送到Queue后,还没有被处理,我们看到后面的消息消费,其实要比EmailService返回可能还要晚一点,EmailService返回以后消息才会被消费处理。有一个QueueConsumer消息队列的消费者,从消息队列中取出这个消息,再把这个消息发送给SmtpAdapter,也就是调用SmtpAdapter,处理逻辑跟同步调用一样,SmtpAdapter通过SMTP的通讯协议,把消息发送给远程的一个服务器,进行邮件发送,通过RemoteServer进行处理,处理完了收到返回,再把返回结果通知消息队列Queue。在这个过程中,客户端的调用,也就是应用程序的调用,和业务逻辑真正发送邮件的操作是不同步的。

2.异步架构的主要组成部分:消息生产者、消息消费者、分布式消息队列

使用异步调用架构的主要手段,就是通过消息队列构建,如下是它的架构图。
在这里插入图片描述

消息的生产者将消息发送到消息队列以后,由消息的消费者从消息队列中获取消息,然后进行业务逻辑的处理,消息的生产者和消费者是异步处理的,彼此不会等待阻塞,所以叫做异步架构
使用消息队列构建一个异步调用架构,你需要了解如下3种角色。

2.1 消息的生产者

是客户端应用程序代码的一部分,用来初始化异步调用处理流程。在基于消息队列的处理中,生产者的职责非常少,它要做的就是创建一个合法的消息,并把这个消息发送到消息队列中,由应用开发者决定生产者的代码在哪里执行,什么时候发送消息。

2.2 消息队列

消息队列是消息发送的目的地和发给消费者的一个缓冲。消息队列实现的方法有好多种,可以用共享文件夹,也可以用关系数据库或者NoSQL系统,当然最主要的还是使用专门的分布式消息队列服务器来实现。

2.3 消息的消费者

消息的消费者从消息队列中接受并处理消息,消息的消费者也是由应用开发者实现的,但是它是一个异步处理的组件。消息的消费者不需要知道生产者存在,它只依赖消息队列中的消息。消息的消费者通常部署在独立的服务器上,和消息的生产者完全隔离,并且可以通过添加硬件的方式进行伸缩。

3.异步架构的两种主要模型:点对点模型和发布订阅模型

使用消息队列构建异步的调用架构,你还需要知道两种模型:点对点模型和发布订阅模型。

3.1 点对点模型(不可重复消费)

消费者和生产者只需要知道消息队列的名字,生产者发送消息到消息队列中,而消息队列的另一端是多个消费者竞争消费消息,每个到达消息队列的消息只会被路由到一个消费者中去,所以消费者看到的是全部消息的一个子集。我们看这张图,消息的生产者有多个,消息的消费者也有多个,多个生产者将消息发送到消息队列中,而有多个消费者去消息队列中对消息进行竞争性的消费。每个消息只会被一个消费者消费,每个消费者只会消费消息队列中的一部分消息。
总结:多个生产者生产消息,但只有一个且仅有一个消费者线程会接受到该消息
在这里插入图片描述

3.2 发布订阅模型(可以重复消费)

在发布订阅模型中,消息可能被发送到不止一个消费者,生产者发送消息到一个主题,而不是队列中。消息被发布到主题后,就会被克隆给每一个订阅它的消费者,每个消费者接收一份消息复制到自己的私有队列。消费者可以独立于其他消费者使用自己订阅的消息,消费者之间不会竞争消息。常用的分布式消息队列都支持发布订阅模型,也就是说消息的发布订阅模型是分布式消息队列的一个功能特性。
总结:消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到topic的消息会被所有订阅者消费。
在这里插入图片描述

支持订阅组的发布订阅模式:
发布订阅模式下,当发布者消息量很大时,显然单个订阅者的处理能力是不足的。实际上现实场景中是多个订阅者节点组成一个订阅组负载均衡消费topic消息即分组订阅,这样订阅者很容易实现消费能力线性扩展。可以看成是一个topic下有多个Queue,每个Queue是点对点的方式,Queue之间是发布订阅方式。
在这里插入图片描述

3.3 两个模型的应用

点对点模型:

主要用于一些耗时较长的逻辑相对独立的业务。

比如说我前面的讲到的发送邮件这样一个操作。因为发送邮件比较耗时,而且应用程序其实也并不太关心邮件发送是否成功,发送邮件的逻辑也相对比较独立,所以它只需要把邮件消息丢到消息队列中就可以返回了,而消费者也不需要关心是哪个生产者去发送的邮件,它只需要把邮件消息内容取出来以后进行消费,通过远程服务器将邮件发送出去就可以了。而且每个邮件只需要被发送一次。所以消息只被一个消费者消费就可以了。

发布订阅模型:

主要用于多个应用服务系统相互关联的业务。
如新用户注册这样一个消息,需要使用按主题发布的方式。
比如新用户注册,一个新用户注册成功以后,需要给用户发送一封激活邮件,发送一条欢迎短信,还需要将用户注册数据写入数据库,甚至需要将新用户信息发送给关联企业的系统,比如淘宝新用户信息发送给支付宝,这样允许用户可以一次注册就能登录使用多个关联产品。一个新用户注册,会把注册消息发送给一个主题,多种消费者可以订阅这个主题。比如发送邮件的消费者、发送短信的消费者、将注册信息写入数据库的消费者,跨系统同步消息的消费者等。

4. 异步架构的应用场景

同步还是异步在目前的大环境下,绝大部分是针对服务键通信的问题,下面就各点说明一下什么时候选择异步架构。

4.1 不影响主线程逻辑,不涉及共享资源,或对共享资源只读,即非互斥操作

关于这一条,继续用订单服务与供应链服务的例子,订单下单成功后,主流程直接返回成功,将该订单的详情通过MQ,异步推送给供应链系统,供应链系统后续执行的结果并不影响订单的生成流程。

如果服务A同步调用服务B,那么A和B就是紧密耦合的,而紧耦合的系统其可伸缩性特征是各服务必须要保持一个节奏,要伸缩服务A必须同时伸缩服务B,同步调用的服务在可用性方面也面临着同样的问题。反过来说,如果服务A和B的通信是异步的,不管是通过MQ或者批处理还是其他什么手段,可以各自根据系统的情况,执行必要的伸缩操作。而且,此时服务A和B的是相互独立的,即使服务B不能正常使用,服务A仍然能够继续工作。

4.2 服务间交互的数据,在时序上的没有严格关系

订单服务传送给供应链服务的订单数据,比如说订单A和订单B,他们传给供应链系统的时序,并不影响供应链服务处理流程,对最终的业务结果没有任何影响。再举一个例子,就是我们的站内推送和各种消息,所有这些消息发放给客户端,并不在乎消息发送给某个客户的先后顺序,只要保证消息最终能顺利发送完毕即可,所以推送给消息服务会采用异步的形式。

【醒目】反过来说,如果需要结果的处理始终和前文保持在一个上下文内,必须要使用同步。

4.3 IO操作或者需要大量计算等耗时操作

这个情况主要用于前端AJAX的情况,先将成功状态返回,几秒后,将详情返回,局部刷新页面。对于网站或者交易系统,消耗数据或执行的延迟时间来换取用户的延迟时间是值得的,因为用户的体验会因此得到提升。活动跟踪、单据开付和报表等处理过程显然都应该属于后台活动,很多步骤可以进一部分解成异步运行,任何可以晚点再做的事情都应该晚点再做。

多说一句,异步性可以从一定程度上降低系统投入的成本。常规的同步操作需要系统必须按照负载的峰值来配备基础设施,即使在大促做年度活动的时间周期里任务最重的时刻,系统也必须有能力立即完成处理。将处理过程转变为异步的流,系统就不需要按照峰值来配备,只需要满足平均负载。异步队列可以将处理任务分摊到较长的时间里,起到削峰的作用。系统的负载变化越大,曲线越多尖峰,就越能从异步处理中得益。

消息队列

1. 消息队列的基本定义和常见类型

我们通常在软件架构中说的消息队列,大部分是用来做服务间通讯的,这里我们以java为主流代表。

1.1 基本定义

Java消息服务(Java Message Service,JMS)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信(即上述说的异步架构的软件系统)。

目前业界常用的消息队列产品,有这么几种:RabbitMQ 、ActiveMQ、RocketMQ 、Kafka

1.2 常见类型与比较

RabbitMQ 的主要特点是性能好,社区活跃,但是RabbitMQ用Erlang开发。

ActiveMQ 影响比较广泛,可以跨平台,使用Java开发,对Java比较友好。

RocketMQ 是阿里推出的一个开源产品,也是使用Java开发,性能比较好,可靠性也比较高。

Kafka 是LinkedIn出品的,专门针对分布式场景进行了优化,因此分布式的伸缩性会比较好。

目前看来,Kafka因为最初设计时就是针对互联网的分布式、高可用应用场景而设计,并且在大数据领域得到广泛支持,资料文档更加完善,因此在互联网企业得到更多的应用。

2. 消息队列的应用场景和好处

2.1 应用场景

  1. 一般都是异步的,不要求立即响应。打个比喻,你同时只能做一件事,但是你有一个ToDoList,每做完一个,在ToDoList中划掉一个,有新的分给你的任务,就放到ToDoList后面,这个ToDoList差不多就是一个队列了,队列就是一种数据结构,减轻的不止是数据库的压力。
  2. 可用于一些不及时的操作,耗时的操作,比如发送邮件、图片处理等等。
    就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同时由于使用了消息队列,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦和
  3. 比如你的服务器一秒能处理100个订单,但秒杀活动1秒进来1000个订单,持续10秒,在后端能力无法增加的情况下,你可以用消息队列将总共10000个请求压在队列里,后台consumer按原有能力处理,100秒后处理完所有请求(而不是直接宕机丢失订单数据)。使用消息队列能够使关键组件顶住增长的访问压力,而不是因为超出负荷的请求而完全崩溃 , 就是防止雪崩。某一时刻数据量突然增大,起到一个缓冲。

2.2 好处

2.1中说的简单的几个例子,其实都是围绕着消息队列的主要功能来的:异步,解耦,削峰填谷。带来的好处如下:

  1. 实现异步处理,提升处理性能

对一些比较耗时的操作,可以把处理过程通过消息队列进行异步处理。这样做可以推迟耗时操作的处理,使耗时操作异步化,而不必阻塞客户端的程序,客户端的程序在得到处理结果之前就可以继续执行,从而提高客户端程序的处理性能。

  1. 可以让系统获得更好的伸缩性

耗时的任务可以通过分布式消息队列,向多台消费者服务器并行发送消息,然后在很多台消费者服务器上并行处理消息,也就是说可以在多台物理服务器上运行消费者。那么当负载上升的时候,可以很容易地添加更多的机器成为消费者。
在这里插入图片描述
如图中的例子,用户上传文件后,通过发布消息的方式,通知后端的消费者获取数据、读取文件,进行异步的文件处理操作。那么当前端发布更多文件的时候,或者处理逻辑比较复杂的时候,就可以通过添加后端的消费者服务器,提供更强大的处理能力。

  1. 可以平衡流量峰值,削峰填谷
    使用消息队列,即便是访问流量持续的增长,系统依然可以持续地接收请求。这种情况下,虽然生产者发布消息的速度比消费者消费消息的速度快,但是可以持续的将消息纳入到消息队列中,用消息队列作为消息的缓冲,因此短时间内,发布者不会受到消费处理能力的影响。
    在这里插入图片描述
    从这张图可以看到,因为消息的生产者是直接面向用户请求的,而用户的请求访问压力是不均衡的。如淘宝每天的访问高峰是在上午10点左右,而新浪微博则可能在某个明星半夜发一条微博后突然出现访问高峰。

在访问高峰,用户的并发访问数可能超过了系统的处理能力,所以在高峰期就可能会导致系统负载过大,响应速度变慢,更严重的可能会导致系统崩溃。这种情况下,通过消息队列将用户请求的消息纳入到消息队列中,通过消息队列缓冲消费者处理消息的速度。

如图中所示,消息的生产者它有高峰有低谷,但是到了消费者这里,只会按照自己的最佳处理能力去消费消息。高峰期它会把消息缓冲在消息队列中,而在低谷期它也还是使用自己最大的处理能力去获取消息,将前面缓冲起来、来不及及时处理的消息处理掉。那么,通过这种手段可以实现系统负载消峰填谷,也就是说将访问的高峰消掉,而将访问的低谷填平,使系统处在一个最佳的处理状态之下,不会对系统的负载产生太大的冲击。

  1. 失败隔离和自我修复

因为发布者不直接依赖消费者,所以分布式消息队列可以将消费者系统产生的错误异常与生产者系统隔离开来,生产者不受消费者失败的影响。 当在消息消费过程中出现处理逻辑失败的时候,这个错误只会影响到消费者自身,而不会传递给消息的生产者,也就是应用程序可以按照原来的处理逻辑继续执行。

所以,这也就意味着在任何时候都可以对后端的服务器执行维护和发布操作。可以重启、添加或删除服务器,而不影响生产者的可用性,这样简化了部署和服务器管理的难度。

  1. 可以使生产者和消费者的代码实现解耦合

也就是说可以多个生产者发布消息,多个消费者处理消息,共同完成完整的业务处理逻辑,但是它们的不需要直接的交互调用,没有代码的依赖耦合。在传统的同步调用中,调用者代码必须要依赖被调用者的代码,也就是生产者代码必须要依赖消费者的处理逻辑代码,代码需要直接的耦合,而使用消息队列,这两部分的代码不需要进行任何的耦合。

耦合程度越低的代码越容易维护,也越容易进行扩展。

比如新用户注册,如果用传统同步调用的方式,那么发邮件、发短信、写数据库、通知关联系统这些代码会和用户注册代码直接耦合起来,整个代码看起来就是完成用户注册逻辑后,后面必然跟着发邮件、发短信这些代码。如果要新增一个功能,比如将监控用户注册情况,将注册信息发送到业务监控系统,就必须要修改前面的代码,至少增加一行代码,发送注册信息到监控系统,我们知道,任何代码的修改都可能会引起bug。

而使用分布式消息队列实现生产者和消费者解耦合以后,用户注册以后,不需要调用任何后续处理代码,只需要将注册消息发送到分布式消息队列就可以了。如果要增加新功能,只需要写个新功能的消费者程序,在分布式消息队列中,订阅用户注册主题就可以了,不需要修改原来任何一行代码。

在这里插入图片描述
这种解耦的特点对于团队的工作分工也很有帮助!从消息生产者的视角看,它只需要构建消息,将消息放入消息队列中,开发就完成了。而从消费者的开发视角看,它只需要从消息队列中获取消息,然后进行逻辑处理。它们彼此之间不进行任何耦合。消息的生产者不关心放入消息队列中下一步会发生什么,而消费者也不需要知道消息从哪里来。这两部分程序的开发者也可以不关心彼此的工作进展,他们开发的代码也不需要集成在一起,只要约定好消息格式,就可以各自开发了。

===================================================
参考博客:

  • https://segmentfault.com/a/1190000019411260
  • https://blog.csdn.net/nsxqf/article/details/80600330
  • https://blog.csdn.net/weixin_33859665/article/details/87956905

猜你喜欢

转载自blog.csdn.net/qq_38941937/article/details/113995431