12.1.2 异步刷盘
异步刷盘是通过 CommitRealTimeService 服务来实现的,话不多说,直接上源码
class CommitRealTimeService extends FlushCommitLogService {
private long lastCommitTimestamp = 0;
@Override
public String getServiceName() {
return CommitRealTimeService.class.getSimpleName();
}
// 任务执行方法
@Override
public void run() {
CommitLog.log.info(this.getServiceName() + " service started");
while (!this.isStopped()) { // 状态判断
// 任务执行间隔,默认200ms
int interval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getCommitIntervalCommitLog();
// 默认提交的最小 page 块数,默认为4
int commitDataLeastPages = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getCommitCommitLogLeastPages();
// 数据提交的间隔时间,默认也是200ms
int commitDataThoroughInterval =
CommitLog.this.defaultMessageStore.getMessageStoreConfig().getCommitCommitLogThoroughInterval();
long begin = System.currentTimeMillis();
// 判断当下提交时间与上一次的时间间隔是否超过200ms,超过了,commitDataLeastPages 置为 0,意思是真实写入文件的page块数不做限制,如果没超过,200ms内只写入 commitDataLeastPages 块page,做这一步其实就是为了性能考虑,减少刷盘的次数,因为刷盘都属于系统调用,系统调用需要消耗一定性能,频繁刷盘会影响性能。
if (begin >= (this.lastCommitTimestamp + commitDataThoroughInterval)) {
this.lastCommitTimestamp = begin;
commitDataLeastPages = 0;
}
try {
// 去刷盘
boolean result = CommitLog.this.mappedFileQueue.commit(commitDataLeastPages);
long end = System.currentTimeMillis();
if (!result) {
// 失败,意味着数据已经刷过了
this.lastCommitTimestamp = end; // result = false means some data committed.
// 再次唤醒刷盘线程
flushCommitLogService.wakeup();
}
if (end - begin > 500) { // 处理时间超过 500ms,要日志记录下来
log.info("Commit data to file costs {} ms", end - begin);
}
// 刷盘成功,下一次刷盘前千金等待 interval 时间
this.waitForRunning(interval);
} catch (Throwable e) {
CommitLog.log.error(this.getServiceName() + " service has exception. ", e);
}
}
// 这是针对线程在stop过程中,进行的收尾工作
boolean result = false;
// 最大重试10次刷盘
for (int i = 0; i < RETRY_TIMES_OVER && !result; i++) {
result = CommitLog.this.mappedFileQueue.commit(0);
CommitLog.log.info(this.getServiceName() + " service shutdown, retry " + (i + 1) + " times " + (result ? "OK" : "Not OK"));
}
CommitLog.log.info(this.getServiceName() + " service end");
}
}
MappedFileQueue.commit
public boolean commit(final int commitLeastPages) {
boolean result = true;
// 根据刷新的位置,确定要刷新的是哪个文件的映射
MappedFile mappedFile = this.findMappedFileByOffset(this.committedWhere, this.committedWhere == 0);
if (mappedFile != null) {
// 刷盘操作,重点看这一句,继续往里跟
int offset = mappedFile.commit(commitLeastPages);
// 刷盘成功后要增加已被刷的位置,用于下次刷盘的判断
long where = mappedFile.getFileFromOffset() + offset;
result = where == this.committedWhere;
this.committedWhere = where;
}
return result;
}
MappedFile.commit
public int commit(final int commitLeastPages) {
if (writeBuffer == null) {
//no need to commit data to file channel, so just regard wrotePosition as committedPosition.
return this.wrotePosition.get();
}
if (this.isAbleToCommit(commitLeastPages)) {
if (this.hold()) {
// 其他逻辑跟同步刷盘中 flush 功能一样,只有这一行不同,继续往里探
commit0(commitLeastPages);
this.release();
} else {
log.warn("in commit, hold failed, commit offset = " + this.committedPosition.get());
}
}
// All dirty data has been committed to FileChannel.
if (writeBuffer != null && this.transientStorePool != null && this.fileSize == this.committedPosition.get()) {
this.transientStorePool.returnBuffer(writeBuffer);
this.writeBuffer = null;
}
return this.committedPosition.get();
}
protected void commit0(final int commitLeastPages) {
int writePos = this.wrotePosition.get();
int lastCommittedPosition = this.committedPosition.get();
if (writePos - this.committedPosition.get() > 0) {
try {
ByteBuffer byteBuffer = writeBuffer.slice();
byteBuffer.position(lastCommittedPosition);
byteBuffer.limit(writePos);
this.fileChannel.position(lastCommittedPosition);
// 写入文件,也可以看下jvm的实现
this.fileChannel.write(byteBuffer);
this.committedPosition.set(writePos);
} catch (Throwable e) {
log.error("Error occurred when commit data to FileChannel.", e);
}
}
}
FileChannel.write的JVM实现
该源码在JVM的FileDispatcherImpl.c文件中
#define pwrite64 pwrite // 宏定义 pwrite64 表示 pwrite 方法
JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileDispatcherImpl_pwrite0(JNIEnv *env, jclass clazz, jobject fdo,
jlong address, jint len, jlong offset)
{
jint fd = fdval(env, fdo);
void *buf = (void *)jlong_to_ptr(address);
// 调用 pwrite 方法,这是一个系统函数,跟write一样,区别就在于p,
return convertReturnVal(env, pwrite64(fd, buf, len, offset), JNI_FALSE);
}