Rocketmq核心源码剖析

Rocketmq源码调试环境构建

1、下载https://github.com/apache/rocketmq/ 对应工程版本文件

2、使用Idea 打开Rocketmq工程。打开工程后我们会看到多个模块,我们需要启动两个服 务Namesrv与Broker服务

打开Namesrv服务入口类运行
org.apache.rocketmq.namesrv.NamesrvStartup#main

打开Broker服务入口类运行
org.apache.rocketmq.broker.BrokerStartup#main

直接运行会启动失败,原因是找不到启动配置文件

Please set the ROCKETMQ_HOME variable in your environment to match the lo cation of the RocketMQ installation

不配环境变量,需要修改启动类

#修改Namesrv启动类
org.apache.rocketmq.namesrv.NamesrvStartup#createNamesrvController
//添加本行代码,路径为Rocketmq项目路径下的子项目distribution工程路径
namesrvConfig.setRocketmqHome("D:\\GitSource\\rocketmq‐rocketmq‐all‐4.3.2
\\distribution");
if (null == namesrvConfig.getRocketmqHome()) {
System.out.printf("Please set the %s variable in your environment to mat
ch the location of the RocketMQ installation%n", MixAll.ROCKETMQ_HOME_ENV);
System.exit(‐2);
}
#修改Broker启动类
org.apache.rocketmq.broker.BrokerStartup#createBrokerController
//添加本行代码
brokerConfig.setRocketmqHome("D:\\GitSource\\rocketmq‐rocketmq‐all‐4.3.2
\\distribution");
if (null == brokerConfig.getRocketmqHome()) {
System.out.printf("Please set the %s variable in your environment to ma
tch the location of the RocketMQ installation", MixAll.ROCKETMQ_HOME_ENV);
System.exit(‐2);
}

运行服务

启动Namesrv  下面参数配置到idea的program arguments参数里
-n localhost:9876 &
启动Broker   下面参数配置到idea的program arguments参数里
-n localhost:9876 -c D:\GitSource\rocketmq-rocketmq-all-4.3.2\distribution\conf\broker.conf &

Namesrv架构

NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo中的 zookeeper,支持Broker的动态注册与发现。主要包括两个功能:

Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的 基本数据。然后提供心跳检测机制,检查Broker是否还存活;

路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端 查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集 群的路由信息,从而进行消息的投递和消费。NameServer通常也是集群的方式部署,各实 例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一 个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线 了,Broker仍然可以向其它NameServer同步其路由信息,Producer,Consumer仍然可以 动态感知Broker的路由的信息。

启动时序图

Rocketmq核心源码剖析_第1张图片

 Broker架构

核心模块

Remoting Module:整个Broker的实体,负责处理来自clients端的请求。

Client Manager:负责管理客户端(Producer/Consumer)和维护Consumer的 Topic订阅信息

Store Service:提供方便简单的API接口处理消息存储到物理硬盘和查询功能。

HA Service:高可用服务,提供Master Broker 和 Slave Broker之间的数据同 步功能。

Index Service:根据特定的Message key对投递到Broker的消息进行索引服 务,以提供消息的快速查询

Rocketmq核心源码剖析_第2张图片

消息存储整体架构 

Rocketmq核心源码剖析_第3张图片

消息存储架构图中主要有下面三个跟消息存储相关的文件构成。

(1) CommitLog:消息主体以及元数据的存储主体,存储Producer端写入的消息主体内容, 消息内容不是定长的。单个文件大小默认1G ,文件名长度为20位,左边补零,剩余为起始 偏移量,比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为 1G=1073741824;当第一个文件写满了,第二个文件为00000000001073741824,起始 偏移量为1073741824,以此类推。消息主要是顺序写入日志文件,当文件满了,写入下一 个文件; 

(2) ConsumeQueue:消息消费队列,引入的目的主要是提高消息消费的性能,由于 RocketMQ是基于主题topic的订阅模式,消息消费是针对主题进行的,如果要遍历 commitlog文件中根据topic检索消息是非常低效的。Consumer即可根据 ConsumeQueue来查找待消费的消息。其中,ConsumeQueue(逻辑消费队列)作为消 费消息的索引,保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset, 消息大小size和消息Tag的HashCode值。consumequeue文件可以看成是基于topic的 commitlog索引文件,故consumequeue文件夹的组织方式如下:topic/queue/file三层 组织结构,具体存储路径为: $HOME/store/consumequeue/{topic}/{queueId}/{fileName}。同样consumequeue文 件采取定长设计,每一个条目共20个字节,分别为8字节的commitlog物理偏移量、4字节 的消息长度、8字节tag hashcode,单个文件由30W个条目组成,可以像数组一样随机访 问每一个条目,每个ConsumeQueue文件大小约5.72M;

(3) IndexFile:IndexFile(索引文件)提供了一种可以通过key或时间区间来查询消息的方 法。Index文件的存储位置是:$HOME \store\index${fileName},文件名fileName是以 创建时的时间戳命名的,固定的单个IndexFile文件大小:40+500W*4+2000W*20= 420000040个字节大小,约为400M,一个IndexFile可以保存 2000W个索引,IndexFile 的底层存储设计为在文件系统中实现HashMap结构,故rocketmq的索引文件其底层实现 为hash索引。

索引文件由索引文件头(IndexHeader)+( 槽位 Slot )+(消息的索引内容)三部分构 成

Rocketmq核心源码剖析_第4张图片 

 

org.apache.rocketmq.store.index.IndexFile
#hashSlot占空间4bytes
private static int hashSlotSize = 4;
#index索引内容空间大小 20bytes
private static int indexSize = 20;
private static int invalidIndex = 0;
#槽位数量,默认500w
private final int hashSlotNum;
#索引数量 2000w
 private final int indexNum;
 #索引文件头信息
 private final IndexHeader indexHeader;

 org.apache.rocketmq.store.index.IndexHeader
 IndexHeader结构字段定义
 #信息头占用空间定长40 bytes,由6个部分构成
 public static final int INDEX_HEADER_SIZE = 40;
 #第一个索引消息落在Broker的时间戳 8bytes
 private AtomicLong beginTimestamp = new AtomicLong(0);
 #最后一个索引消息落在Broker的时间戳 8bytes
 private AtomicLong endTimestamp = new AtomicLong(0);
 #第一个索引消息在commitlog的偏移量 8bytes
 private AtomicLong beginPhyOffset = new AtomicLong(0);
 #最后一个索引消息在commitlog的偏移量 8bytes
 private AtomicLong endPhyOffset = new AtomicLong(0);
 #构建索引占用的槽位数 4bytes
 private AtomicInteger hashSlotCount = new AtomicInteger(0);
 #构建的索引个数,4bytes
 private AtomicInteger indexCount = new AtomicInteger(1);

索引头文件存储结构:

在上面的RocketMQ的消息存储整体架构图中可以看出,RocketMQ采用的是混合型 的存储结构,即为Broker单个实例下所有的队列共用一个日志数据文件(即为 CommitLog)来存储。RocketMQ的混合型存储结构(多个Topic的消息实体内容都存储于 一个CommitLog中)针对Producer和Consumer分别采用了数据和索引部分相分离的存储 结构,Producer发送消息至Broker端,然后Broker端使用同步或者异步的方式对消息刷盘 持久化,保存至CommitLog中。只要消息被刷盘持久化至磁盘文件CommitLog中,那么 Producer发送的消息就不会丢失。正因为如此,Consumer也就肯定有机会去消费这条消 息。当无法拉取到消息后,可以等下一次消息拉取,同时服务端也支持长轮询模式,如果一 个消息拉取请求未拉取到消息,Broker允许等待30s的时间,只要这段时间内有新消息到 达,将直接返回给消费端。这里,RocketMQ的具体做法是,使用Broker端的后台服务线 程—ReputMessageService不停地分发请求并异步构建ConsumeQueue(逻辑消费队 列)和IndexFile(索引文件)数据。

消息刷盘 

Rocketmq核心源码剖析_第5张图片

(1) 同步刷盘:如上图所示,只有在消息真正持久化至磁盘后RocketMQ的Broker端才会真 正返回给Producer端一个成功的ACK响应。同步刷盘对MQ消息可靠性来说是一种不错的 保障,但是性能上会有较大影响,一般适用于金融业务应用该模式较多。

(2) 异步刷盘:能够充分利用OS的PageCache的优势,只要消息写入PageCache即可将成 功的ACK返回给Producer端。消息刷盘采用后台异步线程提交的方式进行,降低了读写延 迟,提高了MQ的性能和吞吐量。 

零拷贝刷盘

以文件下载为例,服务端的主要任务是:将服务端主机磁盘中的文件不做修改地从已连 接的socket发出去。操作系统底层I/O过程如下图所示:

Rocketmq核心源码剖析_第6张图片

过程共产生了四次数据拷贝,在此过程中,我们没有对文件内容做任何修改,那么在内核空 间和用户空间来回拷贝数据无疑就是一种浪费,而零拷贝主要就是为了解决这种低效性。

什么是零拷贝技术? 

零拷贝主要的任务就是避免CPU将数据从一块存储拷贝到另外一块存储,主要就是利用 各种零拷贝技术,避免让CPU做大量的数据拷贝任务,减少不必要的拷贝,或者让别的组件 来做这一类简单的数据传输任务,让CPU解脱出来专注于别的任务。这样就可以让系统资源 的利用更加有效。

原理是磁盘上的数据会通过DMA被拷贝的内核缓冲区,接着操作系统会把这段内核缓冲 区与应用程序共享,这样就不需要把内核缓冲区的内容往用户空间拷贝。应用程序再调用 write(),操作系统直接将内核缓冲区的内容拷贝到socket缓冲区中,这一切都发生在内核 态,最后,socket缓冲区再把数据发到网卡去。

原理如下

Rocketmq核心源码剖析_第7张图片

 

你可能感兴趣的:(RocketMQ,java,开发语言,后端)