前言
上一篇我们提到当消息发送到Broker,Broker会调用CommitLog#putMessage()->MappedFile#appendMessage()->MappedFile#appendMessageInner()->CommitLog#doAppend()将消息追加到缓冲区,也就是MappedFile里。 当CommitLog在初始化的时候会根据MessageStoreConfig的flushDiskType配置,启动两种不同的刷盘服务。
- 同步刷盘:意思就是当消息追加到内存后,就立即刷到文件中存储。
- 异步刷盘:当消息追加到内存后,并不是立即刷到内存,而是启动异步线程操作。
RocketMq默认采采用异步刷盘,当CommitLog完成消息追加到缓冲区MappedFile后,便会调用handleDiskFlush()方法进行刷盘,将消息存储到文件中。handleDiskFlush根据两种刷盘方式处理也不一样。
探索同步刷盘
1、同步刷盘的服务是GroupCommitService,主要逻辑如下: 首先是在handleDiskFlush中提交刷盘请求,并且同步等待刷盘结果,刷盘失败也会标记消息存储失败,返回FLUSH_DISK_TIMEOUT。
2、当请求被提交到GroupCommitService后,GroupCommitService并不是立即处理,而是先放到内部的一个请求队列中,并利用waitPoint通知新请求到来。
3、当GroupCommitService被唤醒后,便会将requestWrite中的请求交换到requestHead中,避免产生锁竞争。
4、GroupCommitService在启动后便会死循环中调用doCommit()方法,而doCommit()则不断遍历requestRead中的请求,进行处理:
5、可以看到最终调用了CommitLog.this.mappedFileQueue.flush(0)来进行刷盘。
虽然同步刷盘的任务也是在异步线程里执行,但是消息存储的主流程会同步等待刷盘结果,所以本质上还是同步操作。
探索异步刷盘
异步刷盘的服务是FlushRealTimeService,不过当内存缓存池TransientStorePool可用时,消息会先提交到TransientStorePool中的WriteBuffer内部,再提交到MappedFile的FileChannel中,此时异步刷盘服务就是CommitRealTimeService,它继承自FlushRealTimeService。 1、handleDiskFlush()直接唤醒异步刷盘服务
2、FlushRealTimeService在启动后,会在死循环中周期性的进行刷盘操作。
探索MappedFile的刷盘
无论哪种刷盘,最终都调用了下面这个方法刷盘
CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages);
点进去看看这个方法都做了什么
1、从mappedFileQueue保存的所有MappedFile中,找出要刷盘的MappedFile。如果找到了对应的MappedFile,则对该MappedFile调用flush()进行刷盘,并更新flushedWhere。
2、刷盘的最终操作在MappedFile#flush()里,具体看下图
而在是否需要刷盘方法isAbleToFlush()里面的逻辑就是看MappedFile满了可以刷盘,或者当超过最小刷盘页数也可以刷盘,避免不必要的刷盘
整个消息刷盘的大致流程如下