高并发架构系列:分布式之消息中间件的特点、选型、及应用场景详解

目录

什么是消息队列

为什么需要消息队列

消息队列的优点

消息队列的特点

消息队列的选型

消息队列的应用场景

消息队列使用总结


消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能,成为异步RPC的主要手段之一。

当今市面上有很多主流的消息中间件,如老牌的ActiveMQ、RabbitMQ,炙手可热的Kafka,阿里巴巴自主开发的Notify、MetaQ、RocketMQ等。

什么是消息队列


消息队列(Message Queue,简称MQ),指保存消息的一个容器,本质是个队列。

消息(Message)是指在应用之间传送的数据,消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象。

消息队列(Message Queue)是一种应用间的通信方式,消息发送后可以立即返回,有消息系统来确保信息的可靠专递,消息发布者只管把消息发布到MQ中而不管谁来取,消息使用者只管从MQ中取消息而不管谁发布的,这样发布者和使用者都不用知道对方的存在。

高并发架构系列:分布式之消息中间件的特点、选型、及应用场景详解_第1张图片

  •  Producer:消息生产者,负责产生和发送消息到 Broker;
  •  Broker:消息处理中心。负责消息存储、确认、重试等,一般其中会包含多个 queue;
  •  Consumer:消息消费者,负责从 Broker 中获取消息,并进行相应处理;

现在常用的MQ组件有ActiveMQ、RabbitMQ、RocketMQ、ZeroMQ,当然近年来火热的Kafka,从某些场景来说,也是MQ,当然kafka的功能更加强大。

虽然不同的MQ都有自己的特点和优势,但是,不管是哪种MQ,都有MQ本身自带的一些特点,下面,咱们谈谈消息队列的的特点、优势、选型、以及应用场景。

为什么需要消息队列


在高并发分布式环境下,由于来不及同步处理,通过使用消息队列,可以异步处理请求,从而缓解系统的压力。

举一个订单系统的例子:用户点击下订单,会触发以下业务逻辑流程:

  •  扣减库存
  •  生成相应的订单
  •  发短信通知等等

在业务发展初期这些逻辑可能放在一起同步执行,随着业务订单量增长,需要提升系统服务的性能,这时候可以将一些不需要立即生效的操作拆分出来异步执行,比如发短信通知等,这种场景就可以使用消息队列MQ。

本质还是通过异步来解决同步的系统压力,所以我们在做架构设计的时候有一个原则:能异步的就尽量不要同步。

消息队列的优点


  • 1、屏蔽异构平台的细节:发送方、接收方系统之间不需要了解双方,只需认识消息。
  • 2、异步:消息堆积能力;发送方接收方不需同时在线,发送方接收方不需同时扩容(削峰)。
  • 3、解耦:防止引入过多的API给系统的稳定性带来风险;调用方使用不当会给被调用方系统造成压力,被调用方处理不当会降低调用方系统的响应能力。
  • 4、复用:一次发送多次消费。
  • 5、可靠:一次保证消息的传递。如果发送消息时接收者不可用,消息队列会保留消息,直到成功地传递它。
  • 6、提供路由:发送者无需与接收者建立连接,双方通过消息队列保证消息能够从发送者路由到接收者,甚至对于本来网络不易互通的两个服务,也可以提供消息路由。

消息队列的特点


  • 1.异步性

将耗时的同步操作,通过以发送消息的方式,进行了异步化处理。减少了同步等待的时间。

  • 2.松耦合

消息队列减少了服务之间的耦合性,不同的服务可以通过消息队列进行通信,而不用关心彼此的实现细节,只要定义好消息的格式就行。

  • 3.分布式

通过对消费者的横向扩展,降低了消息队列阻塞的风险,以及单个消费者产生单点故障的可能性(当然消息队列本身也可以做成分布式集群)。

  • 4.可靠性

消息队列一般会把接收到的消息存储到本地硬盘上(当消息被处理完之后,存储信息根据不同的消息队列实现,有可能将其删除),这样即使应用挂掉或者消息队列本身挂掉,消息也能够重新加载。

消息队列的选型


高并发架构系列:分布式之消息中间件的特点、选型、及应用场景详解_第2张图片

  • 1.ActiveMQ

高并发架构系列:分布式之消息中间件的特点、选型、及应用场景详解_第3张图片

