源码分析RocketMQ刷盘机制

一个人想着一个人 提交于 2021-02-14 21:36:01

刷盘机制支持同步刷盘和异步刷盘。为了了解其具体事项,我们以Commitlog的存储为例来说明RocketMQ是如何进行磁盘读写。

Comitlog#putMessage 首先,主要是将消息写入到MappedFile,内存映射文件。然后根据刷盘策略刷写到磁盘,入口:

CommitLog#putMessage handleDiskFlush

CommitLog#handleDiskFlush

 

 
  1. public void handleDiskFlush(AppendMessageResult result, PutMessageResult putMessageResult, MessageExt messageExt) { // @1

  2. // Synchronization flush

  3. if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) { // @2

  4. final GroupCommitService service = (GroupCommitService) this.flushCommitLogService;

  5. if (messageExt.isWaitStoreMsgOK()) {

  6. GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes());

  7. service.putRequest(request);

  8. boolean flushOK = request.waitForFlush(this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());

  9. if (!flushOK) {

  10. log.error("do groupcommit, wait for flush failed, topic: " + messageExt.getTopic() + " tags: " + messageExt.getTags()

  11. + " client address: " + messageExt.getBornHostString());

  12. putMessageResult.setPutMessageStatus(PutMessageStatus.FLUSH_DISK_TIMEOUT);

  13. }

  14. } else {

  15. service.wakeup();

  16. }

  17. }

  18. // Asynchronous flush

  19. else { // @3

  20. if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {

  21. flushCommitLogService.wakeup();

  22. } else {

  23. commitLogService.wakeup();

  24. }

  25. }

  26. }

 

代码@1 参数详解

AppendMessageResult result 写入到MappedFile(内存映射文件中,bytebuffer)中的结果,具体属性包含:

 

wroteOffset : 下一个写入的偏移量

wroteBytes : 写入字节总长度

msgId : 消息id

storeTimestamp : 消息存储时间,也就是写入到MappedFile中的时间

logicOffset : 逻辑的consumeque 偏移量

pagecacheRT : 写入到MappedByteBuffer(将消息内容写入到内存映射文件中的时长)

代码@2 同步刷盘

代码@3 异步刷盘

1、同步刷盘线程

同步刷盘机制,核心实现类 CommitLog#GroupCommitService

 

同步刷盘核心类,竟然是一个线程,出乎我的意料。

1.1 核心属性

private volatile List<GroupCommitRequest> requestsWrite = new ArrayList<GroupCommitRequest>();

private volatile List<GroupCommitRequest> requestsRead = new ArrayList<GroupCommitRequest>();

requestsWrite : 写队列,主要用于向该线程添加刷盘任务

requestsRead :读队列,主要用于执行特定的刷盘任务,这是是GroupCommitService 设计的一个亮点,把读写分离,每处理完requestsRead中的任务,就交换这两个队列。

1.2 对外方法putRequest,添加刷盘任务

 
  1. public synchronized void putRequest(final GroupCommitRequest request) {

  2. synchronized (this.requestsWrite) {

  3. this.requestsWrite.add(request);

  4. }

  5. if (hasNotified.compareAndSet(false, true)) {

  6. waitPoint.countDown(); // notify

  7. }

  8. }

该方法是很简单,就是将GroupCommitRequest刷盘任务放入到requestWrite中,就返回了,但是这个类是处理同步刷盘的,那调用方什么时候才能知道该刷盘任务已经执行了呢?

不然能说是同步刷盘呢?这又是这个类另外一个设计亮点。为了解开这个疑点,首先看一下调用方法:

GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes());

service.putRequest(request);

boolean flushOK = request.waitForFlush(this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());

原来奥秘在这里,放入后,request.waitForFlush,类似于Future模式,在这方法里进行阻塞等待。

this.countDownLatch.await(timeout, TimeUnit.MILLISECONDS),默认同步刷盘超时时间为5s,那就不需要怀疑了,刷盘后,肯定会调用countDownLatch.countDown()

GroupCommitRequest 具体类的工作机制就不细说了,其刷盘将调用的方法为:CommitLog.this.mappedFileQueue.flush(0);

