rocketmq核心源码分析第四篇一Broker启动分析一第二小节图解broker启动及文件正常异常恢复

broker启动图示

实际上broker启动非常复杂
我们简化来看主要分两部分:加载和start

加载阶段完成各个组件的初始化,其中最重要的是DefaultMessageStore组件

start 阶段完成通信监听,注册nameserver调度等等

  • DefaultMessageStore管理了MQ的消息存储,线程,锁等资源,协调文件读写,线程工作
    在这里插入图片描述

文件恢复

初始化阶段完成DefaultMessageStore加载

CommitLog消息文件加载,consumeQueueTable消费队列加载,indexService文件加载

加载完成后会进行文件恢复

  • 异常恢复: 消息写入CommitLog后,异步由reputMessageService线程完成索引文件和消息队列文件的构建,该过程可能宕机
  • 正常恢复: 当加载完成后,磁盘上的文件会被加载到内存中,但此时尚不知道文件写到哪里,需要根据文件头获取当前写入位置指针等文件元数据信息

DefaultMessageStore.recover源码分析

先恢复ConsumeQueue
在进行commitlog恢复
最后根据commitlog和consumequeue的指针比较清除(commitlog中不存在, consumequeue中存在)consumequeue信息

  • lastExitOK当.abort文件存在为false,不存在为true 【该文件启动时创建,关闭时销毁,异常关闭存在】
    private void recover(final boolean lastExitOK) {
        恢复消息队列,返回最大物理偏移量
        表示消息最后写完的指针在文件中的偏移量
        long maxPhyOffsetOfConsumeQueue = this.recoverConsumeQueue();
        if (lastExitOK) {
           正常恢复
            this.commitLog.recoverNormally(maxPhyOffsetOfConsumeQueue);
        } else {
           异常恢复
            this.commitLog.recoverAbnormally(maxPhyOffsetOfConsumeQueue);
        }
  	
        this.recoverTopicQueueTable();
    }

recoverNormally正常恢复

  • 1 正常恢复意味刷盘完毕,则从倒数第三个进行commitlog文件进行恢复
  • 2 找到最新的commitlog,获取commitlog文件头的元信息
  • 3 根据元信息构建commitlog的指针信息
  • 4 比较commitlog指针和consumequeue指针,释放consumequeue超出消息的指针对应的bytebuf
public void recoverNormally(long maxPhyOffsetOfConsumeQueue) {

    是否校验消息CRC
    boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover();
    获取commitlog文件
    final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();

    if (!mappedFiles.isEmpty()) {
        从倒数第三个commitlog进行恢复 确保足够的安全
        int index = mappedFiles.size() - 3;
        不足三个则从第一个开始恢复
        if (index < 0)
            index = 0;
        MappedFile mappedFile = mappedFiles.get(index);
        ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();
        long processOffset = mappedFile.getFileFromOffset();
        long mappedFileOffset = 0;
        while (true) {
            获取commitlog文件头构建DispatchRequest
            DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover);
            int size = dispatchRequest.getMsgSize();
            
            当前commitlog文件写了一部分
            if (dispatchRequest.isSuccess() && size > 0) {
                mappedFileOffset += size;
            }
            当前文件以写满,跳下一个文件
            else if (dispatchRequest.isSuccess() && size == 0) {
                index++;
                if (index >= mappedFiles.size()) {
                    log.info("recover last 3 physics file over, last mapped file " + mappedFile.getFileName());
                    break;
                } else {
                    mappedFile = mappedFiles.get(index);
                    byteBuffer = mappedFile.sliceByteBuffer();
                    processOffset = mappedFile.getFileFromOffset();
                    mappedFileOffset = 0;
                    log.info("recover next physics file, " + mappedFile.getFileName());
                }
            }
            else if (!dispatchRequest.isSuccess()) {
                log.info("recover physics file end, " + mappedFile.getFileName());
                break;
            }
        }

        processOffset += mappedFileOffset;
        文件commitlog的相关指针信息 提交到哪里 刷盘到哪里 截断脏位在案例
        this.mappedFileQueue.setFlushedWhere(processOffset);
        this.mappedFileQueue.setCommittedWhere(processOffset);
        this.mappedFileQueue.truncateDirtyFiles(processOffset);

        如果发现consumequeue的指针比commitlog的最大指针还要大
        则需要清空consumequeue的脏条目信息,因为根据该consumequeue消费commitlog的消息肯定查不到
        if (maxPhyOffsetOfConsumeQueue >= processOffset) {
            log.warn("maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, processOffset);
            this.defaultMessageStore.truncateDirtyLogicFiles(processOffset);
        }
    }
    ...... 删除其他代码
}

