开发者学堂课程【RocketMQ 知识精讲与项目实战(第三阶段):存储文件内存映射-MappedFile】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/704/detail/12481
存储文件内存映射-MappedFile
内容介绍:
一.重要属性
二.初始化方式
三.MappedFile 提交
一.重要属性
MappedFile 就是 MappedFileQueue 中的每一个队列中的元素,这个类对应的是 CommitLog 中的每一个文件,这个类中重要属性信息如下:
1.int OS_PAGE_SIZE = 1024*4;
//操作系统每页大小,默认4k
刷盘时如果当前每页数据达到1024*4,可以开始给下一页写数据。
2.AtomicLong TOTAL_MAPPED_VIRTUAL_MEMORY = new AtomicLong(0);
//当前JVM实例中 MappedFile 虚拟内存的大小
3.AtomicInteger TOTAL_MAPPED_FILES = new AtomicInteger(0);
//当前JVM实例中 MappedFile 对象个数
4.AtomicInteger wrotePosition = new AtomicInteger(0);
//当前文件的写指针
5.AtomicInteger committedPosition = new AtomicInteger(0);
//当前文件的提交指针
6.AtomicInteger flushedPosition = new AtomicInteger(0);
//刷写到磁盘指针
7.int fileSize;
//文件大小
默认都是1G
8.FileChannel fileChannel;
//文件通道
9.ByteBuffer writeBuffer = null;
//堆外内存 ByteBuffer
writeBuffer 是否能够使用取决于 transientenable 控制,如果支持堆外内存缓存区的开启,这指的是堆外内存的空间
10.TransientStorePool transientStorePool = null;
//堆外内存池
堆外内存数据通过 transientStorePool 对象产生堆外内存区域
11.String fileName;
//文件名称
12.long f
ileFromOffset;
//该文件的处理偏移量
13.File file;
//物理文件
14.MappedByteBuffer mappedByteBuffer;
//物理文件对应的内存映射 Buffer
15.volatile long storeTimestamp = 0;
//文件最后一次内容写入时间
16.boolean firstCreateInQueue = false;
//是否是 MappedFileQueue 队列中第一个文件的标识,默认是false
二.初始化方式
MappedFile 中有两个 init 方法,这两个方法其实是初始化方法。这两个 init 方法的区别
一个是直接就是文件名称,文件大小
另一个重载的方法有文件名称,文件大小,还有一个transientStorePool 存储池的类。
有两种初始化方式是因为与 transientStorePoolEnable 属性有关,该属性如果开启之后,数据先存储到堆外内存中,然后再通过Commit 线程将数据提交到内存映射的方法中,再通过Flush线程将内存映射当中的 Buffer 数据持久化磁盘。所以这两种初始化方式最明显的区别就是数据看直接提交到内存映射中还是先刷写到堆外内存,然后再提交到MappedByteBuffer中。基于MappedByteBuffer,要开启该特征,就会有两种初始化方式。
三. MappedFile 提交
当数据提交到 FileChannel 之后,commitLeastPages 就为本次提交的页数,如果待提交的数据不满足本次最小提交页数,则不执行本次提交操作。当前 writeBuffer 如果为空,就没有提交这一说,对应图如下
在提交时必须满足当前提交的页数必须大于最小页数,才会出发提交的事件。如果当前能够提交,就会进入 commit0 方法提交数据。判断是否能够提交有一个 isAbleToCommit 方法,首先拿到刷盘的指针以及当前文件写的指针,然后判断如果设定了commitLeastPages,如果其大于0,需要通过分页的方式提交,计算了当前写入到 writeBuffer 中的数据与上一次刷盘的数据进行差值的比较,然后根据每一页数据量的大小计算总页数,总页数大于最小提交的页数,则直接提交。如果 commitLeastPages 小于0,如果有新数据,则直接出发提交。为了提高数据提交的性能,一般都会设计最小提交的页数。如果达到了提交条件,通过 commit0 方法进行出发然后提交。如果开启了堆外内存的共享区,会先将数据通过writeBuffer 提交到 fileChannel,如果没有 writeBuffer,则直接等待刷盘。当前提交位置的设定,commit0 方法的作用是将MappedFile中的writeBuffer中的数据提交到文件通道FileChannel 中。
接下来出发刷盘,刷写磁盘直接调用 MappedByteBuffer 或者fileChannel 中的 force 方法将内存中的数据刷写至磁盘。刷盘的位置应该等于 MappedByteBuffer 中的写指针,如果 writeBuffer不为空,也就是现在开启了堆外内存的共享区,那么刷盘的位置应该等于上一次提交的数据,因为提交的数据最终也需要刷写至磁盘中,所以根据 writeBuffer 是否存在,刷盘位置存在区别。可以根据位置查找指定的 MappedBuffer。
此外还有重要的方法 shutdown, MappedFile 文件销毁通过该方法进行。
public void shutdown(final long intervalForcibly) {
if (this.available) {
//关闭 MappedFile
this.available = false;
//设置当前关闭时间戳
this.firstShutdownTimestamp = System.currentTimeMillis();
//释放资源
this.release();
}else if (this.getRefCount() > 0) {
if ((System.currentTimeMillis() - this.firstShutdownTimestamp) >= intervalForcibly) {
this.refCount.set(-1000 -this.getRefCount());
this.release();
}
}
}
以上就是关于 MappedFile 简单的说明,主要明白几个事情。第一MappedFile 这个类对应的是 CommitLog中的每一个数据文件,MappedFile 在进行初始化时,有两种方式,第一种如果开启了堆外内存,要将堆外内存的区域初始化,如果没有开启堆外内存,直接创建对应的 MappedByteBuffer 内存映射的区域。根据是否开启堆外内存,刷盘有两种途径,第一种途径开启了堆外内存,数据先写入writeBuffer,然后再提交到 fileChannel, fileChannel 也是根据内存映射产生的。如果没有开启堆外内存,则直接写入内存文件映射中,然后刷到磁盘,这是二者最大的区别。