在进入具体刷盘逻辑之前,我们再看下异步刷盘线程的实现。

2、异步刷盘线程

 
  1. if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {

  2. flushCommitLogService.wakeup();

  3. } else {

  4. commitLogService.wakeup();

  5. }

  6. public boolean isTransientStorePoolEnable() {

  7. return transientStorePoolEnable && FlushDiskType.ASYNC_FLUSH == getFlushDiskType()

  8. && BrokerRole.SLAVE != getBrokerRole();

  9. }

什么是transientStorePoolEnable ,这个只能从FlushRealTimeService 与 CommitRealTimeService 区别

2.1 FlushRealTimeService 实现机制

 
  1. class FlushRealTimeService extends FlushCommitLogService {

  2. private long lastFlushTimestamp = 0;

  3. private long printTimes = 0;

  4.  
  5. public void run() {

  6. CommitLog.log.info(this.getServiceName() + " service started");

  7.  
  8. while (!this.isStopped()) {

  9. boolean flushCommitLogTimed = CommitLog.this.defaultMessageStore.getMessageStoreConfig().isFlushCommitLogTimed(); // @1

  10.  
  11. int interval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushIntervalCommitLog(); // @2

  12. int flushPhysicQueueLeastPages = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogLeastPages(); // @3

  13.  
  14. int flushPhysicQueueThoroughInterval =

  15. CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogThoroughInterval(); // @4

  16.  
  17. boolean printFlushProgress = false;

  18.  
  19. // Print flush progress

  20. long currentTimeMillis = System.currentTimeMillis();

  21. if (currentTimeMillis >= (this.lastFlushTimestamp + flushPhysicQueueThoroughInterval)) {

  22. this.lastFlushTimestamp = currentTimeMillis;

  23. flushPhysicQueueLeastPages = 0;

  24. printFlushProgress = (printTimes++ % 10) == 0;

  25. }

  26.  
  27. try {

  28. if (flushCommitLogTimed) {

  29. Thread.sleep(interval);

  30. } else {

  31. this.waitForRunning(interval);

  32. }

  33.  
  34. if (printFlushProgress) {

  35. this.printFlushProgress();

  36. }

  37.  
  38. long begin = System.currentTimeMillis();

  39. CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages);

  40. long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();

  41. if (storeTimestamp > 0) {

  42. CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);

  43. }

  44. long past = System.currentTimeMillis() - begin;

  45. if (past > 500) {

  46. log.info("Flush data to disk costs {} ms", past);

  47. }

  48. } catch (Throwable e) {

  49. CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);

  50. this.printFlushProgress();

  51. }

  52. }

  53.  
  54. // Normal shutdown, to ensure that all the flush before exit

  55. boolean result = false;

  56. for (int i = 0; i < RETRY_TIMES_OVER && !result; i++) {

  57. result = CommitLog.this.mappedFileQueue.flush(0);

  58. CommitLog.log.info(this.getServiceName() + " service shutdown, retry " + (i + 1) + " times " + (result ? "OK" : "Not OK"));

  59. }

  60.  
  61. this.printFlushProgress();

  62.  
  63. CommitLog.log.info(this.getServiceName() + " service end");

  64. }

代码@1:flushCommitLogTimed 这个主要是等待方法,如果为true,则使用Thread.sleep,如果是false使用waitForRunning

代码@2:interval :获取刷盘的间隔时间

代码@3:flushPhysicQueueLeastPages 每次刷盘最少需要刷新的页,(如果少于,是不是可以不放弃本次刷盘操作

代码@4:flushPhysicQueueThoroughInterval 如果上次刷新的时间+该值 小于当前时间,则改变flushPhysicQueueLeastPages =0,并每10次输出异常刷新进度。

代码@5:CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages); 调用刷盘操作

代码@6:设置检测点的StoreCheckpoint 的physicMsgTimestamp(commitlog文件的检测点,也就是记录最新刷盘的时间戳)

暂时不深入,在本节之后详细分析刷盘机制。

