“消息队列”是在消息的传输过程中保存消息的容器。消息队列管理器在将消息从它的源中继到它的目标时充当中间人。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。

一.消息队列是什么

维基百科的解释:

1
消息队列(英语:Message queue)是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自用户。消息队列提供了异步的通信协议,每一个贮列中的纪录包含详细说明的数据,包含发生的时间,输入设备的种类,以及特定的输入参数,也就是说:消息的发送者和接收者不需要同时与消息队列互交。消息会保存在队列中,直到接收者取回它。 ——维基百科

解释比较官方,来看一个最简单的架构模型:

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

消息队列是分布式系统中重要组件,使用消息队列主要是通过异步处理提高系统性能和削峰、降低系统耦合性。

下面用一个故事来理解消息队列:

小Y是攻城狮,有一天产品经理告诉他需要实现这样一个需求:”用户下单成功,给用户发送一封确认邮件”.小Y快速确认了需求,很简单嘛,加几行代码就搞定!如是在下单后调用发送邮件代码,经过本地和alpha测试,功能顺利上线。

正常运行了几天,产品经理找到小Y说观察监控数据,发现下单处理时间过长影响了转化率,小Y思考了一下,原来发邮件代码是放在下单完成代码后单线程同步阻塞的方式执行,确实有问题,于是新起了一个线程发邮件,测试通过后立即上线,下单感觉确实更顺畅了。

可是没过多久客服收到很多用户反馈没收到邮件,产品经理和小Y一起熬夜分析想办法,最后找到负责开发邮件模块的同事,负责邮件的同事说:”不用搞那么复杂,我给你提供一个类似邮局信箱的东西,你往这信箱里写上你要发送的消息,以及我们约定的地址。之后你就不用再操心了,我们自然能从约定的地址中取得消息,进行邮件的相应操作”。小Y快速按照邮件同事的建议修改了代码,并测试重新上线。

小Y后来才知道这个就是消息队列,你只需要将你想发送的消息发送到队列里,不用知道具体服务在哪,怎么执行,对应的服务自然能监听到你的发送的消息并获取过来执行。

后来其他业务也把邮件发送更换成这种方式,随着消息量增多,大量消息堆积,需要增加更多消费者来处理队列里的消息,于是便有了分布式消息处理

二.为什么要用消息队列

当系统出现”生产”和”消费”的速度和稳定性不一致时,就需要消息队列,正是这个中间层弥合双方的差异。使用消息队列主要三个好处(六字):解耦、异步、削峰。

1.解耦

场景:A更新数据,通知B,C更新,D等待接入。

传统架构:

传统架构下,A更新数据后分别调用B,C系统API,将更新的数据传过去,这种结构有下面的问题:(1)耦合性太强 (2)B,C系统故障,无法正确接收更新数据 (3)如果有D再接入,A又需要配合修改代码

引入消息队列中间件架构:

引入消息队列中间件的架构后,如果A更新了数据,只需要将消息发布到消息队列,B,C只要从消息队列订阅这条消息,D接入也只需自己去订阅,完全无需A做任何改动。

消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响。

2.异步

同步模式下,整个任务完成要耗时120s

消息队列异步模式下耗时35s,B,C异步运行。
异步处理主要目的是减少请求响应时间,实现非核心流程异步化,提高系统响应性能。

3.削峰

场景1:A系统有10000条数据更新,需要发送给B,C系统

传统架构下,A系统调用B,C系统的接口发消息过去,B,C系统处理能力A是无法预估的,可能B,C系统流量洪峰,最后导致系统崩溃。

消息队列架构下,系统BCD可以按照自己处理能力消费队列里的消息,在流量高峰期短暂积压是被允许的。

