RocketMQ源码:broker 文件刷盘机制

RocketMQ的存储与读写是基于JDK NIO的内存映射机制(MappedByteBuffer)的,消息存储时首先将消息追加到内存中,再根据配置的刷盘策略在不同时间刷盘。

  • 如果是同步刷盘,消息追加到内存后,将同步调用MappedByteBufferforce()方法;
  • 如果是异步刷盘,在消息追加到内存后会立刻返回给消息发送端。

RocketMQ使用一个单独的线程按照某一个设定的频率执行刷盘操作。通过在broker配置文件中配置flushDiskType来设定刷盘方式,可选值为ASYNC_FLUSH(异步刷盘)、SYNC_FLUSH(同步刷盘),默认为异步刷盘

本节以CommitLog文件刷盘机制为例来剖析RocketMQ的刷盘机制,ConsumeQueue文件、Index文件刷盘的实现原理与CommitLog刷盘机制类似。

RocketMQ处理刷盘的实现方法为Commitlog#handleDiskFlush(),刷盘流程作为消息发送、消息存储的 子流程。值得注意的是,Index文件的刷盘并不是采取定时刷盘机制,而是每更新一次Index文 件就会将上一次的改动写入磁盘

1. 刷盘策略

在理解RocketMQ刷盘实现之前,先理解一下上图展示的刷盘的2种实现的:

  1. 直接通过内存映射文件,通过flush刷新到磁盘

  2. 当异步刷盘且启用了对外内存池的时候,先writewriteBuffer,然后commitFilechannel,最后flush到磁盘

CommitLogasyncPutMessage方法中可以看到在写入消息之后,调用了submitFlushRequest方法执行刷盘策略:

public CompletableFuture<PutMessageResult> asyncPutMessage(final MessageExtBrokerInner msg) {
    ...
    // 获取最后一个 MappedFile
    MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile();
    ...
    try {
        ...
        // todo 往mappedFile追加消息
        result = mappedFile.appendMessage(msg, this.appendMessageCallback);
        ...
    } finally {
        putMessageLock.unlock();
    }

    ...
    // todo 消息首先进入pagecache,然后执行刷盘操作,
    CompletableFuture<PutMessageStatus> flushResultFuture = submitFlushRequest(result, msg);
    ...
}
复制代码

刷盘有两种策略:

  • 同步刷盘,表示消息写入到内存之后需要立刻刷到磁盘文件中。

    同步刷盘会构建GroupCommitRequest组提交请求并设置本次刷盘后的位置偏移量的值(写入位置偏移量+写入数据字节数),然后将请求添加到GroupCommitService中进行刷盘。

  • 异步刷盘,表示消息写入内存成功之后就返回,由MQ定时将数据刷入到磁盘中,会有一定的数据丢失风险

CommitLog#submitFlushRequest如下:

public CompletableFuture<PutMessageStatus> submitFlushRequest(AppendMessageResult result, MessageExt messageExt) {
    // Synchronization flush 同步刷盘
    if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
        // 获取GroupCommitService
        final GroupCommitService service = (GroupCommitService) this.flushCommitLogService;
        // 是否等待
        if (messageExt.isWaitStoreMsgOK()) {
            // 构建组提交请求,传入本次刷盘后位置的偏移量:写入位置偏移量+写入数据字节数
            GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes(),
                    this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
            // 刷盘请求
            service.putRequest(request);
            return request.future();
        } else {
            service.wakeup();
            return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
        }
    }
    // Asynchronous flush  异步刷盘 这个就是靠os
    else {
        // 如果未使用暂存池
        if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
            // 唤醒刷盘线程进行刷盘
            flushCommitLogService.wakeup();
        } else  {
            // 如果使用暂存池,使用commitLogService,先将数据写入到FILECHANNEL,然后统一进行刷盘
            commitLogService.wakeup();
        }
        // 返回结果
        return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
    }
}
复制代码

2. 同步刷盘

如果使用的是同步刷盘,首先获取了GroupCommitService,然后构建GroupCommitRequest组提交请求,将请求添加到GroupCommitService中,GroupCommitService用于提交刷盘数据。

2.1 GroupCommitRequest提交请求

GroupCommitRequestCommitLog的内部类:

  • nextOffset:写入位置偏移量+写入数据字节数,也就是本次刷盘成功后应该对应的flush偏移量
  • flushOKFuture:刷盘结果
  • timeoutMillis:刷盘的超时时间,超过超时时间还未刷盘完毕会被认为超时
public static class GroupCommitRequest {
    // 刷盘点偏移量
    private final long nextOffset;
    // 刷盘状态
    private CompletableFuture<PutMessageStatus> flushOKFuture = new CompletableFuture<>();
    private final long startTimestamp = System.currentTimeMillis();
    // 超时时间
    private long timeoutMillis = Long.MAX_VALUE;

    public GroupCommitRequest(long nextOffset, long timeoutMillis) {
        this.nextOffset = nextOffset;
        this.timeoutMillis = timeoutMillis;
    }
    public void wakeupCustomer(final PutMessageStatus putMessageStatus) {
        // todo 在这里调用 结束刷盘,设置刷盘状态
        this.flushOKFuture.complete(putMessageStatus);
    }
复制代码

2.2 GroupCommitService处理刷盘

GroupCommitServiceCommitLog的内部类,从继承关系中可知它实现了Runnable接口,在run方法调用waitForRunning等待刷盘请求的提交,然后处理刷盘,不过这个线程是在什么时候启动的呢?

public class CommitLog {
    /**
     * GroupCommit Service
     */
    class GroupCommitService extends FlushCommitLogService {
        // ...
        // run方法
        public void run() {
            CommitLog.log.info(this.getServiceName() + " service started");
            while (!this.isStopped()) {
                try {
                    // 等待刷盘请求的到来
                    this.waitForRunning(10);
                    // 处理刷盘
                    this.doCommit();
                } catch (Exception e) {
                    CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
                }
            }
            // ...
        }
    }
}
复制代码

2.2.1 刷盘线程的启动

BrokerController的启动方法中,可以看到调用了messageStorestart方法,前面可知使用的是DefaultMessageStore,进入到DefaultMessageStore的start方法,它又调用了commitLog的start方法,在CommitLogstart方法中,启动了刷盘的线程和监控刷盘的线程:

public class BrokerController {
    public void start() throws Exception {
        if (this.messageStore != null) {
            // 启动
            this.messageStore.start();
        }
        // ...
    }
}

public class DefaultMessageStore implements MessageStore {
   /**
     * @throws Exception
     */
    public void start() throws Exception {
        // ...
        this.flushConsumeQueueService.start();
        // 调用CommitLog的启动方法
        this.commitLog.start();
        this.storeStatsService.start();
        // ...
    }
}

public class CommitLog {
    private final FlushCommitLogService flushCommitLogService; // 刷盘
    private final FlushCommitLogService commitLogService; // commitLogService
    public void start() {
        // 启动刷盘的线程
        this.flushCommitLogService.start();
        flushDiskWatcher.setDaemon(true);
        
        if (defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
            this.commitLogService.start();
        }
    }
}
复制代码

2.2.2 刷盘请求的处理

既然知道了线程在何时启动的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值