2.2 CommitRealTimeService

 
  1. public void run() {

  2. CommitLog.log.info(this.getServiceName() + " service started");

  3. while (!this.isStopped()) {

  4. int interval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getCommitIntervalCommitLog(); // @1

  5.  
  6. int commitDataLeastPages = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getCommitCommitLogLeastPages(); // @2

  7.  
  8. int commitDataThoroughInterval =

  9. CommitLog.this.defaultMessageStore.getMessageStoreConfig().getCommitCommitLogThoroughInterval(); // @3

  10.  
  11. long begin = System.currentTimeMillis();

  12. if (begin >= (this.lastCommitTimestamp + commitDataThoroughInterval)) {

  13. this.lastCommitTimestamp = begin;

  14. commitDataLeastPages = 0;

  15. }

  16.  
  17. try {

  18. boolean result = CommitLog.this.mappedFileQueue.commit(commitDataLeastPages);

  19. long end = System.currentTimeMillis();

  20. if (!result) {

  21. this.lastCommitTimestamp = end; // result = false means some data committed.

  22. //now wake up flush thread.

  23. flushCommitLogService.wakeup();

  24. }

  25.  
  26. if (end - begin > 500) {

  27. log.info("Commit data to file costs {} ms", end - begin);

  28. }

  29. this.waitForRunning(interval);

  30. } catch (Throwable e) {

  31. CommitLog.log.error(this.getServiceName() + " service has exception. ", e);

  32. }

  33. }

  34.  
  35. boolean result = false;

  36. for (int i = 0; i < RETRY_TIMES_OVER && !result; i++) {

  37. result = CommitLog.this.mappedFileQueue.commit(0);

  38. CommitLog.log.info(this.getServiceName() + " service shutdown, retry " + (i + 1) + " times " + (result ? "OK" : "Not OK"));

  39. }

  40. CommitLog.log.info(this.getServiceName() + " service end");

  41. }

  42. }

代码@1:interval CommitRealTimeService 执行间隔

代码@2:commitDataLeastPages :每次commit最少的页数

代码@3:上上面的对应,,CommitRealTimeService与FlushRealTimeService 不同之处,是调用的方法不一样,

FlushRealTimeService 调用mappedFileQueue.flush,而CommitRealTimeService调用commit方法。

行文至此,我们只是了解异步刷盘,同步刷盘去线程的实现方式,接下来,是时候进入到刷盘具体逻辑,也就是Commitlog mappedFileQueue

3、刷盘机制实现

具体实现类:MappedFileQueue

3.1 核心属性与构造方法

 
  1. private static final int DELETE_FILES_BATCH_MAX = 10;

  2.  
  3. private final String storePath;

  4.  
  5. private final int mappedFileSize;

  6.  
  7. private final CopyOnWriteArrayList<MappedFile> mappedFiles = new CopyOnWriteArrayList<MappedFile>();

  8.  
  9. private final AllocateMappedFileService allocateMappedFileService;

  10.  
  11. private long flushedWhere = 0;

  12. private long committedWhere = 0;

  13.  
  14. private volatile long storeTimestamp = 0;

  15.  
  16. public MappedFileQueue(final String storePath, int mappedFileSize,

  17. AllocateMappedFileService allocateMappedFileService) {

  18. this.storePath = storePath;

  19. this.mappedFileSize = mappedFileSize;

  20. this.allocateMappedFileService = allocateMappedFileService;

  21. }

MappedFileQueue 就是MappedFile的队列,也就是MappedFile的容器。

storePath:文件存储路径

mappedFileSize:单个MappedFile文件长度

mappedFiles :mappedFile集合

allocateMappedFileService: 创建MappedFileService

flushedWhere :刷盘位置

committedWhere :commit位置

 

我们先看一下MappedFileQueue在什么时候创建:

我们以commitlog为例:

this.mappedFileQueue = new MappedFileQueue(defaultMessageStore.getMessageStoreConfig().getStorePathCommitLog(),

defaultMessageStore.getMessageStoreConfig().getMapedFileSizeCommitLog(), defaultMessageStore.getAllocateMappedFileService());

