一、存储性能优化
1.1 PageCache
我们知道,Broker在将消息写入CommitLog时,采用了“顺序写”的方式,这样可以大大提升性能。但是,光这样做还是不够的,因为毕竟仍然是磁盘IO操作,要想进一步提升性能,必须利用内存。
所以,Broker将数据写入CommitLog文件的时候,其实不是直接写入底层的物理磁盘文件,而是先进入OS的PageCache内存缓存中,后续由OS后台线程异步化的将OS PageCache中的数据刷入底层的磁盘文件中:
所以,RocketMQ正式通过磁盘文件顺序写+OS PageCache写入+OS异步刷盘的策略来保证消息写入的性能。
在上述这种异步刷盘的模式下,Producer将消息发送给Broker,Broker将消息写入OS PageCache中,就会直接返回ACK给生产者,生产者收到ACK消息就认为写入成功了。
有异步刷盘就有同步刷盘,同步刷盘主要的不同点就是,只有Broker强制把这条消息刷入底层的磁盘文件后,才会返回ACK给生产者。
在异步刷盘的模式下,如果Broker将消息写入PageCahe并响应给生产者后突然宕机,此时消息在缓存中没有写入底层的磁盘文件,就会造成消息丢失——生产者认为发送成功,实际上消息写入失败。我会在后续章节对MQ中的消息丢失问题专门讲解。
1.2 mmap
RocketMQ除了利用PageCache和异步刷盘来提升性能外,还使用了一种叫做mmap的技术。
多次拷贝问题
传统的磁盘IO操作,当我们的程序需要将数据写入操作系统的磁盘上时(或从磁盘上读取文件内容到程序中),涉及用户空间和内核空间的转换,我们的程序进程有自己独立的内存空间,而操作系统也有自己的内核缓存区,所以一次数据写入或读取过程是像下面这样的:
可以看到,无论是写入还是读取,都涉及两次数据拷贝,这就是传统IO的数据多次拷贝问题。
内存映射
RocketMQ底层对CommitLog、ConsumeQueue之类的磁盘文件的读写操作,基本上都会采用mmap技术配合pagecache进行文件读写优化。
mmap技术会先将一个磁盘文件的物理地址(比如CommitLog文件或ConsumeQueue文件)映射到程序进程自己的私有内存里来,所谓映射并不是直接将磁盘文件中的数据读取到内存,而是把磁盘文件的一些物理地址和用户进程私有空间的一些虚拟内存地址进行映射,这个过程并没有任何的数据拷贝操作,如下图:
mmap技术在进行文件映射时,对文件大小一般限制,在1.5GB~2GB之间,所以RocketMQ才让CommitLog单个文件在不超过1GB,ConsumeQueue文件是5.72MB。
上述这个地址映射的过程,具体到代码层面,RocketMQ是利用JDK NIO包下的MappedByteBuffer.map()
来实现的,其底层就是基于mmap技术。
所以,假设现在要写入消息内容到CommitLog文件中,那么过程是这样的:
- 首先,Broker会将一个CommitLog文件的物理地址通过
MappedByteBuffer.map()
映射到Broker进程自己的虚拟内存中; - 接着,写入消息时,消息内容会先进入PageCache(PageCahe就对应虚拟内存);
- OS的线程异步将PageCache中的内容刷入CommitLog磁盘中(顺序写入)。
上述整个过程没有“内核IO缓存区”参与,只有一次数据拷贝过程,就是从PageCache里拷贝到磁盘文件里而已!这个就是使用mmap技术之后,相比于传统磁盘IO的一个性能优化。
Broker会针对磁盘上的各种CommitLog、ConsumeQueue文件预先分配好MappedFile,也就是提前对一些可能接下来要读写的磁盘文件进行内存映射,这样后续读写文件时,就可以直接执行了。
1.3 文件预热
上述我们讲了mmap技术对消息写入的优化,事实上,对于消息读取,整个过程也只有一次数据拷贝而已,其过程如下:
- 读取消息时,首先判断要读取的数据是否在PageCache里?如果在的话,直接从PageCache里读取;
- 如果PageCache里没有,就会从磁盘文件里加载数据到PageCache中(这个过程利用mmap技术只需一次拷贝);
- PageCache在加载数据时,会将被加载数据的临近其它数据块也一起加载,提升命中率。
RocketMQ在提前进行内存映射后,还会提前将后面可能频繁读取的一些数据(比如CommitLog、ConsumeQueue文件),尽可能多的加载到PageCache中,这就是所谓的文件预热。
二、总结
RocketMQ的持久化机制,其核心就是:CommitLog保存消息内容,ConsumeQueue保存消息地址——offset。
RocketMQ为了提升消息持久化的性能,采用了如下手段:
- 顺序写入消息内容至磁盘文件尾;
- 将消息先写入PageCache,然后异步刷盘;
- 利用mmap技术进行内存映射,以减少数据拷贝次数;
- 提前加载磁盘文件内容到PageCache中,进行文件预热,提升读取效率。