消息队列
消息队列(Message Queue, MQ)常用在分布式系统中,用于异步、削峰、解耦等。
当前主流的消息队列产品包括:
Apache Kafka: 以其高性能、低延迟和高可靠性著称,特别适合大数据流处理和事件驱动的应用场景。
RabbitMQ: 基于AMQP协议,提供了丰富的特性和良好的社区支持,适用于多种应用场景,尤其是企业级项目。
RocketMQ: 阿里巴巴开源的一款分布式消息中间件,具有高吞吐量、低延迟的特点,适用于金融级别的交易系统。
NATS: 轻量级、高速的消息传输系统,适用于微服务架构和物联网场景。
RocketMQ发展历程
2007年,淘宝实施了“五彩石”项目,“五彩石”用于将交易系统从单机变成分布式,也是在这个过程中产生了阿里巴巴第一代消息引擎——Notify。2011年,阿里巴巴在研究了Kafka的整体机制和架构设计之后,基于Kafka的设计使用Java进行了完全重写并推出了MetaQ 1.0版本,主要是用于解决顺序消息和海量堆积的问题。而在2012年,阿里巴巴发现MetaQ原本基于Kafka的架构在阿里巴巴如此庞大的体系下很难进行水平扩展,所以在2012年的时候就开发了RocketMQ 3.0版本。在2015年,又基于RocketMQ开发了阿里云上的Aliware MQ和Notify 3.0。在2016年的时候,阿里巴巴将RocketMQ的内核引擎捐赠给了Apache基金会。
RocketMQ架构
- Name Server在RocketMQ中充当注册中心的角色。是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。
- Broker是RocketMQ中真正存储消息的地方。部署相对复杂,Broker可以部署多Master、多Slave。每个Broker与Name Server集群中的所有节点建立长连接,定时注册Topic信息到所有Name Server。
- Producer 消息生产者,与Name Server集群中的其中一个节点(随机选择)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。
- Consumer 消息消费者,与Name Server集群中的其中一个节点(随机选择)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。
为什么选择RocketMQ
- 具有高性能、高可靠、高实时、分布式特点。
- 能够保证严格的消息顺序
- 提供丰富的消息拉取模式
- 高效的订阅者水平扩展能力
- 实时的消息订阅机制
- 亿级消息堆积能力
消费模型
RocketMQ支持集群消费和广播消费模式。
集群消费
消息会投递到消费者集群中的一个节点进行消费。
广播消费
消息会投递到所有消费者节点进行消费。
关键设计原理
消息存储
RocketMQ的Broker Strore从Mysql innodb上借鉴了许多,采用了一种基于日志的存储方式,将消息以顺序写入的方式追加到文件中,从而实现高性能的消息存储和读取。
RocketMQ的消息存储是由consume queue和commit log配合完成的。
consume queue是消息的逻辑队列,相当于字典的目录,用来指定消息在物理文件commit log上的位置。
Commit Log的消息是真实的消息数据,存储单元长度不固定,文件顺序写,随机读。消息的存储结构如下表所示,按照编号顺序以及编号对应的内容依次存储。
这种存储设计,最大的好处是写速度很快,多toipc共享顺序写commit log,又平衡了topic维度拆分的读操作。
**而读性能优化方面,使用了二次查询(topic queue log -> commit log)**有所折损性能,使用了零拷贝、客户端批量查(默认1k条消息)以提高转盘命中率。
消息订阅
RocketMQ消息订阅有两种模式,一种是Push模式,即MQServer主动向消费端推送;另外一种是Pull模式,即消费端在需要时,主动到MQServer拉取。但在具体实现时,Push和Pull模式都是采用消费端主动拉取的方式。之所以这样设计是为了降低MQServer在集群规模较大的情况下的可能会对可靠性有所影响。
消费端的Push模式实际上是通过长轮询的模式来实现的,长轮询是利用HTTP协议的特性,让客户端能够长时间保持一个请求不被关闭,直到服务器有新数据推送为止。这样可以实现近乎实时的推送效果。好处是数据实时性高的同时降低了Server的压力。
顺序消息
RocketMQ在进行消息发送时,可以按照业务逻辑选择对应的消息队列进行发送,保证对应的一批消息发到同一个队列中,即可保证消息的顺序。
producer.send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Long id = (Long) arg;
int index = (int) (id % mqs.size());
return mqs.get(index);
}
}, args);