这是本人学习的总结,主要学习资料如下
- 马士兵教育
- rocketMq官方文档
目录
- 1、Broker启动流程
- 2、一些重要的类
- 2.1、MappedFile
- 2.2、MessgeStore
- 2.3、MessageStore的加载启动流程
- 3、技术亮点
- 3.1、 内存映射
- 3.1.1、简介
- 3.1.2、源码
1、Broker启动流程
Broker
启动流程与NameServer
的几乎一样,都是先读取外部配置并利用外部配置创建出BrokerController
。
接着使用创建好的BrokerController
,在其initialize()
方法中创建运行启动时必要的实例。
之后再start()
方法中开启网络和持久化服务。
流程如下所示。
2、一些重要的类
以上是Broker
中比较重要的类,他们被分成三个层次。
业务层是用于处理发送和接受信息,他们会调用逻辑存储层的对象获取基础一些的服务。
逻辑存储层的类可以看成是代表着Broker
里的文件或者文件夹的,比如CommitLog
这个类就代表着存储消息详细信息的commitlog
文件夹;ConsumeQueue
则代表存储消息队列的comsumequeue
。
存储I/O层里则是Broker
里最贴近底层的类,比如MappedFile
代表着某一个文件。
2.1、MappedFile
MappedFile
代表着一个物理文件。
它是对File
类的一个包装,底层还是用File
访问磁盘文件。不过MappedFile
增加了一些功能,比如增加了wrotePosition
这个成员,代表着下次写文件时,系统可以直接通过wrotePosition
知道文件的末尾在哪,这样就可以直接写入文件。
2.2、MessgeStore
MessageStore
是用于读写存储文件的一个类。这里的存储文件是指CommitLog
,comsumeQueue
这些文件。
源码中使用它的实现类DefaultMessageStore
,通过load()
方法完成初始化,之后在开启服务的start()
方法中通过start()
方法开启服务。
所以MessageStore
是管理存储文件的一个类。源码中还有多个和它类似的成员,他们负责不同的功能,但都是load()
初始化,start()
开启服务。
2.3、MessageStore的加载启动流程
MessageStore的启动加载包含了很多很多服务,比如CommitLog
,ConsumeQueue
等。但他们的加载启动都大同小异,所以这里只选了CommitLog
讲解。
MessageStore
是BrokerController
的一个成员变量。它在BrokerController
中的initialize()
完成实例初始化,随后调用其load()
方法更进一步地加载具体的服务。以下是涉及到的代码片段。
以这个线索来看CommitLog
加载的具体内容,messageStore.load() -> commitLog.load() -> doLoad()
。
可以看出所谓的加载,就是访问/store/commitlog
下的所有文件,将他们包装成MappedFile
存起来,方便后续的访问。
3、技术亮点
3.1、 内存映射
3.1.1、简介
MappedFile
在Broker
中可以说是最底层的代表文件的类,它使用了内存映射技术大大加快了文件的读写速度。
下面是网络数据到磁盘的过程。
一般的IO会有四次复制,两次DMA
拷贝,两次CPU拷贝。
CPU拷贝的效率要慢很多,一般来说200M的数据,DMA
拷贝仅需2ms
,而CPU拷贝需要200ms
。所以200M的数据从网络设备缓冲区到磁盘用传统的IO需要404ms
。
内存映射技术是建立一个磁盘空间和内存空间的映射通道,会覆盖一片磁盘空间,最大是1.5G ~ 2G
。
当我们向被覆盖的内存空间写数据时,数据可以通过通道到达磁盘。这种方式允许DMA
直接从内存拷贝数据到磁盘。
所以应用内存映射技术后,数据从网络设备缓冲区到磁盘就只需要一次CPU拷贝,两次DMA
拷贝。200m的数据只需要204ms
,是传统IO的一半。
因为内存映射最多只能覆盖1.5G ~ 2G
的磁盘空间,所以commitlog
的文件最大是1G
,保证每次映射能完整覆盖一个文件。
3.1.2、源码
内存映射的代码是放在MappedFile
中,在BrokerController.initialize()
阶段建立各个文件的内存映射通道。可根据一下的线索看到源码。
start() -> createBrokerController() -> controller.intialize() -> messageStore.load() -> commitLog.load() -> mappedFileQueue.load() -> doLoad() -> new MappedFile() -> init()
。
所以MappedFile
在实例初始化时就会建立内存映射通道,以下是构造方法调用的init()
的关键内容。
private void init(final String fileName, final int fileSize) throws IOException {
this.file = new File(fileName);
try {
// rw表示允许读写
this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();
this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);
TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(fileSize);
TOTAL_MAPPED_FILES.incrementAndGet();
ok = true;
} catch (FileNotFoundException e) {
log.error("Failed to create file " + this.fileName, e);
throw e;
} finally {
if (!ok && this.fileChannel != null) {
this.fileChannel.close();
}
}
}
可以看到,它通过new RandomAccessFile(this.file, "rw").getChannel()
创建文件的内存通道,并且赋值给fileChannel
。即使没有看其他的源码也可以知道,之后涉及到文件的读写操作最后一定会先获取这个fileChannel
,再调用其中的read(),write()
等方法进行读写。
其中还有一个叫mappedByteBuffer
的成员变量,它代表着被映射的磁盘空间。