Apache出品,最早使用的消息队列产品,时间比较长了,最近版本更新比较缓慢。

  • 2.RabbitMQ

RabbitMQ是erlang语言开发,结合erlang语言本身的并发优势,支持很多的协议:AMQP,XMPP, SMTP, STOMP,也正是如此,使的它变的非常重量级,更适合于企业级的开发。性能较好,但是不利于做二次开发和维护。

  • 3.RocketMQ

高并发架构系列:分布式之消息中间件的特点、选型、及应用场景详解_第4张图片

阿里开源的消息中间件,纯Java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。

  • 4.ZeroMQ

高并发架构系列:分布式之消息中间件的特点、选型、及应用场景详解_第5张图片

号称最快的消息队列系统,尤其针对大吞吐量的需求场景。

扩展性好,开发比较灵活,采用C语言实现,实际上只是一个socket库的重新封装,如果做为消息队列使用,需要开发大量的代码。

  • 5.Kafka

高并发架构系列:分布式之消息中间件的特点、选型、及应用场景详解_第6张图片

Kafka是Apache下的一个子项目,是一个高性能跨语言分布式发布/订阅消息队列系统,而Jafka是在Kafka之上孵化而来的,即Kafka的一个升级版。

  • 6.消息队列的详细比较

 

高并发架构系列:分布式之消息中间件的特点、选型、及应用场景详解_第7张图片

  • 7.消息队列总结

消息队列的选型需要根据具体应用需求而定,ZeroMQ小而美,RabbitMQ大而稳,Kakfa和RocketMQ快而强劲。

消息队列的应用场景


高并发架构系列:分布式之消息中间件的特点、选型、及应用场景详解_第8张图片

  • 1.异步处理

消息队列的主要特点是异步处理,主要目的是减少请求响应时间,实现非核心流程异步化,提高系统响应性能。

所以典型的使用场景就是将比较耗时而且不需要即时(同步)返回结果的操作,作为消息放入消息队列。

  • 2.应用解耦

高并发架构系列:分布式之消息中间件的特点、选型、及应用场景详解_第9张图片

使用了消息队列后,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦。

解耦是消息队列要解决的最本质问题。所谓解耦,简单点讲就是一个事务,只关心核心的流程。而需要依赖其他系统但不那么重要的事情,有通知即可,无需等待结果。换句话说,基于消息的模型,关心的是“通知”,而非“处理”。

每个成员不必受其他成员影响,可以更独立自主,只通过消息队列MQ来联系。

举一个例子:用户下订单流程,下订单后会发生扣库存这个动作,上游系统订单下游系统扣库存,就可以通过上图的消息队列MQ来联系,扣库存异步化,从而实现订单系统与库存系统的应用解耦。

  • 3.流量削锋

流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。

应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。

试想上下游对于事情的处理能力是不同的。

比如,Web前端每秒承受上千万的请求,并不是什么神奇的事情,只需要加多一点机器,再搭建一些LVS负载均衡设备和Nginx等即可。

但数据库的处理能力却十分有限,即使使用SSD加分库分表,单机的处理能力仍然在万级。由于成本的考虑,我们不能奢求数据库的机器数量追上前端。

这种问题同样存在于系统和系统之间,如短信系统可能由于短板效应,速度卡在网关上(每秒几百次请求),跟前端的并发量不是一个数量级。

但用户晚上个半分钟左右收到短信,一般是不会有太大问题的。如果没有消息队列,两个系统之间通过协商、滑动窗口等复杂的方案也不是说不能实现。

但系统复杂性指数级增长,势必在上游或者下游做存储,并且要处理定时、拥塞等一系列问题。而且每当有处理能力有差距的时候,都需要单独开发一套逻辑来维护这套逻辑。所以,利用中间系统转储两个系统的通信内容,并在下游系统有能力处理这些消息的时候,再处理这些消息,是一套相对较通用的方式。

具体可以参考:阿里P8架构师谈:双11秒杀系统如何设计?

  • 4日志处理

日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题。

  • 5消息通讯

消息队列一般都内置了高效的通信机制,因此也可以用于单纯的消息通讯,比如实现点对点消息队列或者聊天室等。

  • 6最终一致性

最终一致性指的是两个系统的状态保持一致,要么都成功,要么都失败。

当然有个时间限制,理论上越快越好,但实际上在各种异常的情况下,可能会有一定延迟达到最终一致状态,但最后两个系统的状态是一样的。

