要理解rocketMq broker CommitLog文件的创建就要了解AllocateMappedFileService的运行机制,本文从以下几个方面详细介绍mapped文件从提交请求到创建,再到刷盘三个步骤仔细介绍mapped文件的生成和数据刷盘。
mapped文件创建任务线程AllocateMappedFileService.该任务在CommitLog类初始化的时候创建。在broker控制器启动的时候启动。
启动之后线程锁死在while循环当中,并阻塞获取requestQueue队列当中的AllocateRequest请求实例。
当服务启动没有CommitLog文件可以使用,或者最后一个可写的CommitLog文件写满的时候,都会触发tryCreateMappedFile方法创建新的mapped文件。
protected MappedFile tryCreateMappedFile(long createOffset) {
String nextFilePath = this.storePath + File.separator + UtilAll.offset2FileName(createOffset);
String nextNextFilePath = this.storePath + File.separator + UtilAll.offset2FileName(createOffset
+ this.mappedFileSize);
return doCreateMappedFile(nextFilePath, nextNextFilePath);
}
创建的过程中,如果配有线程任务AllocateMappedFileService实例,会调用putRequestAndReturnMappedFile,将本次请求创建新mapped文件操作放置到requestQueue队列单中。
通过调用栈,可以看出MappedFile创建是采用了AllocateMappedFileService这种异步处理模式。AllocateMappedFileService线程感知到提交上来创建新mapped文件的请求之后,将需要创建的文件绝对路径包装成AllocateRequest对象。并将其放入请求table当中(requestTable)。并将AllocateRequest对象放置到requestQueue当中。(注意每次请求map文件都是一次就请求创建两个。)
AllocateRequest result = this.requestTable.get(nextFilePath);
之后就是异步等待文件创建的结果,等待的时间默认是5秒
boolean waitOK = result.getCountDownLatch().await(waitTimeOut, TimeUnit.MILLISECONDS);
如果5秒之后不能够获取结果,会报错【create mmap timeout】不然说明本次请求正常生成了文件,将该请求从requestTable移除,并返回新生成的文件MappedFile。
处理requestTable请求的过程就是AllocateMappedFileService的要做的事情。
该线程任务采用阻塞的方式拉取requestTable中的请求,并根据是否配置了transientStorePoolEnable和本机是否是broker两个条件分别创建两种MappedFile
1、如果是设置了transientStorePoolEnable=true,则创建DefaultMappedFile并调用其init方法初始化
mappedFile = ServiceLoader.load(MappedFile.class).iterator().next();
mappedFile.init(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool());
该方法会初始化writeBuffer字段,该字段就是transientStorePool中的某个availableBuffers(对外内存)。
2、如果设置了transientStorePoolEnable=false,则正常调用DefaultMappedFile实例化方法
mappedFile = new DefaultMappedFile(req.getFilePath(), req.getFileSize());
该方法不会有writeBuffer字段,不会使用到对外直接存储。
这两种方法都是用了map方法将文件映射到内存。
在mappedFile追加报文的时候会调用appendMessageBuffer方法,该方法就会根据是否设置了transientStorePoolEnable=true采用使用直接内存还是pageCache两种方式写数据。
文件创建好之后数据根据不通的配置写到了对外内存writeBuffer和pageCache当中。写到pageCache操作系统会定期刷盘,写到直接内存writeBuffer,则需要我们异步将其写入pageCache当中。
刷盘分为两个步骤,提交和刷数据到磁盘。
提交过程
this.mappedFileQueue.commit(0);
①获取待提交的mappedFile,这个mappedFile跟根据transientStorePoolEnable配置的不同有两种。
②、调用mappedFile的commit方法提交
2.1、提交时候首先要获取锁,这里面使用的是AtomicLong字段refCount实现。
2.2、获取锁之后提交过程为将当前已提交位置和已写入位置之间内容写入fileChannel当中。
2.3、释放锁字段refCount。
2.4、更新已提交位移。
2.5、如果是当前文件都已经提交到fileChannel当中,那么就将writeBuffer空间归还给transientStorePool
刷数据到磁盘
this.mappedFileQueue.flush(0);
①获取待刷新的mappedFile,这个mappedFile跟根据transientStorePoolEnable配置的不同有两种。每次刷新完成都会把最新刷新位移同步到flushedWhere当中。
②、调用mappedFile的flush方法
2.1、获取待写数据位置
2.2、如果使用了对外内存调用this.fileChannel.force(false);。因为在提交的时候writeBuffer数据已经放入到了fileChannel当中,所以直接刷fileChannel即可
2.3、如果不是对外内存,直接刷新pageCache,即this.mappedByteBuffer.force()
2.4、更新刷新位置flushedPosition
刷盘过程是以上两个步骤,但是刷盘时机可以根据不同场景可以选择不同的方式,同步刷盘和异步刷盘。
CommitLog文件的创建是通过线程任务AllocateMappedFileService来完成,通过将创建请求包装成请求对象AllocateRequest将其提交到待处理列表requestTable中。AllocateMappedFileService线程循环拉取请求,并根据broker的不同transientStorePoolEnable配置使用不同的空间。transientStorePoolEnable=true则使用对外内存,如果transientStorePoolEnable=false使用pagecache空间。写到直接内存writeBuffer的数据是要通过rocketmq的内部机制将其放置fileChannel当中,并刷盘。具体的刷盘时机后文再介绍。刷盘的包括两个方面:将writeBuffer写入fileChannel当中;将fileChannel数据刷到磁盘。