第十二章-Broker-异步刷盘(二)

本文详细解读了异步刷盘在CommitRealTimeService中的实现过程,重点关注MappedFileQueue.commit方法和FileChannel.write的JVM底层实现,强调了如何通过优化减少系统调用来提升性能。
摘要由CSDN通过智能技术生成

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);
}
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多栖码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值