其中allocateMappedFileService 为AllocateMappedFileService

MappedFileQueue 就是MappedFile的队列,也就是MappedFile的容器。

storePath:文件存储路径

mappedFileSize:单个MappedFile文件长度

mappedFiles :mappedFile集合

allocateMappedFileService: 创建MappedFileService

flushedWhere :刷盘位置

committedWhere :commit位置

3.2 核心方法:load

 
  1. public boolean load() {

  2. File dir = new File(this.storePath);

  3. File[] files = dir.listFiles();

  4. if (files != null) {

  5. // ascending order

  6. Arrays.sort(files);

  7. for (File file : files) {

  8.  
  9. if (file.length() != this.mappedFileSize) {

  10. log.warn(file + "\t" + file.length()

  11. + " length not matched message store config value, ignore it");

  12. return true;

  13. }

  14.  
  15. try {

  16. MappedFile mappedFile = new MappedFile(file.getPath(), mappedFileSize);

  17.  
  18. mappedFile.setWrotePosition(this.mappedFileSize);

  19. mappedFile.setFlushedPosition(this.mappedFileSize);

  20. mappedFile.setCommittedPosition(this.mappedFileSize);

  21. this.mappedFiles.add(mappedFile);

  22. log.info("load " + file.getPath() + " OK");

  23. } catch (IOException e) {

  24. log.error("load file " + file + " error", e);

  25. return false;

  26. }

  27. }

  28. }

  29.  
  30. return true;

  31. }

// 该方法主要是按顺序,创建MappedFile,此时这个时候关注一下,初始化时wrotePosition,flushedPosition,committedPosition全设置为最大值,这要怎么玩呢?是否还记得启动时需要恢复commitlog,consume,index文件,(recover)方法,在删除无效文件时,会重置上述值

接下来,我们先梳理一下目前刷盘出现的关键属性,然后进入到刷盘机制的世界中来:

1、MappedFileQueue 与MappedFile的关系

可以这样认为,MappedFile代表一个个物理文件,而MappedFileQueue代表由一个个MappedFile组成的一个连续逻辑的大文件。

并且每一个MappedFile的命名已该文件在整个文件序列中的偏移量来表示。

2、MappedFileQueue

flushedWhere: 整个刷新的偏移量,针对该MappedFileQueue

committedWhere:当前提交的偏移量,针对该MappedFileQueue commit与flush的区别?

3、MappedFile

wrotePosition :当前待写入位置

committedPosition 提交位置

flushedPosition 刷新位置 应该是 commitedPosition <= flushedPosition

接下来,主要来看MappedFileQueue comit flush方法

3.3 MappedFileQueue #commit

 
  1. public boolean commit(final int commitLeastPages) {

  2. boolean result = true;

  3. MappedFile mappedFile = this.findMappedFileByOffset(this.committedWhere, false); // @1

  4. if (mappedFile != null) {

  5. int offset = mappedFile.commit(commitLeastPages); // @2

  6. long where = mappedFile.getFileFromOffset() + offset; // @3

  7. result = where == this.committedWhere; // @4

  8. this.committedWhere = where; // @5

  9. }

  10.  
  11. return result;

  12. }

代码@1:根据committedWhere 找到具体的MappedFile文件

代码@2:调用MappedFile的commit函数

代码@3,mappedFile返回的应该是当前commit的偏移量,加上该文件开始的偏移,,表示MappedFileQueue当前的提交偏移量

代码@4:如果result = true,则可以认为MappedFile#commit 本次并没有执行commit操作

代码@5,更新当前的ccomitedWhere指针。

接下来继续查看MappedFile#commit的实现:

MappedFile#commit()

 
  1. public int commit(final int commitLeastPages) { // @1

  2. if (writeBuffer == null) { // @2

  3. //no need to commit data to file channel, so just regard wrotePosition as committedPosition.

  4. return this.wrotePosition.get();

  5. }

  6. if (this.isAbleToCommit(commitLeastPages)) { // @3

  7. if (this.hold()) {

  8. commit0(commitLeastPages);

  9. this.release();

  10. } else {

  11. log.warn("in commit, hold failed, commit offset = " + this.committedPosition.get());

  12. }

  13. }

  14.  
  15. // All dirty data has been committed to FileChannel.

  16. if (writeBuffer != null && this.transientStorePool != null && this.fileSize == this.committedPosition.get()) {

  17. this.transientStorePool.returnBuffer(writeBuffer);

  18. this.writeBuffer = null;

  19. }

  20.  
  21. return this.committedPosition.get();

  22. }

