基本架构
RocketMQ由name server,broker,producer,consumer四个组件构成,每个组件都没有单点问题,整体架构图如下:
每个组件的用途如下:
name server:提供轻量级服务发现和路由,每个服务器记录完整的路由信息,用来提供完整的读写服务。name server记录了broker,topic等状态信息,其他角色的服务器向name server上报状态信息,并记录到内存中(一般不开启持久化存储);
broker:为topic和queue提供轻量级存储,支持推和拉模式,内置了容错、削峰填谷、万亿级消息有序能力;除此之外,broker提供了灾难恢复、丰富的统计功能、报警等传统消息系统缺乏的功能,broker是RocketMQ集群的核心;
producer:支持分布式、快速失败和低延迟特性,RocketMQ支持同步、异步、单向三种消息发送方式;
consumer:支持分布式、推模式和拉模式(推模式是对拉模式的封装);支持集群模式和广播模式两种,RocketMQ支持局部消息顺序消费,但不支持全局消息顺序消费(将topic的队列数设为1可以达到全局有序,但会失去高可用);
topic:topic用于将消息按主题划分。producer和consumer都是按topic进行生产和消费的,topic是逻辑概念,每个topic都是由分布在broker上的queue构成的;
Tag:对topic的进一步细化,用来标记在同一个topic中不同用途的消息;
心跳机制:
name server是高可用的核心,name server集群之间的服务器互不通信,broker启动时向name server中的所有发送注册信息,name server接受broker集群的注册并通过心跳检查broker是否在线,心跳间隔为30s,心跳请求中包括broker所有的topic信息、生产者和消费者信心,name server发现2分钟内某个broker没有心跳就认为broker下线,但name server不会主动通知producer、consumer关于broker宕机的信息;
producer每隔30秒从name server获取topic跟broker的映射关系,之后跟topic涉及的所有broker建立长连接,心跳间隔也是30秒,心跳包包含生产者所有信息,如果broker检查到某个producer在2分钟内没有心跳则断开连接;
consumer从name server获取订阅当前topic存在哪些broker上,之后跟broker建立长连接,同样每隔30秒发送一次心跳,心跳包包含消费者的所有信息,broker发现某个consumer在2分钟之内没有心跳,就断开连接;
高可用机制:
name server:name server是轻量级、无状态集群,每个name server服务器都保存全量元数据信息,只要有一台name server服务器可用,name server就可以提供注册、元数据服务;
broker:broker分为master和slave,master可读可写,slave只读;一个master和多个slave组成一个broker set,同一个broker set内的brokerName相同,集群中master之间是相互独立的;从可用性角度讲,一个set中作为master的broker宕机后,没有其他broker接替执行master角色,broker本身是没有高可用机制的;
producer:从上面的分析可以看出来,producer写数据的broker master宕机时不会得到通知,失败后默认重试2次,重试过程中选择队列的机制比较关键,默认不启用broker故障延迟机制,方法的主要目的是规避上次故障的broker,代码如下:
public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
if (lastBrokerName == null) {
return selectOneMessageQueue();
} else {
int index = this.sendWhichQueue.getAndIncrement();
for (int i = 0; i < this.messageQueueList.size(); i++) {
int pos = Math.abs(index++) % this.messageQueueList.size();
if (pos < 0) {
pos = 0;
}
MessageQueue mq = this.messageQueueList.get(pos);
if (!mq.getBrokerName().equals(lastBrokerName)) {
return mq;
}
}
return selectOneMessageQueue();
}
}
public MessageQueue selectOneMessageQueue() {
int index = this.sendWhichQueue.getAndIncrement();
int pos = Math.abs(index) % this.messageQueueList.size();
if (pos < 0)
pos = 0;
return this.messageQueueList.get(pos);
}
consumer:consumer消费topic消息时,遇到broker宕机一样不会得到通知,广播模式下,每个consumer都会得到消息,而在集群模式下,每个消息只能被一个consumer处理掉,所以consumer集群消费topic消息时,按照queue进行负载均衡,一个queue只被一个consumer消费,如果某个consumer挂掉,其他consumer会接替挂掉的consumer继续消费queue里的消息。消费的流程非常复杂,核心的方法有两个:队列的负载均衡机制与重新分布机制;消费进度管理机制。后面专门写文章介绍这两个机制。
底层存储:
消息底层有CommitLog,ConsumeQueue,IndexFile三种文件协同:
CommitLog:存储broker上所有topic的全量消息内容,完全顺序写;
ConsumeQueue:由于CommitLog是所有队列的消息内容,消费者从CommitLog读消息的时候就会变成随机读,为了提高消费消息时的读性能,RocketMQ设计了ConsumeQueue,;为了节省磁盘空间,ConsumeQueue不保存全量数据,默认保留30万条消息,消费者从ConsumeQueue读取消息时,就是连续读,大幅优化读性能;消息进行持久化到CommitLog时,异步转发到ConsumeQueue中;
IndexFile:消息索引文件是为了提高消费topic消息时的定位速度而设计,主要存储消息Key与Offset的对应关系。
底层存储架构图如下:
高性能:
高性能写:写的场景是producer发送消息时进行持久化,高性能写的关键机制是顺序写和PageCache,在broker上,所有的topic的内存存储在同一个CommitLog上,满1G时建立新文件,RocketMQ默认的CommitLog保留时间为3天或者磁盘容量达到75%时从最老的数据文件开始清理;
高性能读:读的场景是consumer消费消息时,broker从磁盘上读取消息数据,高性能读的关键机制是零拷贝复制,跳跃表和预读,其中预读是操作系统的特性。由于同一个topic的消息数据文件在CommitLog上是顺序但不连续的,所以读采用ConsumeQueue消息队列文件作为辅助,来提升读性能。
个人公众号:
自己在软件这个行业10来年的工作和学习历程中犯了不少错误,也走了不少弯路,在此公众号分享自己的成长心路和工作心得,希望给后来的从业者一个参照,不要再犯我犯过的错误,不要再走我走过的弯路。在此我也会分享工作中遇到的技术问题和自己研究技术的记录与心得;在项目过程中遇到的风险和暴露于化解风险的过程和方法;在团队管理过程中的心得和体会。后续会发布一系列专题技术文章,程序员成长系列文章,项目的系列文章,行业发展分析和展望的文章,甚至会包含婚恋和育儿的心得文章,我是个不专注却热爱生活的工程师!