业界有一些为“最终一致性”而生的消息队列,如:

  •  Notify(阿里)
  •  QMQ(去哪儿)等

其设计初衷,就是为了交易系统中的高可靠通知。

以一个银行的转账过程来理解最终一致性,转账的需求很简单,如果A系统扣钱成功,则B系统加钱一定成功。反之则一起回滚,像什么都没发生一样。

然而,这个过程中存在很多可能的意外:

  1.  A扣钱成功,调用B加钱接口失败。
  2.  A扣钱成功,调用B加钱接口虽然成功,但获取最终结果时网络异常引起超时。
  3.  A扣钱成功,B加钱失败,A想回滚扣的钱,但A机器down机。

可见,想把这件看似简单的事真正做成,真的不那么容易。

所有跨VM的一致性问题,从技术的角度讲通用的解决方案是:

  1.  强一致性,分布式事务,但落地太难且成本太高,后文会具体提到。
  2.  最终一致性,主要是用“记录”和“补偿”的方式。在做所有的不确定的事情之前,先把事情记录下来,然后去做不确定的事情,结果可能是:成功、失败或是不确定,“不确定”(例如超时等)可以等价为失败。成功就可以把记录的东西清理掉了,对于失败和不确定,可以依靠定时任务等方式把所有失败的事情重新搞一遍,直到成功为止。
  3.  回到刚才的例子,系统在A扣钱成功的情况下,把要给B“通知”这件事记录在库里(为了保证最高的可靠性可以把通知B系统加钱和扣钱成功这两件事维护在一个本地事务里),通知成功则删除这条记录,通知失败或不确定则依靠定时任务补偿性地通知我们,直到我们把状态更新成正确的为止。
  4.  整个这个模型依然可以基于RPC来做,但可以抽象成一个统一的模型,基于消息队列来做一个“企业总线”。
  5.  具体来说,本地事务维护业务变化和通知消息,一起落地(失败则一起回滚),然后RPC到达broker,在broker成功落地后,RPC返回成功,本地消息可以删除。否则本地消息一直靠定时任务轮询不断重发,这样就保证了消息可靠落地broker。
  6.  broker往consumer发送消息的过程类似,一直发送消息,直到consumer发送消费成功确认。
  7.  我们先不理会重复消息的问题,通过两次消息落地加补偿,下游是一定可以收到消息的。然后依赖状态机版本号等方式做判重,更新自己的业务,就实现了最终一致性。

最终一致性不是消息队列的必备特性,但确实可以依靠消息队列来做最终一致性的事情。

另外,所有不保证100%不丢消息的消息队列,理论上无法实现最终一致性。好吧,应该说理论上的100%,排除系统严重故障和bug。

像Kafka一类的设计,在设计层面上就有丢消息的可能(比如定时刷盘,如果掉电就会丢消息)。哪怕只丢千分之一的消息,业务也必须用其他的手段来保证结果正确。

  • 7广播

消息队列的基本功能之一是进行广播。

如果没有消息队列,每当一个新的业务方接入,我们都要联调一次新接口。有了消息队列,我们只需要关心消息是否送达了队列,至于谁希望订阅,是下游的事情,无疑极大地减少了开发和联调的工作量。

比如本文开始提到的产品中心发布产品变更的消息,以及景点库很多去重更新的消息,可能“关心”方有很多个,但产品中心和景点库只需要发布变更消息即可,谁关心谁接入。

消息队列使用总结


  1. 消息队列不是万能的,对于需要强事务保证而且延迟敏感的,RPC是优于消息队列的。
  2. 对于一些无关痛痒,或者对于别人非常重要但是对于自己不是那么关心的事情,可以利用消息队列去做。
  3. 支持最终一致性的消息队列,能够用来处理延迟不那么敏感的“分布式事务”场景,而且相对于笨重的分布式事务,可能是更优的处理方式。
  4. 当上下游系统处理能力存在差距的时候,利用消息队列做一个通用的“漏斗”,在下游有能力处理的时候,再进行分发。
  5. 如果下游有很多系统关心你的系统发出的通知的时候,果断地使用消息队列吧。

参考:https://youzhixueyuan.com/message-queue-mq-comparison-and-application-scenario.html 

你可能感兴趣的:(消息中间件)