【一起学RocketMq】消息刷盘源码分析

1,175 阅读2分钟

前言

上一篇我们提到当消息发送到Broker,Broker会调用CommitLog#putMessage()->MappedFile#appendMessage()->MappedFile#appendMessageInner()->CommitLog#doAppend()将消息追加到缓冲区,也就是MappedFile里。 当CommitLog在初始化的时候会根据MessageStoreConfig的flushDiskType配置,启动两种不同的刷盘服务。

image.png

  • 同步刷盘:意思就是当消息追加到内存后,就立即刷到文件中存储。
  • 异步刷盘:当消息追加到内存后,并不是立即刷到内存,而是启动异步线程操作。

RocketMq默认采采用异步刷盘,当CommitLog完成消息追加到缓冲区MappedFile后,便会调用handleDiskFlush()方法进行刷盘,将消息存储到文件中。handleDiskFlush根据两种刷盘方式处理也不一样。

image.png

探索同步刷盘

1、同步刷盘的服务是GroupCommitService,主要逻辑如下: 首先是在handleDiskFlush中提交刷盘请求,并且同步等待刷盘结果,刷盘失败也会标记消息存储失败,返回FLUSH_DISK_TIMEOUT。

image.png

2、当请求被提交到GroupCommitService后,GroupCommitService并不是立即处理,而是先放到内部的一个请求队列中,并利用waitPoint通知新请求到来。

image.png

3、当GroupCommitService被唤醒后,便会将requestWrite中的请求交换到requestHead中,避免产生锁竞争。

image.png

4、GroupCommitService在启动后便会死循环中调用doCommit()方法,而doCommit()则不断遍历requestRead中的请求,进行处理:

image.png

5、可以看到最终调用了CommitLog.this.mappedFileQueue.flush(0)来进行刷盘。

image.png

虽然同步刷盘的任务也是在异步线程里执行,但是消息存储的主流程会同步等待刷盘结果,所以本质上还是同步操作。

探索异步刷盘

异步刷盘的服务是FlushRealTimeService,不过当内存缓存池TransientStorePool可用时,消息会先提交到TransientStorePool中的WriteBuffer内部,再提交到MappedFile的FileChannel中,此时异步刷盘服务就是CommitRealTimeService,它继承自FlushRealTimeService。 1、handleDiskFlush()直接唤醒异步刷盘服务

image.png

2、FlushRealTimeService在启动后,会在死循环中周期性的进行刷盘操作。

image.png

image.png

探索MappedFile的刷盘

无论哪种刷盘,最终都调用了下面这个方法刷盘

CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages);

点进去看看这个方法都做了什么
1、从mappedFileQueue保存的所有MappedFile中,找出要刷盘的MappedFile。如果找到了对应的MappedFile,则对该MappedFile调用flush()进行刷盘,并更新flushedWhere。

image.png

2、刷盘的最终操作在MappedFile#flush()里,具体看下图

image.png

而在是否需要刷盘方法isAbleToFlush()里面的逻辑就是看MappedFile满了可以刷盘,或者当超过最小刷盘页数也可以刷盘,避免不必要的刷盘

image.png

整个消息刷盘的大致流程如下

image.png