版本
- 基于
rocketmq-all-4.3.1
版本
简介
-
RocketMQ使用MappedFile、MappedFileQueue来封装存储文件。MappedFileQueue是MappedFile的管理容器,它是对存储目录进行封装
-
MappedFile是RocketMQ内存映射文件的具体实现。将消息字节写入Page Cache缓冲区中(commit方法),或者将消息刷入磁盘(flush)。
CommitLog consumerQueue、index
三类文件磁盘的读写都是通过MappedFile -
MappedFile的核心属性
- wrotePosition:保存当前文件所映射到的消息写入page cache的位置
- flushedPosition:保存刷盘的最新位置
- wrotePosition和flushedPosition的初始化值为0,一条1k大小的消息送达,当消息commit也就是写入page cache以后,
wrotePosition
的值为1024 * 1024;如果消息刷盘以后,则flushedPosition
也是1024 * 1024;另外一条1k大小的消息送达,当消息commit时,wrotePosition的值为1024 * 1024 + 1024 * 1024,同样,消息刷盘后,flushedPosition的值为1024 * 1024 + 1024 * 1024。
-
MappedFile源码
public class MappedFile extends ReferenceResource { //内存页大小,linux下通过getconf PAGE_SIZE获取,一般默认是4k public static final int OS_PAGE_SIZE = 1024 * 4; protected static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); //所有MappedFile实例已使用字节总数 private static final AtomicLong TOTAL_MAPPED_VIRTUAL_MEMORY = new AtomicLong(0); //MappedFile个数 private static final AtomicInteger TOTAL_MAPPED_FILES = new AtomicInteger(0); //MappedFile 当前文件所映射到的消息写入pagecache的位置 protected final AtomicInteger wrotePosition = new AtomicInteger(0); //ADD BY ChenYang 已经提交(持久化)的位置 protected final AtomicInteger committedPosition = new AtomicInteger(0); //flushedPosition来维持刷盘的最新位置 private final AtomicInteger flushedPosition = new AtomicInteger(0); protected int fileSize; protected FileChannel fileChannel; /** * Message will put to here first, and then reput to FileChannel if writeBuffer is not null. 堆外内存ByteBuffer,如果不为空(transientStorePoolEnable=true),数据受限将存储在buffer中,然后提交到FileChannel */ protected ByteBuffer writeBuffer = null; //堆外内存池,内存池中的内存会提供内存锁机制 protected TransientStorePool transientStorePool = null; private String fileName; //映射的起始偏移量,也是文件名 private long fileFromOffset; //磁盘的物理文件 private File file; private MappedByteBuffer mappedByteBuffer; //文件最后一次写入的时间戳 private volatile long storeTimestamp = 0; //是否是MappedFileQueue中的第一个文件 private boolean firstCreateInQueue = false; ...省略... }
构造方法
-
根据
transientStorePoolEnable
是否为true调用不同的构造方法。- transientStorePoolEnable=true(只在异步刷盘情况下生效)表示将内容先保存在堆外内存中。TransientStorePool会通过
ByteBuffer.allocateDirect
调用直接申请堆外内存,消息数据在写入内存的时候是写入预申请的内存中 - 通过Commit线程将数据提交到FileChannel中
- 在异步刷盘的时候,再由刷盘线程(Flush线程)将数据持久化到磁盘文件。
- transientStorePoolEnable=true(只在异步刷盘情况下生效)表示将内容先保存在堆外内存中。TransientStorePool会通过
-
构造方法源码
/** * 如果设置transientStorePoolEnable为false则调用此方法,参见 * org.apache.rocketmq.store.AllocateMappedFileService#mmapOperation() */ public MappedFile(final String fileName, final int fileSize) throws IOException { init(fileName, fileSize); } /** * 如果设置transientStorePoolEnable为true则调用此方法,参见 *org.apache.rocketmq.store.config.MessageStoreConfig#isTransientStorePoolEnable() * org.apache.rocketmq.store.AllocateMappedFileService#mmapOperation() */ public MappedFile(final String fileName, final int fileSize, final TransientStorePool transientStorePool) throws IOException { init(fileName, fileSize, transientStorePool); } public void init(final String fileName, final int fileSize, final TransientStorePool transientStorePool) throws IOException { init(fileName, fileSize); //如果transientStorePoolEnable为true,则初始化MappedFile的 //writeBuffer,该buffer从transientStorePool中获取 this.writeBuffer = transientStorePool.borrowBuffer(); this.transientStorePool = transientStorePool; }
-
FileChannel
提供了map()方法把文件映射到虚拟内存,通常情况可以映射整个文件,如果文件比较大,可以进行分段映射,RocketMQ这里映射大小为(0,fileSize)。当通过map()方法建立映射关系之后,就不依赖于用于创建映射的FileChannel
。特别是,关闭通道(Channel)对映射的有效性没有影响。MappedFile
的初始化(init)方法,初始化MappedByteBuffer
,模式为MapMode.READ_WRITE(读/写),此模式对缓冲区的更改最终将写入文件;但该更改对映射到同一文件的其他程序不一定是可见的。private void init(final String fileName, final int fileSize) throws IOException { this.fileName = fileName; this.fileSize = fileSize; this.file = new File(fileName); //通过文件名获取起始偏移量 this.fileFromOffset = Long.parseLong(this.file.getName()); boolean ok = false; //确保父目录存在 ensureDirOK(this.file.getParent()); try { 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("create file channel " + this.fileName + " Failed. ", e); throw e; } catch (IOException e) { log.error("map file " + this.fileName + " Failed. ", e); throw e; } finally { if (!ok && this.fileChannel != null) { this.fileChannel.close(