目录
什么是消息队列
消息队列(Message Queue),从广义上来讲是一种消息队列服务的中间件,提供一套完整的信息生产、传递、消费的软件系统。
消息队列所涵盖的功能远不止于队列,其本质是两个进程传递信息的一种方法。两个进程可以分布在同一台机器上,亦可以分布在不同的机器上。
众所周知,进程可以通过 RPC(Remote Procedure Call,远程过程调用)进行,那么我们为什么要用消息队列这种软件服务来传递消息呢?
以 12306 手机 APP 为例,订票的操作流程为:
第一步:输入车票信息,起点站、终点站、座席等信息,发送订票请求;
第二步:提交订单,之后等待,此时 APP 界面会出现转圈,意味着你正在和其他人进行抢票;
第三步:3s 或更短时间内短信告知你是否购票成功。
那么 12306 在处理如上订票的过程中会遇到以下问题:
(1)如果今天这个车次只能出售 2000 张票,而实际却有超过 20 万人提交了购票信息,如果逐一请求处理,那么可能将有超过 90% 的人需要耗时大约 3s 来等待,这种情况怎么处理?
(2)如果下游有 20 个系统需要在订票成功之后进行通知,如果逐一调用这些接口进行通知,而如果其中一个系统的通知任务执行失败,那么通知成功的任务会怎样?
(3)12306 的架构会不断调整,当数据结构发生变化时,下游 20 个系统都要随着一起变化吗?
为了处理这些问题,最好的方法就是使用消息队列。
为什么需要消息队列
1.削峰填谷
业务系统在超高并发场景中,由于后端服务来不及同步吃力过多过快的请求,可能导致请求堵塞,严重时可能会由于高负荷拖垮服务器。其实我们都希望流量能够像一条直线一样一直比较平稳,这样我们的系统也会更加稳定。但是实际的流量会随着时间而不断变化,像 12306 这种的 APP,有时候流量大到难以想象,而一年中不同的时间段,其流量大小也不同,有高峰期,也有低谷期。为了能支持最高峰的流量,我们通过采用短平快的方式——直接扩容服务器,来增加服务端的吞吐量。优点是显而易见的,短时间内服务器的吞吐量增加了好几倍,甚至是数十倍。缺点也很明显,流量低峰期时服务器相对较闲。那么如何平衡平时的空闲与节假日的超高峰呢?消息队列便是目前业界比较常用的手段。利用消息队列扭转处理订票请求,告知用户 30min 内会告知订票结果。优缺点也很明显,性能有了明显的提升,但是我们作为业务开发人员,还要维护一个消息队列服务,人手便有些不足。
2.程序间解耦
不同的业务端在联合开发功能时,常常由于排期不同、人员调配不方便等原因导致项目延期。这中间最根本的原因是业务耦合过度。即上下游系统之间的通信是彼此依赖的,所以不得不协调上下游所有的资源同步进行,跨团队处理问题显然比在团队内部处理问题的难度要大。
加入消息队列之后,上下游系统进行开发、联调、测试时完全不依赖,大大降低了系统间的耦合度。
3.异步处理
处理订票请求是一个漫长的过程,需要检查预订的车次是否有足够数量的余票、下单扣减库存、更新缓存、调用三方短信等一系列操作。这些耗时的操作我们可以通过消息队列的方式,把请求提交成功的消息告诉用户,然后异步处理这些耗时的操作,保证 30min 能能把处理的结果通过短信推送给用户,否则系统处理多久,用户就得等多久。
4.数据的最终一致性
举例来说,我需要将我 A 银行卡上的钱转 1000 块到 B 银行卡,那么正常的处理流程就是我先在 A 银行的 APP 上提交转账请求,然后 A 银行卡扣除我 1000 块的账户余额,之后 B 银行卡会增加我 1000 块的账户余额,那么如果在这个过程当中通信失败的话,我就无法直到我的钱到底是被扣了还是没有被扣。此时就需要消息队列了。即,我在 A 银行的 APP 上提交转账请求之后,A 银行的后端服务会立即扣除我 1000 块的账户余额,并通知我说转账请求发送成功之类的消息。之后 A 银行会通过消息队列来通知 B 银行给我的账户余额增加 1000 块,处理完成之后会通过短信告诉我转账已成功。那么此时消息队列使用的优点便是:
(1)免去了 A 银行的重试机制的复杂逻辑,即 A 银行不需要多次去请求并等待 B 银行的处理结果,只需要告诉 B 银行这个消息即可;
(2)免去了 B 银行多次处理 A 银行请求的压力;
(3)即使 B 银行的服务器不可用,也不影响我在 A 银行提交转账申请。
常见的消息队列
消息队列名称 | Apache ActiveMQ | Apache Kafka | Apache RocketMQ | Apache Pulsar |
---|---|---|---|---|
产生时间 | 2007 | 2012 | 2017 | 2018 |
贡献公司 | Apache | 阿里巴巴 | 雅虎 | |
特性 | 支持协议众多:AMQP、STOMP、MQTT、JMS; 消息是持久化的JDBC | 超高写入速率; end-to-end 耗时毫秒级 | 万亿级消息支持; 万级 Topic 数量支持; end-to-end 耗时毫秒级 | 存储计算分离; 支持 SQL 数据查询 |
管理后台 | 自带 | 独立部署 | 独立部署 | 无 |
多语言客户端 | 支持 | 支持 | Java、C++、Python、Go、C# | Java、C++、Python、Go |
数据流支持 | 不支持 | 支持 | 支持 | 支持 |
消息丢失 | 理论上不会丢失 | 理论上不会丢失 | 理论上不会丢失 | 理论上不会丢失 |
文档完备性 | 好 | 极好 | 极好 | 社区完善 中 |
商业公司实践 | 国内部分企业 | 阿里巴巴 | 雅虎、腾讯、智联招聘 | |
容错 | 无重试机制 | 无重试机制 | 支持重试与死信消息 | 支持重试与死信消息 |
顺序消息 | 支持 | 支持 | 支持 | 支持 |
定时消息 | 不支持 | 不支持 | 支持 | 支持 |
事务消息 | 不支持 | 支持 | 支持 | 支持 |
消息轨迹 | 不支持 | 不支持 | 支持 | 自己实现简单 |
消息查询 | 数据库中查询 | 不支持 | 支持 | 支持 SQL |
重放消息 | 未知 | 暂停重放 | 实时重放 | 支持 |
宕机 | 自动切换 | 自动选主 | 手动重启 | 自动切换 |
RocketMQ 的发展史
和大部分组件产生的原因类似,阿里巴巴内部为了适应淘宝 B2C 的更快、更复杂的业务,2001 年启动了“五彩石项目”,阿里巴巴的第一代消息队列服务 Notify 就是在这个背景下产生的。2010 年,阿里巴巴内部的 Apache ActiveMQ 仍然作为核心技术被广泛应用于各个业务线,而顺序消息、海量消息堆积、完全自主控制消息队列服务,也是阿里巴巴同时期急需的,在这种背景下,2011 年,MetaQ 产生。
2011 年,LinkedIn 将 Kafka 开源。2012 年,阿里巴巴参考 Kafka 的设计,基于对 MetaQ 的理解和实际应用,研发了一套通用消息队列引擎,也就是 rocketMQ 。自此才有了第一代真正的 RocketMQ,2016 年阿里云上线 RocketMQ 消息队列服务。自 2001 年到 2012 年,11 年的实际使用、运维,和业务不断碰撞,才得以抽象并整理出一个真正的行业级产品。
2016 年 11 月,阿里巴巴将 RocketMQ 捐献给 Apache 基金会。Apache 社区有一个很重要的概念:社区大于代码。虽然 RocketMQ 已经开源 3 年,在国内小有名气,而且在阿里巴巴被广泛应用并有较好的效果,但是依然不能达到 Apache 优秀项目的标准。在 RocketMQ 被捐献后,通过一系列的修改、评审、调整,悄悄升级至 4.0 版本,正式进入孵化阶段。2017 年 9 月 25 日,RocketMQ 成功毕业,即 Apache 社区项目孵化成功。成为 Apache 顶级项目 ,它是国内首个互联网中间件在 Apache 的顶级项目,也是继 ActiveMQ、Kafka 后 Apache 家族中全新的一代消息队列引擎。
RocketMQ 主要由 Producer、Broker、Consumer 三部分组成,其中 Producer 负责生产消息,Consumer 负责消费消息,Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器,每个 Broker 可以存储多个Topic 的消息,每个 Topic 的消息也可以分片存储于不同的 Broker。Message Queue 用于存储消息的物理地址,每个 Topic 中的消息地址存储于多个 Message Queue 中。ConsumerGroup 由多个 Consumer 实例构成。