十二、消息队列-MQ
文章目录
- 前言
- 一、MQ介绍
- 1. 背景
- 2. 解决思路
- 3. 解决方案
- 二、应用场景
- 三、常见的MQ产品
- 四、MQ选型总结
- 五、相关知识
- 1. AMQP
- 2. JMS
- 五、如何设计实现一个消息队列
- 1. 设计消息队列的思路
- 2. 实现队列基本功能
- 3. 队列高级特性设计
前言
消息队列
传统应用系统出现同步耗时、系统耦合、高并发等痛点问题,支撑业务逐渐吃力。
通过引入MQ(Message Queue)消息队列机制,实现异步调用,系统解耦,流量削峰的方式,解决传统应用系统的痛点问题。
作为分布式系统中重要的组件,MQ本质是创建和维护应用程序间消息传输的通道。实现高性能,高可用,可伸缩和最终一致性架构。
一、MQ介绍
1. 背景
传统应用系统出现以下痛点问题,支撑业务逐渐吃力。
- 痛点1-同步耗时
有些复杂的业务系统,一次用户请求可能会同步调用N个系统的接口,需要等待所有的接口都返回了,才能真正的获取执行结果。
这种同步接口调用的方式总耗时比较长,非常影响用户的体验,特别是在网络不稳定的情况下,极容易出现接口超时问题。
- 痛点2-系统耦合
很多复杂的业务系统,一般都会拆分成多个子系统。以用户下单为例,请求会先通过订单系统,然后分别调用:支付系统、库存系统、积分系统 和 物流系统。
系统之间耦合性太高,如果调用的任何一个子系统出现异常,整个请求都会异常,对系统的稳定性非常不利。
- 痛点3-高并发
有时候为了吸引用户,会搞一些促销活动,比如秒杀。
如果用户操作突增,一时间所有的请求都到数据库,可能会导致数据库无法承受这么大的压力,响应变慢或者直接挂掉。
对于这种突然出现的请求峰值,无法保证系统的稳定性。
2. 解决思路
通过引入MQ(Message Queue)消息队列机制,实现异步调用,系统解耦,流量削峰的方式,解决传统应用系统的痛点问题。
- 方法1-异步调用
主业务执行结束后从属业务通过MQ,异步执行,减低业务的响应时间,提高用户体验。
2. 方法2-系统解耦
主业务完成以后,发送一条MQ,其余模块异步消费MQ消息,既可实现业务,又降低模块之间的耦合。
3. 方法3-流量削峰
高并发情况下,业务异步处理,提供高峰期业务处理能力,避免系统瘫痪。
3. 解决方案
MQ(Message Queue)是一种跨进程的通信机制,用于上下游传递消息。作为分布式系统中重要的组件,MQ本质是创建和维护应用程序间消息传输的通道,实现高性能,高可用,可伸缩和最终一致性架构。
MQ早已成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能,成为异步RPC的主要手段之一。
当今市面上有很多主流的MQ,如老牌的ActiveMQ、RabbitMQ,炙手可热的Kafka,阿里巴巴自主开发RocketMQ等。
二、应用场景
- 异步处理
不用MQ,那么我们的代码必然耦合在一起,下单成功后,依次要通过RPC远程调用这几个系统,然后同步等到他们的响应才能返回给用户是否成功的结果。假设每个系统耗时200ms,那么就得花费600ms。
2. 应用解耦
我购买车票成功后,会收到信息提醒,但是如果短信系统故障了,客户就有可能收到不短信了,这就是各个系统之间的耦合太高了,我们应该解耦。传统的做法如下:
我们在订单系统产生数据后,将订单这条数据发送给MQ,就返回成功,然后让短信、邮件等系统都订阅MQ,一旦发现MQ有消息,他们主动拉取消息,然后解析,进行业务处理。
这样一来,就算你短信系统挂了,丝毫不会影响其他系统,而且如果后来想加一个新的系统,你也不用改订单系统的代码了,你只要订阅我们的MQ提供的消息就行了。
应用解耦是消息队列 MQ 的主要特点,主要目的是减少请求响应时间和解耦。主要的使用场景就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同时,由于使用了消息队列MQ,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦合。
- 流量削峰
流量削峰也是消息队列 MQ 的常用场景,一般在秒杀或团队抢购(高并发)活动中使用广泛。
以12306为例,假设平时可能买票的人不多,所以订单系统的QPS( 每秒查询率 )也不是很高,每秒也就处理1000个请求,但是一到节假日、春运期间可能抢票的人就非常多,并发量远远大于平时,这个时候,订单系统明显扛不住了。
为解决这些问题,可以设计高可用的MQ,让所有的请求都到MQ,缓存起来。这样一来高峰期的流量和数据都将积压在MQ中,流量高峰就被削弱了(削峰),然后我们的订单系统就避免了高并发的请求,它可以慢慢的从MQ中拉取自己能力范围内的消息就行处理。这样一来,高峰期积压的消息也终将被消费完,可以叫做填谷。
三、常见的MQ产品
目前业界有很多MQ产品,比较出名的有下面这些:
-
ActiveMQ
历史悠久的Apache开源项目。已经在很多产品中得到应用,实现了JMS1.1规范,可以和springjms轻松融合,实现了多种协议,支持持久化到数据库,对队列数较多的情况支持不好。 -
RabbitMQ
使用erlang语言开发,性能较好,适合于企业级的开发。但是不利于做二次开发和维护。 -
RocketMQ
阿里巴巴的MQ中间件,由java语言开发,性能非常好,能够撑住双十一的大流量,而且使用起来很简单。 -
Kafka
Kafka是Apache下的一个子项目,是一个高性能跨语言分布式Publish/Subscribe消息队列系统,相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统。 -
ZeroMQ
号称最快的消息队列系统,尤其针对大吞吐量的需求场景。扩展性好,开发比较灵活,采用C语言实现,实际上只是一个socket库的重新封装,如果做为消息队列使用,需要开发大量的代码。
ZeroMQ仅提供非持久性的队列,也就是说如果down机,数据将会丢失。
四、MQ选型总结
需要根据具体的应用场景和需求的多维度来决定:
性能:吞吐量、并发时效性
可靠性:可用性、集群支持、持久化、性能稳定性、安全性
可维护性:管理界面等运维能力
易用性:平台熟悉度
兼容可扩展性:易于扩展
服务支持:社区活跃度
-
ActiveMQ
作为老牌的消息队列,吞吐量比较低,也缺少大规模吞吐量场景的验证、社区活跃度也很低,数据持久化的支持一般,目前渐渐被淘汰,已经不是主流了,不太建议选择了。 -
RabbitMQ和RocketMQ
社区比较活跃,吞吐量比较高,支持AMQP,稳定性也比较好,如果你的场景是应用需要可靠性消息传递和较高的并发,那么这两者是比较好的选择。
要注意,rabbitMQ是使用Erlang语言开发的,而RocketMQ则使用Java语言开发,所以如果是需要深度研究掌握的话,要考虑团队中是否有Erlang工程师,如果不具备相关的人才储备的话,更建议选择RocketMQ。当然,如果只是小团队简单使用,则rabbitMQ是一个挺好的选择。 -
Kafka和Pulsar
如果是大数据领域的实时计算、日志采集等场景,那么这两者是比较好的选择。
Kafka经历了超大规模应用的验证,社区活跃度很高,性能也非常高,几乎是全世界这个领域的事实性的标准。
Pulsar作为新兴的分布式消息传递系统,可扩展性强、性能高、社区活跃度也很高,最重要的是支持存储和计算分离,这在云原生下是非常出色的一项能力,并且天然支持跨数据中心的容灾,目前的应用也越来越广泛,如果集群对于持久化要求高,数据级别是超大规模,对于机器成本敏感,且支持多数据中心容灾,则建议选择Pulsar。 -
RabbitMQ:当需要性能稳定、低延时、功能强大且易于管理的方案,建议使用 RabbitMQ。
-
RocketMQ:当需要低延迟和金融级别的稳定性,且吞吐量需求较大的消息队列,系统主要场景是处理在线业务,比如在交易系统中传递订单,那 RocketMQ 是最适合的方案。
-
Kafka:对消息吞吐量需求很大,且不在乎消息偶尔丢失的情况,像收集日志、监控信息或是前端埋点这类海量数据,或是应用场景大量使用了大数据、流计算相关的开源产品,那 Kafka 是最适合的消息队列。
比较项 | ActiveMQ | RabbitMQ | RocketMQ | Kafka | Pulsar |
---|---|---|---|---|---|
单机吞吐量 | 较低(万级) | 一般(万级) | 高(十万级) | 高(十万级) | 高(十万级) |
时效性 | ms级 | us级 | ms级 | ms级以内 | - |
可用性 | 高(主从架构) | 高(主从架构) | 非常高(分布式架构) | 非常高(分布式架构) | - |
持久化 | 支持(小) | 支持(小) | 支持(大) | 支持(大) | 支持(大) |
顺序消息 | 不支持 | 不支持 | 支持 | 支持 | 支持 |
性能稳定性 | 好 | 好 | 一般 | 较差 | 一般 |
集群支持 | 主备模式 | 镜像模式(复制) | 主备模式 | Leader-Slave每台既是master也是slave,集群可扩展性强 | 集群模式,broker无状态,易迁移,支持跨数据中心 |
消费模式 | P2P、Pub-Sub | direct、fanout、topic、Headers | 基于Topic和MessageTag的的Pub-Sub | 基于Topic的Pub-Sub | 基于Topic的Pub-Sub |
管理界面 | 一般 | 较好 | 一般 | 无 | 无 |
计算和存储分离 | 不支持 | 不支持 | 不支持 | 不支持 | 支持 |
AMQP支持 | 支持 | 支持 | 支持 | 不完全支持 | 不完全支持 |
开发语言 | Java | Erlang | Java | Java/Scala | Java |
维护者 | Apache | Spring | Apache(Alibaba) | Apache(Confluent) | Apache(StreamNative) |
Star数量 | 2.1K | 10.4K | 18.8K | 24.3K | 12.4K |
Contributor | 126 | 246 | 438 | 991 | 600 |
社区活跃度 | 低 | 高 | 较高 | 高 | 高 |
功能特性 | 成熟的产品,有较多的文档;各种协议支持较好 | 并发能力很强,性能很好,延时很低;管理界面丰富 | MQ功能比较完毕,扩展性佳 | 支持主要的MQ功能,在大数据领域应用广泛 | 可扩展性强、性能高、云原生下非常出色,支持存储和计算分离,跨数据中心的容灾 |
五、相关知识
1. AMQP
**高级消息队列协议(Advanced Message Queuing Protocol)**是一个网络协议。它支持符合要求的客户端应用(application)和消息中间件代理(messaging middleware broker)之间进行通信。主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP是协议,类比HTTP。
2. JMS
Java消息服务(JavaMessage Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的 API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。JMS是API接口规范,类比JDBC。
五、如何设计实现一个消息队列
1. 设计消息队列的思路
- 一次RPC做成两次RPC
基于消息的系统模型,不一定需要broker(消息队列服务端)。
而之所以要设计一个消息队列,并且配备broker,无外乎要做两件事情:
-
消息的转储,在更合适的时间点投递,或者通过一系列手段辅助消息最终能送达消费机。
-
规范一种范式和通用的模式,以满足解耦、最终一致性、错峰等需求。
掰开了揉碎了看,最简单的消息队列可以做成一个消息转发器,把一次RPC做成两次RPC。发送者把消息投递到服务端(简称broker),服务端再将消息转发一手到接收端,就是这么简单。
- 整体设计思路如下
两次RPC加一次转储,外加消费确认的第三次RPC
-
数据流
build一个整体的数据流,例如producer发送给broker,broker发送给consumer,consumer回复消费确认,broker删除/备份消息等。 -
RPC
利用RPC将数据流串起来。考虑RPC的高可用性,尽量做到无状态,方便水平扩展。 -
内容转储
考虑如何承载消息堆积,在合适的时机投递消息。而处理堆积的最佳方式,就是存储,存储的选型需要综合考虑性能/可靠性和开发维护成本等诸多因素。 -
消费关系
而为了实现广播等功能,必须要维护消费关系,可以利用zk/config server等保存消费关系。
- 在完成了上述几个功能后,消息队列基本就实现了。然后我们可以考虑一些高级特性,如可靠投递,事务特性,性能优化等。
2. 实现队列基本功能
下面以设计消息队列时重点考虑的模块为主线,穿插灌输一些消息队列的特性实现方法,来具体分析设计实现一个消息队列时的方方面面。
- RPC通信协议
所谓消息队列,无外乎两次RPC加一次转储,当然需要消费端最终做消费确认的情况是三次RPC。既然是RPC,需要考虑,负载均衡、服务发现、通信协议、序列化协议等等。
因为消息队列的RPC,和普通的RPC没有本质区别。利用公司现有的RPC框架:Thrift也好,Dubbo也好,或者是其他自定义的框架也好。
简单来讲,服务端提供两个RPC服务,一个用来接收消息,一个用来确认消息收到。并且做到不管哪个server收到消息和确认消息,结果一致即可。
- 高可用
依赖于RPC和存储的高可用来做的。
- 服务端承载消息堆积的能力
为了满足我们错峰/流控/最终可达等一系列需求,把消息存储下来,然后选择时机投递就显得是顺理成章的了。
存储可以做成很多方式。比如存储在内存里,存储在分布式KV里,存储在磁盘里,存储在数据库里等等。但归结起来,主要有持久化和非持久化两种。
持久化的形式能更大程度地保证消息的可靠性(如断电等不可抗外力),并且理论上能承载更大限度的消息堆积(外存的空间远大于内存)。
但并不是每种消息都需要持久化存储。很多消息对于投递性能的要求大于可靠性的要求,且数量极大(如日志)。这时候,消息不落地直接暂存内存,尝试几次failover,最终投递出去。
- 存储子系统的选择
理论上,从速度来看,文件系统 > 分布式KV(持久化) > 分布式文件系统 > 数据库,而可靠性却截然相反。
还是要从支持的业务场景出发作出最合理的选择,如果你们的消息队列是用来支持支付/交易等对可靠性要求非常高,但对性能和量的要求没有这么高,而且没有时间精力专门做文件存储系统的研究,DB是最好的选择。
分布式KV(如MongoDB,HBase)等,或者持久化的Redis,由于其编程接口较友好,性能也比较可观,如果在可靠性要求不是那么高的场景,也不失为一个不错的选择。
- 消费关系解析
消息队列定义了一堆名词,如JMS 规范中的Topic/Queue,Kafka里面的Topic/Partition/ConsumerGroup,RabbitMQ里面的Exchange等等。
抛开现象看本质,无外乎是单播与广播的区别。所谓单播,就是点到点;而广播,是一点对多点。当然,对于互联网的大部分应用来说,组间广播、组内单播是最常见的情形。
至于广播关系的维护,一般由于消息队列本身都是集群,所以都维护在公共存储上,如config server、zookeeper等。维护广播关系所要做的事情基本是一致的:
-
发送关系的维护。
-
发送关系变更时的通知。
3. 队列高级特性设计
- 可靠投递(最终一致性)
-
消费确认
-
重复消息和顺序消息
-
版本号
-
状态机
-
中间件对于重复消息的处理
-
事务
-
性能相关
-
异步/同步
-
批量
- push 还是 pull,模型简要分析
-
慢消费
-
消息延迟与忙等
-
顺序消息