场景2:每天0:00到12:00,A系统风平浪静,每秒并发请求数量50个.结果每次一到12:00~13:00,每秒并发请求数量突然会暴增到5k+条,A系统是直连mysql,假设mysql最大并发处理能力每秒2k,很明显这个时候mysql会被打死,导致系统崩溃,停止服务。但一过高峰期每秒请求量可能就50个,对系统毫无压力。如果使用消息队列,高峰期5k+每秒的请求先写入到消息队列,A系统每秒最多处理2k个请求,不超过自己最大处理能力,这样高峰期不会崩溃,由于这时消息数入大于出,就形成了高峰期短暂积压(百万消息堆积),高峰期过后,每秒就50个请求,A系统还是按照每秒2k处理,多出来的能力可以快速处理高峰期积压。

三.使用消息队列带来的问题

考虑在项目引入一项新技术,除了知道这项技术的优点,更重要是对弊端的了解,如果都没考虑过,就把技术引入,就给项目带来了潜在风险。上面我们列了消息队列的有点,来一起看看它的缺点。

1.降低系统可用性

系统可用性在某种程度上降低,在加入MQ之前,不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后就需要去考虑.

2.增加系统复杂度

原来两个系统简单直接调API相互通讯,加入MQ后,消息传递链路加长,延时会增加,要考虑消息传递的可靠性(消息丢失怎么处理?),消息不被重复消费,数据一致性等问题。

四.什么时候用消息队列

1. 数据驱动的任务依赖

什么是任务依赖?举个例子:电商公司需要跑拣货单,在这之前可能需要同步订单,更新库存等,这些任务之间就存在一定依赖关系。下面是一个任务依赖示范图:

(1) Task3需要Task2输出作为输入
(2) Task2需要Task1输出作为输入

Task1,Task2,Task3就是任务依赖关系,必须先按照顺序Task1->Task2->Task3执行。类似的需求很多时候我们都是用cron人工排时间实现(预估每个task执行时间,然后留合适的间隔时间buffer):
(1) Task1 00:00执行,执行50分钟,Task1的buffer时间10分钟
(2) Task2 01:00执行,执行40分钟,Task2的buffer时间20分钟
(3) Task3 02:00执行,执行时间30分钟,依次类推

这种方式可能遇到的问题:

(1) 如果一个Task执行超出预期时间(例如随着样本数据增加,需要更长时间计算),后面的Task不清楚前面是否成功,此时需要手动重跑或调整时间安排。
(2) 需要预留很多buffer,总任务执行时间很长,后置任务不能再前置任务完成立即执行
(3) 如果一个任务被多个任务依赖,定时任务提现不出来,被改动后,依赖的任务容易出错
(4) 一个任务的时间调整,将有多个任务执行时间调整

加入MQ优化后:

(1)task1执行完成,发送消息X1到MQ,不需要预留buffer时间,上游执行完,立即发起通知
(2)task2订阅消息X1,收到消息后第一时间启动执行,执行完成发送消息X2,多个任务依赖只需要订阅相关消息即可,不关心依赖任务执行时间。
(3)task3以此类推。

注意MQ只用来传递上下游执行完成的消息,不传递真正的输入输出数据。

2. 上游不关心下游执行结果

上游任务完成不关心下游执行结果,例如下完订单给用户发送邮件,下单和发送邮件是两个任务,下完单后发消息到MQ,发送邮件任务收到消息开始发送邮件,至于发送成功还是失败,下单这个任务已经不关心。

3. 异步返回执行时间长

任务A执行时间长,完成后发消息给MQ,消息订阅方收到结果。例如微信付款流程,用户发起付款调用微信付款服务,是否是立即付款不确定,但发起付款方很在乎支付结果,这时可以让微信服务回调接口将支付结果发到MQ,发起方订阅MQ消息,这样微信付款完成发起方立即就收到结果通知。

五.如何选择消息队列

上面从优缺点,使用场景方面介绍了消息队列,我们可以看到消息队列是一个复杂的架构,引入它有很多好处,但也得针对它带来的问题做各种额外技术方案和架构来规避。市面上消息队列款式比较多,那么怎么选择呢?我找了目前使用较多的几个消息队列做对比。

任何脱离业务实际的技术选型就是耍流氓,可根据团队技术栈,业务规模做出合适选择。对于中小型互联网公司,RabbitMQ是不错的选择,管理简单,社区活跃,文档多,兼容语言多。kafaka更多是给大数据领域准备。