本文曾经收录到 Github 仓库,该仓库蕴含 计算机根底、Java 根底、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享 等外围知识点,欢送 star~
Github 地址:https://github.com/Tyson0314/…
如果让你来设计一个 MQ,该如何下手?须要思考哪些问题?又有哪些技术挑战?
对于 MQ 来说,不论是 RocketMQ、Kafka 还是其余音讯队列,它们的实质都是:一发一存一生产。上面咱们以这个实质作为根,一起由浅入深地聊聊 MQ。
从 MQ 的实质说起
将 MQ 掰开了揉碎了来看,都是「一发一存一生产」,再直白点就是一个「转发器」。
生产者先将音讯投递一个叫做「队列」的容器中,而后再从这个容器中取出音讯,最初再转发给消费者,仅此而已。
下面这个图便是音讯队列最原始的模型,它蕴含了两个关键词:音讯和队列。
1、音讯:就是要传输的数据,能够是最简略的文本字符串,也能够是自定义的简单格局(只有能按预约格局解析进去即可)。
2、队列:大家应该再相熟不过了,是一种先进先出数据结构。它是寄存音讯的容器,音讯从队尾入队,从队头出队,入队即发消息的过程,出队即收音讯的过程。
原始模型的进化
再看明天咱们最罕用的音讯队列产品(RocketMQ、Kafka 等等),你会发现:它们都在最原始的音讯模型上做了扩大,同时提出了一些新名词,比方:主题(topic)、分区(partition)、队列(queue)等等。
要彻底了解这些形形色色的新概念,咱们化繁为简,先从音讯模型的演进说起(情理好比:架构从来不是设计进去的,而是演进而来的)
2.1 队列模型
最后的音讯队列就是上一节讲的原始模型,它是一个严格意义上的队列(Queue)。音讯依照什么程序写进去,就依照什么程序读出来。不过,队列没有“读”这个操作,读就是出队,从队头中“删除”这个音讯。
这便是队列模型:它容许多个生产者往同一个队列发送音讯。然而,如果有多个消费者,实际上是竞争的关系,也就是一条音讯只能被其中一个消费者接管到,读完即被删除。
2.2 公布 - 订阅模型
如果须要将一份音讯数据分发给多个消费者,并且每个消费者都要求收到全量的音讯。很显然,队列模型无奈满足这个需要。
一个可行的计划是:为每个消费者创立一个独自的队列,让生产者发送多份。这种做法比拟笨,而且同一份数据会被复制多份,也很节约空间。
为了解决这个问题,就演化出了另外一种音讯模型:公布 - 订阅模型。
在公布 - 订阅模型中,寄存音讯的容器变成了“主题”,订阅者在接管音讯之前须要先“订阅主题”。最终,每个订阅者都能够收到同一个主题的全量音讯。
认真比照下它和“队列模式”的异同:生产者就是发布者,队列就是主题,消费者就是订阅者,无本质区别。惟一的不同点在于:一份音讯数据是否能够被屡次生产。
2.3 小结
最初做个小结,下面两种模型说白了就是:单播和播送的区别。而且,当公布 - 订阅模型中只有 1 个订阅者时,它和队列模型就一样了,因而在性能上是齐全兼容队列模型的。
这也解释了为什么古代支流的 RocketMQ、Kafka 都是间接基于公布 - 订阅模型实现的?此外,RabbitMQ 中之所以有一个 Exchange 模块?其实也是为了解决音讯的投递问题,能够变相实现公布 - 订阅模型。
包含大家接触到的“生产组”、“集群生产”、“播送生产”这些概念,都和下面这两种模型相干,以及在利用层面大家最常见的情景:组间播送、组内单播,也属于此领域。
所以,先把握一些共性的实践,对于大家再去学习各个消息中间件的具体实现原理时,其实能更好地抓住实质,分清概念。
透过模型看 MQ 的利用场景
目前,MQ 的利用场景十分多,大家能滚瓜烂熟的是:零碎解耦、异步通信和流量削峰。除此之外,还有提早告诉、最终一致性保障、程序音讯、流式解决等等。
那到底是先有音讯模型,还是先有利用场景呢?答案必定是:先有利用场景(也就是先有问题),再有音讯模型,因为音讯模型只是解决方案的形象而已。
MQ 通过 30 多年的倒退,能从最原始的队列模型倒退到明天百花齐放的各种消息中间件(平台级的解决方案),我感觉万变不离其宗,还是得益于:音讯模型的适配性很广。
咱们试着从新了解下音讯队列的模型。它其实解决的是:生产者和消费者的通信问题。那它比照 RPC 有什么分割和区别呢?
通过比照,能很显著地看出两点差别:
1、引入 MQ 后,由之前的一次 RPC 变成了当初的两次 RPC,而且生产者只跟队列耦合,它基本无需晓得消费者的存在。
2、多了一个两头节点「队列」进行音讯转储,相当于将同步变成了异步。
再返过去思考 MQ 的所有利用场景,就不难理解 MQ 为什么实用了?因为这些利用场景无外乎都利用了下面两个个性。
举一个理论例子,比如说电商业务中最常见的「订单领取」场景:在订单领取胜利后,须要更新订单状态、更新用户积分、告诉商家有新订单、更新举荐零碎中的用户画像等等。
引入 MQ 后,订单领取当初只须要关注它最重要的流程:更新订单状态即可。其余不重要的事件全副交给 MQ 来告诉。这便是 MQ 解决的最外围的问题:零碎解耦。
革新前订单零碎依赖 3 个内部零碎,革新后仅仅依赖 MQ,而且后续业务再扩大(比方:营销零碎打算针对领取用户处分优惠券),也不波及订单零碎的批改,从而保障了外围流程的稳定性,升高了保护老本。
这个革新还带来了另外一个益处:因为 MQ 的引入,更新用户积分、告诉商家、更新用户画像这些步骤全副变成了异步执行,能缩小订单领取的整体耗时,晋升订单零碎的吞吐量。这便是 MQ 的另一个典型利用场景:异步通信。
除此以外,因为队列能转储音讯,对于超出零碎承载能力的场景,能够用 MQ 作为“漏斗”进行限流爱护,即所谓的流量削峰。
咱们还能够利用队列自身的程序性,来满足音讯必须按程序投递的场景;利用队列 + 定时工作来实现音讯的延时生产 ……
MQ 其余的利用场景根本相似,都能回归到音讯模型的个性上,找到它实用的起因,这里就不一一剖析了。
总之,就是倡议大家多从复杂多变的实际场景再回归到实践层面进行思考和形象,这样能吃得更透。
如何设计一个 MQ?
理解了下面这些理论知识以及利用场景后,上面咱们再一起看下:到底如何设计一个 MQ?
4.1 MQ 的雏形
咱们还是先从简略版的 MQ 动手,如果只是实现一个很毛糙的 MQ,齐全不思考生产环境的要求,该如何设计呢?
文章结尾说过,任何 MQ 无外乎:一发一存一生产,这是 MQ 最外围的性能需要。另外,从技术维度来看 MQ 的通信模型,能够了解成:两次 RPC + 音讯转储。
有了这些了解,我置信只有有肯定的编程根底,不必 1 个小时就能写出一个 MQ 雏形:
1、间接利用成熟的 RPC 框架(Dubbo 或者 Thrift),实现两个接口:发消息和读音讯。
2、音讯放在本地内存中即可,数据结构能够用 JDK 自带的 ArrayBlockingQueue。
4.2 写一个实用于生产环境的 MQ
当然,咱们的指标绝不止于一个 MQ 雏形,而是心愿实现一个可用于生产环境的消息中间件,那难度必定就不是一个量级了,具体咱们该如何下手呢?
1、先把握这个问题的关键点
如果咱们还是只思考最根底的性能:发消息、存音讯、生产音讯(反对公布 - 订阅模式)。
那在生产环境中,这些根底性能将面临哪些挑战呢?咱们能很快想到上面这些:
1、高并发场景下,如何保障收发音讯的性能?
2、如何保障音讯服务的高可用和高牢靠?
3、如何保障服务是能够程度任意扩大的?
4、如何保障音讯存储也是程度可扩大的?
5、各种元数据(比方集群中的各个节点、主题、生产关系等)如何治理,需不需要思考数据的一致性?
可见,高并发场景下的三高问题在你设计一个 MQ 时都会遇到,「如何满足高性能、高牢靠等非功能性需要」才是这个问题的关键所在。
2、整体设计思路
先来看下整体架构,会波及三类角色:
另外,将「一发一存一生产」这个外围流程进一步细化后,比拟残缺的数据流如下:
基于下面两个图,咱们能够很快明确出 3 类角色的作用,别离如下:
1、Broker(服务端):MQ 中最外围的局部,是 MQ 的服务端,外围逻辑简直全在这里,它为生产者和消费者提供 RPC 接口,负责音讯的存储、备份和删除,以及生产关系的保护等。
2、Producer(生产者):MQ 的客户端之一,调用 Broker 提供的 RPC 接口发送音讯。
3、Consumer(消费者):MQ 的另外一个客户端,调用 Broker 提供的 RPC 接口接管音讯,同时实现生产确认。
3、具体设计
上面,再展开讨论下一些具体的技术难点和可行的解决方案。
难点 1:RPC 通信
解决的是 Broker 与 Producer 以及 Consumer 之间的通信问题。如果不反复造轮子,间接利用成熟的 RPC 框架 Dubbo 或者 Thrift 实现即可,这样不须要思考服务注册与发现、负载平衡、通信协议、序列化形式等一系列问题了。
当然,你也能够基于 Netty 来做底层通信,用 Zookeeper、Euraka 等来做注册核心,而后自定义一套新的通信协议(相似 Kafka),也能够基于 AMQP 这种标准化的 MQ 协定来做实现(相似 RabbitMQ)。比照间接用 RPC 框架,这种计划的定制化能力和优化空间更大。
难点 2:高可用设计
高可用次要波及两方面:Broker 服务的高可用、存储计划的高可用。能够拆开探讨。
Broker 服务的高可用,只须要保障 Broker 可程度扩大进行集群部署即可,进一步通过服务主动注册与发现、负载平衡、超时重试机制、发送和生产音讯时的 ack 机制来保障。
存储计划的高可用有两个思路:1)参考 Kafka 的分区 + 多正本模式,然而须要思考分布式场景下数据复制和一致性计划(相似 Zab、Raft 等协定),并实现主动故障转移;2)还能够用支流的 DB、分布式文件系统、带长久化能力的 KV 零碎,它们都有本人的高可用计划。
难点 3:存储设计
音讯的存储计划是 MQ 的外围局部,可靠性保障曾经在高可用设计中谈过了,可靠性要求不高的话间接用内存或者分布式缓存也能够。这里重点说一下存储的高性能如何保障?这个问题的决定因素在于存储构造的设计。
目前支流的计划是:追加写日志文件(数据局部)+ 索引文件的形式(很多支流的开源 MQ 都是这种形式),索引设计上能够思考浓密索引或者稠密索引,查找音讯能够利用跳转表、二份查找等,还能够通过操作系统的页缓存、零拷贝等技术来晋升磁盘文件的读写性能。
如果不谋求很高的性能,也能够思考现成的分布式文件系统、KV 存储或者数据库计划。
** 难点 4:生产关系治理
**
为了反对公布 - 订阅的播送模式,Broker 须要晓得每个主题都有哪些 Consumer 订阅了,基于这个关系进行音讯投递。
因为 Broker 是集群部署的,所以生产关系通常保护在公共存储上,能够基于 Zookeeper、Apollo 等配置核心来治理以及进行变更告诉。
难点 5:高性能设计
存储的高性能后面曾经谈过了,当然还能够从其余方面进一步优化性能。
比方 Reactor 网络 IO 模型、业务线程池的设计、生产端的批量发送、Broker 端的异步刷盘、生产端的批量拉取等等。
4.3 小结
再总结下,要答复好:如何设计一个 MQ?
1、须要从功能性需要(收发音讯)和非功能性需要(高性能、高可用、高扩大等)两方面动手。
2、功能性需要不是重点,能笼罩 MQ 最根底的性能即可,至于延时音讯、事务音讯、重试队列等高级个性只是精益求精的货色。
3、最外围的是:能联合功能性需要,理分明整体的数据流,而后顺着这个思路去思考非功能性的诉求如何满足,这才是技术难点所在。
参考:https://toutiao.io/posts/ix9h…
最初给大家分享一个 Github 仓库,下面有大彬整顿的 300 多本经典的计算机书籍 PDF,包含 C 语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生 等,能够 star 一下,下次找书间接在下面搜寻,仓库继续更新中~
Github 地址:https://github.com/Tyson0314/…