RocketMQ源码分析(八)之MappedFile

版本

  1. 基于rocketmq-all-4.3.1版本

简介

  1. RocketMQ使用MappedFileMappedFileQueue来封装存储文件。MappedFileQueueMappedFile的管理容器,它是对存储目录进行封装

  2. MappedFile是RocketMQ内存映射文件的具体实现。将消息字节写入Page Cache缓冲区中(commit方法),或者将消息刷入磁盘(flush)。CommitLog consumerQueue、index三类文件磁盘的读写都是通过MappedFile

  3. MappedFile的核心属性

    • wrotePosition:保存当前文件所映射到的消息写入page cache的位置
    • flushedPosition:保存刷盘的最新位置
    • wrotePositionflushedPosition的初始化值为0,一条1k大小的消息送达,当消息commit也就是写入page cache以后,wrotePosition的值为1024 * 1024;如果消息刷盘以后,则flushedPosition也是1024 * 1024;另外一条1k大小的消息送达,当消息commit时,wrotePosition的值为1024 * 1024 + 1024 * 1024,同样,消息刷盘后,flushedPosition的值为1024 * 1024 + 1024 * 1024。
  4. 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;
        ...省略...
    }
    

构造方法

  1. 根据transientStorePoolEnable是否为true调用不同的构造方法。

    • transientStorePoolEnable=true(只在异步刷盘情况下生效)表示将内容先保存在堆外内存中。TransientStorePool会通过ByteBuffer.allocateDirect调用直接申请堆外内存,消息数据在写入内存的时候是写入预申请的内存中
    • 通过Commit线程将数据提交到FileChannel中
    • 在异步刷盘的时候,再由刷盘线程(Flush线程)将数据持久化到磁盘文件。
  2. 构造方法源码

    /**
     * 如果设置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;
    }
    
  3. 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(
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值