消息队列(Message Queue,简称MQ),其主要用于在复杂的微服务系统中进行消息通讯,它的优势能够大体整理成如下几点:html
微服务系统业务之间相互依赖,各类调用错综复杂,若是不能良好对服务进行解耦那一个服务的可用性、并发都会受到其余服务的影响。数据库
在没有引用MQ的以前服务调用大概是这些步骤:架构
图上的A服务是直接调用的,这是没啥问题的,可是服务上线后要迭代更新的麻,这个时候要是服务C的开发人员有点代码小洁癖说:我这个C服务接口命名不太好,我须要从新更新下,当A服务的小哥哥还戴着小耳机听着小歌曲,忽然就得改代码了~~。并发
后来负责服务C的那小哥哥也很差意思了,提出你们一块儿使用MQ吧,因而A、C的调用就变成下面这个样子了:机器学习
服务A不直接调用C而是向消息队列中发送消息(生产者),另外一边的C取出队列中的消息(消费者)进行处理,这样A、C就完成了解耦。微服务
举个例子,在没引入MQ以前服务调用多个服务都是同步调用,好比像这样:性能
服务A要顺序的调用B、C服务来完成业务逻辑若是A->B
须要200ms,A->C
须要200ms,再加上自身业务逻辑处理可能须要花费500ms,其中有400ms是调用A和B的花费,明明自身100ms就能处理完还白白浪费400ms,不能忍啊因而能够引入MQ作一下改造:学习
这下有了MQ,A服务只须要发一条消息好比花费50ms,再加上自身业务逻辑的100ms,那整个调用过程只须要花费150ms了,这样对并发和性能都有必定的改善。大数据
突发流量就是互联网很常见的状况,有时候有热点、突发事件,那日常QPS为100的接口,忽然提高10-20倍这个时候没有MQ全部流量直接进入服务,这对服务和数据库都是很大的挑战:日志
再次引入MQ就状况就不同了,服务A先将请求丢给MQ,而后能够慢慢消费掉:
使用MQ还有不少好处,可是他也会带一些麻烦事。首先就是会下降系统的可用性,好比MQ挂了怎么办呢?因此在引入MQ以前就须要考虑以后带来的哪些问题,不能只看它的好处也须要考虑它很差的地方。好比下面列出的这些问题要若是解决:
下面咱们来分析下这些问题。
若是是单机消息队列,一台机器挂了消息队列都就不用了,这是不能接受的,若是是一个消息队列群集,一台机器挂了还有其余机器能正常提供服务,因此要保证消息队列的高可用,咱们就须要作消息队列集群。
以RabbitMQ为例它有两种集群模式:
普通模式,RabbitMQ会同步各个节点的数据/状态,但不包括消息队列
,默认状况下,消息队列驻留在一个节点上,尽管它们在全部节点上都是可见且可访问的。
在这种模式下,每一个节点都有会全部节点的元数据信息,因此当发送消息到队列时,不管链接的是哪个节点都能正确的发送,可是节点只会同步其余节点的元数据,消息队列的数据仍是在一个节点上,若是这个节点挂了那就意味着发消息就会失败,没法保证消息队列的高可用。
默认状况下,RabbitMQ中Queue与Binding、Exchange不同,它只会存于声明队列的节点中,可是能够选择使Queue跨多个节点进行镜像。
每个镜像队列由一个Master和一个或多个镜像组成,任何队列的的操做,都会先应用到Master节点上而后传播到多个镜像节点。若是Master节点挂了,最老的镜像节点将会成为新的Master节点。
RabbitMQ有两种集群方法:普通模式
、镜像模式
,要实现消息队列的高可用能够选一种合适的集群方式来达到,关于RabbitMQ的集群搭建方式,因为篇幅有限这里就很少说,可自行查看 Distributed RabbitMQ文章。
想象下消费者收到重复的消息会发生什么状况,好比订单支付消息,若是支付服务收到两条重复的消息让用户去支付两次,那用户确定是不肯意的,明明已经支付过了还要支付。
如上图中第四步消费消息B的时候失败了,若是支付服务在作完业务以后,发送ACK以前服务挂了,MQ没有收到ACK,因为消息还存在队列中,服务恢复正常后会再次收到消息,若是支付不作检查那用户就会发生两次支付。
要避免这个重复消费的问题,能够在消费端引入内存、Redis、数据库来保存消息消费记录,根据消息Id来判断消息是否已经被消费过。
假设有订单服务和支付服务,正常流程是用户下单成功,而后向支付服务发送支付消息,这里面就涉及订单服务、支付服务、MQ的交互了,消息丢失能够分为三种状况:
生产者消息丢失,可使用本地消息表解决、消息确认/重发等方式来解决。以RabbitMQ为例,它有confirm
机制,发出去的消息是否入队列,会使用回调的形式告知生产者,生产者收到消息后判断是Ack
仍是Nak
,若是是Nak
则重发消息。
此时还会有问题,若是极端状况下订单服务挂了,再次重启后消息就真丢失了,因此最好仍是在生产中对消息作持久化,待订单服务恢复后使用Job从新发送消息。
MQ消息丢失通常为未开启持久化,MQ挂了再次重启后消息丢失,因此应当将消息持久化到磁盘中。若是MQ收到消息后在同步到磁盘以前MQ挂了,那磁盘中也没有消息,这样仍是会致使消息丢失消息,不过这只是小几率事件。
消费者消息丢失,大都为开启了autoAck
选项,消费者收到消息后还未完成处理,此时服务挂了,因为开启了autoAck
, MQ会觉得此消息已经被成功消费,将消息从队列中移除,而服务恢复事后也不会收到原来的消息了。
有些场景下要保持消息的顺序消费怎么办?好比写Log都是一条条打印出来,若是发到消息队列后出现消费顺序不一致那消息的那日志就会乱掉,给看日志的人带来没必要要的麻烦。好比为了加快日志的处理速度使用三个消费都处理日志:
按图上的流程,消费者A、B、C可能分别消费日志一、二、3,这时候就没法保证消息的处理顺序。要保证消息的消费顺序,首先让消息都发送到同一个队列,而后使用一个消费者去处理消息:
这样消息的处理速度就大大下降,要保持消息的顺序,则又想让消息的处理速度不至于太慢,能够引用本地队列:
《架构文摘》天天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性 能、高稳定)、大数据、机器学习等各个热门领域。