recoverAbnormally异常恢复

整体机制同正常恢复,但起始处理commitlog不是从倒数第三个文件开始
根据checkpoint倒序遍历文件,获取第一个完整无异常的commitlog

  • 异常恢复无法确认从倒数第三个文件开始计算,因为不能确定最后的刷盘点在哪里
  • 所有需要checkpoint机制从最后一个文件开始倒序遍历检查出正常文件
  • checkpoint在刷盘时会写入一个时间戳,commitlog在刷盘时会写入时间戳
  • 当前commitlog文件时间戳小于checkpoint时间戳,则该commitlog文件正常刷盘,作为备选文件
 public void recoverAbnormally(long maxPhyOffsetOfConsumeQueue) {
    boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover();
    final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();
    int index = mappedFiles.size() - 1;
    MappedFile mappedFile = null;
    for (; index >= 0; index--) {
        mappedFile = mappedFiles.get(index);
        /**
         *  根据checkpoint文件  寻找第一个匹配的文件
         *
         *  通过savePoint检查点与每一个文件存储的
         */
        if (this.isMappedFileMatchedRecover(mappedFile)) {
            log.info("recover from this mapped file " + mappedFile.getFileName());
            break;
        }
    }
    ------------------------------------------------------------------------------------
    if (index < 0) {
        index = 0;
        mappedFile = mappedFiles.get(index);
    }
    ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();
    long processOffset = mappedFile.getFileFromOffset();
    long mappedFileOffset = 0;
    while (true) {
        获取commitlog文件头构建DispatchRequest
        DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover);
        int size = dispatchRequest.getMsgSize();

        if (dispatchRequest.isSuccess()) {
            当前commitlog文件写了一部分
            if (size > 0) {
                mappedFileOffset += size;

                if (this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) {
                    if (dispatchRequest.getCommitLogOffset() < this.defaultMessageStore.getConfirmOffset()) {
                        this.defaultMessageStore.doDispatch(dispatchRequest);
                    }
                } else {
                    重新构建index consumequeue
                    this.defaultMessageStore.doDispatch(dispatchRequest);
                }
            }
            当前文件以写满,跳下一个文件
            else if (size == 0) {
                index++;
                if (index >= mappedFiles.size()) {
                    break;
                } else {
                    mappedFile = mappedFiles.get(index);
                    byteBuffer = mappedFile.sliceByteBuffer();
                    processOffset = mappedFile.getFileFromOffset();
                    mappedFileOffset = 0;
                }
            }
        } else {
            break;
        }
    }
    commitlog恢复到这里
    processOffset += mappedFileOffset;
    this.mappedFileQueue.setFlushedWhere(processOffset);
    this.mappedFileQueue.setCommittedWhere(processOffset);
    this.mappedFileQueue.truncateDirtyFiles(processOffset);
    清除 ConsumeQueue 冗余数据
    if (maxPhyOffsetOfConsumeQueue >= processOffset) {
        log.warn("maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, processOffset);
        this.defaultMessageStore.truncateDirtyLogicFiles(processOffset);
    }
    
}

总结

  • 图解broker启动
  • 讲述了消息队列的正常和异常恢复

扩展点一commitlog和consumequeue文件概述

一个commitlog文件一个consumequeue文件
默认1G默认600万个字节,约5.72M
每个消息长度不定每个条目固定20字节,共30万条目
所有topic共享一个(逻辑)commitlog文件存储每个topic有各自consumequeue文件
发送消息时消息存储commitlog,后异步构建consumequeue消费消息根据消费位点读取下一个consumequeue条目【固定20字节】根据consumequeue的offset读取commitlog消息内容

consumequeue条目如下: 是commitlog的索引文件
在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值