1 概述
RocketMQ和其他存储系统类似,如Redis等,提供了同步和异步两种刷盘方式,同步刷盘方式能够保证数据被写入硬盘,做到真正的持久化,但是也会让系统的写入速度受制于磁盘的IO速度;而异步刷盘方式在将数据写入缓冲之后就返回,提供了系统的IO速度,却存在系统发生故障时未来得及写入硬盘的数据丢失的风险。
RocketMQ提供了SYNC_FLUSH
和ASYNC_FLUSH
两种方式,也即同步和异步刷盘方式,同步刷盘在写入消息后会等待刷盘进度大于等于当前写入经度之后返回,而异步刷盘则在写入消息之后直接返回,不再等待刷盘进度。
在阅读本文前可先看文章RocketMQ源码-MappedFile介绍,了解其中介绍的暂存池相关原理以及具体刷盘操作时commit
和flush
动作的区别,本文在介绍刷盘时则不再赘述。
其实同步刷盘、异步刷盘和我们在文章RocketMQ源码-主从同步复制和异步复制介绍的同步复制、异步复制原理基本相同,同步刷盘也是阻塞等待当前刷盘进度大于等于此次写入进度然后返回,而异步刷盘写入之后直接返回,由后台线程定时进行刷盘动作。
2 相关类介绍
GroupCommitService
如果配置的刷盘方式为同步方式,即SYNC_FLUSH
,那么根据我们在文章RocketMQ源码-MappedFile介绍第8节介绍的注意事项可知,该配置肯定不会启用MappedFile
的暂存池TransientStorePool
功能。而GroupCommitService
就是用于同步刷盘时进行实际的刷盘动作。
FlushRealTimeService
用于没有启用暂存池的异步刷盘动作,主要是定时触发flush
动作。
CommitRealTimeService
用于启用了暂存池的异步刷盘动作,和FlushRealTimeService
不同的是,CommitRealTimeService
在刷盘时会先将从暂存池借用的ByteBuffer
中的数据commit
到fileChannel
中,然后调用flush
对fileChannel
进行刷盘操作。
3 同步刷盘原理
CommitLog.putMessage
在写入消息之后,会调用handleDiskFlush
进行刷盘相关处理,该方法实现如下:
//CommitLog
public void handleDiskFlush(AppendMessageResult result, PutMessageResult putMessageResult, MessageExt messageExt) {
// Synchronization flush
//如果是同步刷盘,则进入阻塞等待刷盘进度大于当前写入进度
if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
//获取负责刷盘的服务,根据上面相关类介绍,如果是同步刷盘
//方式此处获取的服务为GroupCommitService类实例
final GroupCommitService service = (GroupCommitService) this.flushCommitLogService;
if (messageExt.isWaitStoreMsgOK()) {
//这个和主从Broker同步复制使用同样的类,该类主要记录
//了当前写入后需要等待刷盘的进度,只有达到该进度才
//从阻塞中返回
GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes());
service.putRequest(request);
//等待同步刷盘任务完成或发生失败
boolean flushOK = request.waitForFlush(this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
if (!flushOK) {
log.error("do groupcommit, wait for flush failed, topic: " + messageExt.getTopic() + " tags: " + messageExt.getTags()
+ " client address: " + messageExt.getBornHostString());
putMessageResult.setPutMessageStatus(PutMessageStatus.FLUSH_DISK_TIMEOUT);
}
} else {
service.wakeup();
}
}
// Asynchronous flush
else {
//如果是异步刷盘,则唤醒相关的服务,这里根据是否启用了暂存
//池调用不同的服务进行后台刷盘动作
if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
flushCommitLogService.wakeup();
} else {
commitLogService.wakeup();
}
}
}
相关的服务源码比较简单,和主从同步复制及其类似,这里不再介绍,建议阅读文章RocketMQ源码-主从同步复制和异步复制做对比理解。
4 异步刷盘
异步刷盘则写入消息之后直接返回,由ServiceThread
实现类FlushRealTimeService
以及CommitRealTimeService
在后台根据配置的刷盘频率进行异步刷盘,FlushRealTimeService
对未启用暂存池的MappedFile
进行刷盘,而CommitRealTimeService
则对启用了暂存池的MappedFile
进行刷盘。