文章来源(建议大家阅读原文):《吃透 MQ 系列》之核心基础篇
一、什么是MQ?
MQ的本质就是“一发一存一消费”。
生产者把消息投递到队列中,然后从队列中取出消息,发送给消费者。
二、MQ演化
常用的MQ有RocketMQ、Kafka等,都是基于最原始的消息模型做扩展,同时支持一些新的特性,如:主题topic、分区partition、队列queue等。
要理解这些MQ,可以先从最简单的模型演化(架构都是不断演化而来的,并非一开始就设计的很完美)。
1. 队列模型
上面说的最简单的模型就是队列模型,FIFO。不多这个队列的“读”,就是从队列中删除这个消息。但是,一个消息只能给一个消费者,不支持多个消费者消费同一个消息。
2.发布订阅模型
如果需要把同一个消息给多个消费者消费,每个消费者都要求收到全量的消息。显然队列模型不能满足该需求。
一种可行的方案:为每个消费者创建一个队列,生产者发送多份消息。这种做法,一份数据会复制多份,很浪费空间。
另一种解决方案就解决了这个问题:发布订阅模型。
在发布订阅模型中,存放消息的容器变成了主题topic,订阅者可以指定订阅的主题topic。最终每个订阅者都可以收到同一个主题的全量消息。
他和队列模式的异同:生产者就是发布者,消费者就是订阅者,队列就是主题topic,无本质区别。唯一的区别:一份消息能否被多次消费。
3.小结
两种模式,就是单播和广播的区别。当发布订阅模式只有一个订阅者时,它就和队列模式一样了。所以这也解释了为什么现代主流的RocketMQ、Kafka都是直接基于发布订阅模式实现的。
三、MQ的应用场景
目前,MQ的应用场景非常多:系统解耦、异步通信和流量削峰。除此之外,还有延迟通知、最终一致性保证、顺序消息、流式处理等。所以MQ的适配性很广。
举个栗子,比如说电商业务中最常见的订单支付场景:在订单支付成功后,需要更新订单状态、更新用户积分、通知商家有新的订单、更新推荐系统中的用户画像等。
引入MQ后,订单支付现在只需要关注它最重要的流程:更新订单状态即可。其他的不重要的事情都交给MQ来通知。即实现系统解耦、异步通信。
改造前,订单系统依赖上个外部系统,改造后仅仅依赖MQ,而且后续业务扩展(如:营销系统针对支付用户奖励优惠券),也不会涉及订单系统的修改,从而保证了核心业务的稳定,降低了维护成本。
四、如何设计一个MQ?
1. 雏形
MQ即“一发一存一消费”,从技术的角度来看MQ的通信模型:两次RPC+消息转储。
MQ雏形:
a. 直接利用RPC框架,实现两个接口:发消息和读消息。
b. 消息放在本地内存中存储。
2. 写一个适合生产环境的MQ
生产环境中的MQ,需要解决哪些问题呢?
a. 高并发场景下,如何保证收发消息的性能?
b. 如何保证消息服务的高可用和高可靠?
c. 如何保证服务是可以水平扩展的?
d. 如何保证消息存储是可以水平扩展的?
e. 各种元数据(如集群中各个节点、主题topic、消费关系等)如何管理,需不需要考虑数据的一致性?
整体框架会涉及三类角色:
将“一发一存一消费”进一步细化后,比较完整的数据流如下:
三类角色的作用:
a. Broker(服务端): MQ中最核心的部分,是MQ的服务端,为生产者和消费者提供RPC接口,负责消息的存储、备份和删除,以及消费关系的维护。
b. Productor(生产者):调用Broker的RPC接口发送消息。
c. Consumer(消费者):调用Broker提供的RPC接口接收消息,同时完成消费确认。
详细设计
下面展开讨论一些具体的技术难点和可行的解决方案。
- 难点1:RPC通信
可直接利用成熟的框架Dubbo/Thrift实现,这样就不用重复造轮子,不用考虑服务注册发现、负载均衡、通信协议、序列化方式等一系列的问题了。
- 难点2:高可用设计
主要是两个部分的高可用:Broker的高可用、消息存储的高可用。
a. Broker的高可用,只需要保证Broker可水平扩展进行集群部署即可。进一步通过服务自动注册发现、负载均衡、超时重试机制、发送和消费消息时的ack机制来保证。
b. 消息存储的高可用:1)参考Kafka的分区+多副本的模式,但是需要考虑分布式场景下数据复制和一致性方案(类似Zab、Raft协议),并实现自动故障转移; 2)可以用主流的DB、分布式文件系统、带持久化能力的KV数据库。
- 难点3:高性能的存储设计
目前主流的方案:追加写日志文件(数据部分)+索引文件的方式,索引设计可以考虑稠密索引和稀疏索引,查找消息可以利用跳表、二分查找等,还可以通过操作系统的页缓存、零拷贝等技术来提升磁盘文件的读写性能。
- 难点4:消费关系管理
为了支持发布订阅模式,Broker需要知道每个主题topic有哪些consumer订阅了,基于这个关系进行消息投递。
由于Broker是集群部署的,所以消费关系通常维护在公共存储上,可以基于Zookeeper等配置中心来管理以及进行变更通知。
- 难点5:高性能设计
采用Reactor网络IO模型、业务线程池设计、生产端的批量发送、Broker的异步刷盘、消费端的批量拉取等。
小结:如何设计一个MQ?
a. 需要从功能需求(一发一存一消费)和非功能需求(高性能、高可用、高扩展等)两方面入手;
b. 功能性需求不是重点,能覆盖MQ的基础功能即可。至于延时消息、事务消息、重试队列等高级特性,是锦上添花的功能。
c. 最核心的是:能结合功能性需求,理清楚整体的数据流,然后顺着这个思路去思考非功能性的需求如何满足,这才是难点!
总结
这篇文章从MQ 一发一存一消费 的本质出发,讲解了消息模型的演进过程,这是MQ的最核心的理论基础。
最后通过回答:如何设计一个MQ,来对MQ有一个更深入的认识和理解。
文章来源(建议大家阅读原文):《吃透 MQ 系列》之核心基础篇