消息中间价存储一般都是利用磁盘,在廉价的PC机上一般是使用机械硬盘,但机械硬盘的速度比访问内存慢了n个数量级,但一款优秀的消息中间件必然会将硬件资源压榨到极致,接下来看看rocketMq是如何做到高效存储的。
1、rocketMq存储结构
rocketMq存储
这张流程图简单介绍了rocketMq的存储实现,先简单说明下各自的含义
- MappedFile 所有的topic数据都写到同一个文件中,文件的大小默认为1G,使用mmap与磁盘文件做映射,初始化时使用
mlock
将内存锁定,防止pagecache被os交换到swap区域。数据是顺序写,数据写满后自动创建下个MappedFile顺序写入。 - MappedFileQueue MappedFile的队列,存储封装了所有的MappedFile实例。
- CommitLog 封装了写入消息和读取消息的实现,根据MappedFileQueue找到正在写的MappedFile,之后将消息写入到pagecache。
- ConsumerQueue 一个topic可以设置多个queue,每个consumerQueue对应一个topic下的queue,相当于kafka里的partition概念。里面存储了msg在commitLog中的offset、size、tagsCode,固定长度是20字节,consumer可以根据消息的offset在commitLog找到具体的消息。
2、高性能存储实现
2.1、mmap&&page cache
先简单介绍下mmap,mmap一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
rocketMq默认的文件大小为1G,即将1G的文件映射到物理内存上。但mmap初始化时只是将文件磁盘地址和进程虚拟地址做了个映射,并没有真正的将整个文件都映射到内存中,当程序真正访问这片内存时产生缺页异常,这时候才会将文件的内容拷贝到page cache。试想,如果一开始只是做个映射,而到具体写消息时才将文件的部分页加载到pagecache,那效率将会是多么的低下。MappedFile初始化的操作是由单独的线程(AllocateMappedFileService)实现的,就是对应的生产消费模型。还好rocketMq在初始化MappedFile时做了内存预热,事先向page cache 中写入一些数据flush到磁盘,使整个文件都加载到page cache中。接下来简单看下如何预热的
public void warmMappedFile(FlushDiskType type, int pages) {