代码@1:参数,commitLeastPages 至少提交的页数,如果当前需要提交的数据所占的页数小于commitLeastPages ,则不执行本次提交操作

代码@2:如果writeBuffer 等于null,则表示IO操作都是直接基于FileChannel,所有,此时返回当前可写的位置,作为committedPosition即可,这里应该就有点commit是个啥意思了,

如果数据先写入到writeBuffer中,则需要提交到FileChannel(MappedByteBuffer mappedByteBuffer)

代码@3:判断是否可以执行提交操作

 
  1. protected boolean isAbleToCommit(final int commitLeastPages) {

  2. int flush = this.committedPosition.get();

  3. int write = this.wrotePosition.get();

  4.  
  5. if (this.isFull()) {

  6. return true;

  7. }

  8.  
  9. if (commitLeastPages > 0) {

  10. return ((write / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE)) >= commitLeastPages;

  11. }

  12.  
  13. return write > flush;

  14. }

从代码可以看出

@1 如果文件写满(this.fileSize == this.wrotePosition.get()) 则可以执行commit

@2 如果有最小提交页数要求,则(当前写入位置/ pagesize(4k) - 当前flush位置/pagesize(4k) 大于commitLeastPages时,再提交。

@3,如果没有最新提交页数要求,则只有当前写入位置大于flush,则可提交。

代码@4:执行具体的提交操作

 
  1. protected void commit0(final int commitLeastPages) {

  2. int writePos = this.wrotePosition.get();

  3. int lastCommittedPosition = this.committedPosition.get();

  4.  
  5. if (writePos - this.committedPosition.get() > 0) {

  6. try {

  7. ByteBuffer byteBuffer = writeBuffer.slice(); // @1

  8. byteBuffer.position(lastCommittedPosition);

  9. byteBuffer.limit(writePos);

  10. this.fileChannel.position(lastCommittedPosition);

  11. this.fileChannel.write(byteBuffer); // @2

  12. this.committedPosition.set(writePos); // @3

  13. } catch (Throwable e) {

  14. log.error("Error occurred when commit data to FileChannel.", e);

  15. }

  16. }

  17. }

代码@1,这里使用slice方法,主要是用的同一片内存空间,但单独的指针。

代码@2:将bytebuf当前 上一次commitedPosition + 当前写位置这些数据全部写入到FileChannel中,commit的左右原来是这要的,是将writeBuffer中的数据写入到FileChannel中

代码@3:更新committedPosition的位置。

讲到这里,commit 的作用就非常明白了,为了加深理解,该是来理解MappedFile几个核心属性的时候了。

protected int fileSize; // 文件的大小

protected FileChannel fileChannel; // 文件通道

/**

* Message will put to here first, and then reput to FileChannel if writeBuffer is not null.

*/

protected ByteBuffer writeBuffer = null; // 如果不为空,内容写写入到writeBuffer,然后再重新放入到FileChannel中,这个重新放入,其实就是commit操作。

protected TransientStorePool transientStorePool = null; // 临时存储,只有pool不为空,wrtieBuffer才不会为空,也就是MessageStoreConfig中transientStorePoolEnable设置为true时

// 才会生效,也是writeBuffer的容器。也就是writeBuffer其实就是堆内内存,如果transientStorePoolEnable为true,消息是先直

// 接放入到堆内存中,然后定时commit到堆外内存(FileChannel,MappedByteBuffer)中,再定时flush.

private long fileFromOffset; // 文件初始偏移量(在整个MappedFile链表中的逻辑偏移量)

private File file; // 物理文件

private MappedByteBuffer mappedByteBuffer; // mappedByteBufer,FileChanel的内存映射。

如果启用了MessageStoreConfig的transientStorePoolEnable=true,消息在追加时,先放入到writeBuffer中,然后定时commit到FileChannel,,然后定时flush,如果transientStorePoolEnable=false(默认)则消息追加时,直接存入MappedByteBuffer中,然后定时flush ,【备注,说的是异步刷盘,如果是同步刷盘】,应该是直接调用flush方法。

接下来我们再看一下flush方法,其实基本明了了,就是调用FileChannel的force()方法。

3.4 MappedFileQueue#flush MappedFile#flush

 
  1. public boolean flush(final int flushLeastPages) {

  2. boolean result = true;

  3. MappedFile mappedFile = this.findMappedFileByOffset(this.flushedWhere, false);

  4. if (mappedFile != null) {

  5. long tmpTimeStamp = mappedFile.getStoreTimestamp();

  6. int offset = mappedFile.flush(flushLeastPages);

  7. long where = mappedFile.getFileFromOffset() + offset;

  8. result = where == this.flushedWhere;

  9. this.flushedWhere = where;

  10. if (0 == flushLeastPages) {

  11. this.storeTimestamp = tmpTimeStamp;

  12. }

  13. }

  14.  
  15. return result;

  16. }

  17. /**

  18. * @return The current flushed position

  19. */

  20. public int flush(final int flushLeastPages) {

  21. if (this.isAbleToFlush(flushLeastPages)) {

  22. if (this.hold()) {

  23. int value = getReadPosition();

  24.  
  25. try {

  26. //We only append data to fileChannel or mappedByteBuffer, never both.

  27. if (writeBuffer != null || this.fileChannel.position() != 0) {

  28. this.fileChannel.force(false);

  29. } else {

  30. this.mappedByteBuffer.force();

  31. }

  32. } catch (Throwable e) {

  33. log.error("Error occurred when force data to disk.", e);

  34. }

  35.  
  36. this.flushedPosition.set(value);

  37. this.release();

  38. } else {

  39. log.warn("in flush, hold failed, flush offset = " + this.flushedPosition.get());

  40. this.flushedPosition.set(getReadPosition());

  41. }

  42. }

  43. return this.getFlushedPosition();

  44. }

具体代码很好理解,就不一一分析了。

RocketMQ的刷盘机制就介绍到这,我们再简单做个总结

先讲一下RocketMQ的存储设计亮点:(以CommitLog为例)

单个commitlog文件,默认大小为1G,由多个commitlog文件来存储所有的消息,commitlog文件的命名以该文件在整个commitlog中的偏移量来命名,举例如下,

例如一个commitlog文件,1024个字节,

第一个文件: 00000000000000000000

第二个文件: 00000000000000001024

MappedFile封装一个一个的CommitLog文件,而MappedFileQueue就是封装的就是一个逻辑的commitlog文件。mappedFile队列,从小到大排列。

使用内存映射机制,MappedByteBuffer,具体封装类为MappedFile。

1、同步刷盘 每次发送消息,消息都直接存储在FileChannel中,使用的是(MapFile的mappdByteBuffer),然后直接调用force()方法刷写到磁盘,等到force刷盘成功后,再返回给调用发(GroupCommitRequest#waitForFlush)就是其同步调用的实现。

2、异步刷盘

分为两种情况,是否开启堆内存缓存池,具体配置参数:MessageStoreConfig#transientStorePoolEnable

transientStorePoolEnable=true

消息在追加时,先放入到writeBuffer中,然后定时commit到FileChannel,,然后定时flush,

transientStorePoolEnable=false(默认)

消息追加时,直接存入MappedByteBuffer中,然后定时flush ,【备注,说的是异步刷盘,如果是同步刷盘】,应该是直接调用flush方法。

 

MappedFile 重要的指针

wrotePosition:当前写入的指针

committedPosition : 上一次提交的指针 (transientStorePoolEnable=true时有效)

flushedPosition : 上一次flush的指针

OS_PAGE_SIZE = 1024 * 4 : 一页大小,4K

flushedPosition <= committedPosition <= wrotePosition <= fileSIze

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!