zoukankan      html  css  js  c++  java
  • 初识MQ

    MQ

    作为金融企业对公众提供服务一定要保证其可用性,尽量做到更多个9(一般SLA中描述的高可用性99.99%,中的9越多代表全年服务可用时间越长服务更可靠,停机时间越短),随着软件系统的复杂度越来越高,故障是不可避免的。这就需要企业实现整体的弹性架构(Resilient Architecture),为应对故障而设计。

    因为是同步请求,常因为执行方失败、超时等因素而影响最终用户体验,而且很多故障是无法彻底消除的。而且RPC和RMI调用需要服务的消费方和服务的提供方同时在线,并且双方需要通过某种机制确认彼此的调用关系,因为存在这些弊端,就导致了面向消息的中间件(MOM)的产生,通过在企业架构中引入消息中间件,确保在故障发生时,受此影响的系统部分在一个很小的范围内。消息中间件就是在分布式系统中间引入一个透明的中间层,隔离服务的提供方和消费方。

    2 为什么要引入MQ

    2.1何为MQ

    消息队列(MQ)是一种不同应用程序之间(跨进程)的通信方法,用于上下游应用程序之间传递消息。我们拆分来看:

    消息:应用程序通过写入和检索出入列队的数据(消息)来通信。

    队列:除去了接收和发送应用程序同时执行的要求。

    这样就实现了上游与下游之间的解耦,上游向MQ发送消息,下游从MQ接收消息,上游下游互不依赖,它们只依赖MQ。因为有队列的存在,MQ可在上下游之间进行缓冲,把上游信息先缓存起来,下游根据自己的能力从MQ中拉去信息,起到削峰的作用。

    2.2 使用MQ带来的好处

    2.2.1 解耦

     什么是耦合

    高内聚低耦合,是软件工程中的概念,这里的低耦合是指各个组件之间,尽可能相互独立。通俗一点的理解就是,增加模块间调用透明化,最高的透明度就是不用知道彼此的存在,因此减少接口的复杂性、规范调用的方式及传递的信息,降低产品各模块的依赖,提高重用程度。

    如何解耦

    在企业整体架构中解耦,主要设计两个方面:一是简化减少交互,二是增加一个中间层实现两方的隔离,MQ就是其中的中间层(如下图所示)。

     

    引入MQ后生产者和消费者不必知道彼此的存在也不必同时在线,主要交互流程如下:

    1) 生产者负责:生产消息并通过SDK或API调用发送给MQ(同步发送或者异步发送);

    2) MQ负责:接收消息,并持久化消息到消息存储(同步或异步存储消息);

    3) 生产者接收来自MQ的响应(消息发送状态或异常);

    4) 消费者订阅消息后,接收来自MQ的消息;

    5) 消费者执行消息对应的后续业务操作;

    6) 对消费结果进行确认(消费成功、失败、异常等)。

    2.2.2 削峰填谷

    由于系统闲忙分布不均,QPS常相差几十倍甚至更高,特别是在遇到营销活动时,瞬间流量很可能超过后端系统的承载能力,这就要考虑通过消息中间件来缓冲,MQ客户端实例根据自己的处理能力从MQ服务器拉取消息,以此来减轻或消除后端系统的瓶颈。

     

    2.2.3 异构集成

    由于各种原因,我们在企业信息化建设过程中,都会面临软件产品来自不同的厂家只解决某特定领域的问题,这些产品因为封闭的架构无法对外提供服务或缺少核心开发而无法做大的改造,这就造成了彼此之间很难集成。通过引入MQ可以部分解决该问题,只需要在某个环节生产一条消息,或者根据消息做出具体的响应,只需与MQ对接,不必与其他系统做一对一的对接。

    2.2.4异步隔离

    为了提供金融服务的整体弹性,需要隔离内部、外部系统间的依赖。如支付通知分为两种,一种是同步通知,这时API调用会因为网络故障而超时,因为服务提供方处理能力限制而得不到及时响应……等多种因素影响,另一种是异步通知,在一定时效范围内最终通知到即可,从而提供提高最终用户的体验和交易成功率,提高业务的整体生产率。

    3.常见产品特性比较

    4.MQ的两种模式:点对点、发布/订阅

    点对点模式:Queue,不可重复消费

    生产者发送一条消息到queue,一个queue可以有很多消费者,但是一个消息只能被一个消费者接受,当没有消费者可用时,这个消息会被保存在queue中直到有一个可用的消费者;消息被消费以后,queue中不再有存储,所以消息消费者不可能消费到已经被消费的消息。所以Queue实现了一个可靠的负载均衡。

     

    发布/订阅模式:Topic,可以重复消费

    发布/订阅模式涉及三个角色:发布者、订阅者、消费者

    订阅者:是成为消费者的前提,订阅是向发布者说明自己是个消费者的过程。简单来说,发布者发送到topic的消息,只有订阅了topic的订阅者才能成为消费者,才会接收到消息。

    Topic: 发布/订阅模式中的topic实现了发布和订阅,当发布一个消息,所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到这个消息的拷贝。

    整个实现上分三个步骤:

    1、  初始化发布者、订阅者。

    2、  订阅者注册到发布者

    3、  发布者依次向订阅者发布消息。

    点对点模式和发布/订阅模式的结合:

    发布订阅模式下,当发布者的消息量很大时,单个订阅者的处理能力很难满足系统要求。在实际应用场景中,一般是由多个订阅者节点组成一个订阅组来消费topic信息,即分组订阅,每个消息只会由一个订阅组中的某一个订阅者来消费, 类似于点对点模式。这种模式下很容易就能实现对消费能力的线性扩展。

    四、MQ主要应用场景:

    主要针对以下特征可采用MQ

    1、解耦

    以贷款扣款为例: 订单系统 –> MQ -> 结算系统

    订单系统在生成用户订单后将消息抛送至MQ后立即返回,然后由结算系统去消费该MQ中的消息,进行用户账户金额的扣减。这样就将订单生成系统和结算系统就行了解耦,订单系统只需关注创建订单,无需等待后续流程的执行结果,这样可以减小订单系统的压力,最大可能的提高系统响应速度,进而提升订单量,而结算系统则只需关心账户金额扣减,保证账户金额的一致性即可。

    2、削峰填谷

    在每逢大促时,订单系统就会迎来数量高峰,在短时间内会生成大量的订单,这势必会给结算系统带来压力,在短时间内升高服务器的利用率,甚至满载,从而达到系统瓶颈。而在订单低谷期订单量比较小,结算系统服务器又会长时间处于空闲状态。

    此类问题可以通过MQ的方式来解决。订单系统将订单消息抛送至MQ队列后,结算系统会通过消费端拉取消息的方式来处理订单消息,拉取的速度可由结算系统根据当前自身服务器的情况进行控制,这样就可以避开由于大促带来的订单瞬时高峰,使结算流量趋于平稳,保证了结算系统的稳定性。

    3、最终一致性

    订单系统:创建成功订单、发送成功通知到MQ,两个操作可以看成一个事务。

    当一次发送MQ失败之后,可以结合定时任务进行补偿,这样可以保证生成订单的结果可以落地到mq的存储中。同样结算系统消费端依靠MQ重试机制一直发送消息,直到消费端最终确认扣款业务成功处理完成。这样我们通过MQ实现了最终一致性。

    五、优缺点

    MQ对应的优点是:

    1、高并发

    在高并发分布式环境下,由于来不及同步处理,请求往往发生堵塞,影响系统性能;而异步处理请求,可以缓解系统的压力。

    2、松耦合

    任何一个应用对MQ的调用不依赖于任何其它应用,没有任何依赖或者时序要求,应用依赖于MQ的能力保证消息传递。(应用发送消息到MQ之后并不关心消息如何或者什么时候被传递。同样的消息的接收者也不关心消息从哪里或者如何到来。在不同的环境中这样做的好处是允许客户端使用不同的语言编写甚至使用不同的线路协议。MQ作为中间人存在,允许不同环境的集成和异步交互。)

    MQ的不足是:

    1、多了一个MQ组件,使消息系统更加复杂 。

    2、消息传递路径更长,延时会增加 。

    3、消息可靠性和重复性互为矛盾,消息不丢不重难以同时保证。

    4、上游无法知道下游的执行结果。

    举个例子:用户登录场景,登录页面调用passport服务,passport服务的执行结果直接影响登录结果,此处的“登录页面”与“passport服务”就必须使用调用关系,而不能使用MQ通信。

    (无论如何,记住这个结论:调用方实时依赖执行结果的业务场景,请使用调用,而不是MQ。)

    6.问题总结

    1、消息乱序

    有些业务场景,比如购买流程,下单和扣款两个操作是有先后顺序的。

    Topic1: 队列1

                  队列2

                  队列3

                  ……

    Topic2:   队列1

                  队列2

                  ……

    生产者发出topic+消息体, 根据匹配topic可以判断出应该把消息丢到哪个队列里。每个队列对应唯一topic,同一topic可能对应多个队列。也就是同一种topic的消息可能进入到不同队列里,即便在同一个队列里的消息通常也是多线程处理的,所以无法保证消息的顺序。

    要实现严格的顺序消息,简单且可行的办法就是:保证生产者 - MQ - 消费者是一对一对一的关系。比如同一个topic下只有一个队列,且队列是单线程处理的。但存在问题:1、并行度不够,影响整个系统性能。2、发生异常时(如消费端出现问题),会导致整个处理流程阻塞,需要花费更多时间解决问题。

    PS:目前市场上很多应用都是不关心消息是否乱序的。而针对大部分业务场景也是不必要求消息顺序的。

    针对必须要保证顺序的业务,可以设置业务id,同一个业务的一组消息设置同一个id,丢入同一个队列中,并且单线程处理。

    2、消息重复

    由于网络延迟等容易引入消息重复的问题。现在通常的处理方法是不解决。但是不解决可能存在消费端收到两条一样的消息。解决这个问题,通常两种方法:

    A、幂等性

    幂等性:某个函数或者某个接口使用相同参数调用一次或者无限次,其造成的后果是一样的。

    比如在系统中,调用方A调用系统B的接口进行用户的扣费操作时,由于网络不稳定,A重试了N次该请求,那么不管B是否接收到多少次请求,都应该保证只会扣除该用户一次费用。

    例如:对于订单扣费行为,系统会生成唯一全局的一个流水号用来标识同一个业务流程,多个相同业务流程的流水号不重复。服务器1处理了流水号1的扣费行为后,再有流水号1的扣款消息到达其他服务器时,无论执行几次扣款行为,最终结果都是只扣款1次。

    B、日志表

    在后台维护一张表从而达到去重的目的。比如每条消息设置唯一的消息id,若发送成功则记录在日志表中,新消息到达时,查看消息是否已经在日志表中,若存在则不再处理。

    PS:MQ实现去重逻辑,一定程度上会影响消息系统的性能,所以目前很多去重逻辑是由上游业务方来保证。(MQ+上游双重保证)

    3、消息重试:生产者端重试、消费者端重试

    重点说下消费者端重试,消费结果两种:1、成功;2、失败重试。消费者必须返回其中一种结果。

    消费者端的失败,分为2种情况,一个是exception,一个是timeout。

    Exception: 消息正常的到了消费者,结果消费者发生异常,处理失败了。

    处理方法:mq把失败的消息丢到重试队列中,重试队列一般会有重试次数限制,若超过该次数仍未消费成功,一般会有两种处理:1、丢弃该消息。(常用)2、不再重试,且存储该消息,并使用另外一种方式消费。

    Timeout:比如由于网络原因导致消息没有从MQ到达消费者上。

    处理方法:丢到重试队列中不断的尝试发送这条消息,直至发送成功为止!(通常我们会设置重试上限)

  • 相关阅读:
    通过Asp.Net MVC的区域功能实现将多个MVC项目部署
    对初步创业的软件企业的思考
    白色恋人
    ASP.NET中动态控制RDLC报表
    什么时候该用委托,为什么要用委托,委托有什么好处....
    asp.net mvc 3
    Salesforce多租户架构
    很有用的系统命令和一些技巧
    产品设计
    RDLC报表部署及MVC部署 所需dll
  • 原文地址:https://www.cnblogs.com/feimaoyuzhubaobao/p/10151995.html
Copyright © 2011-2022 走看看