Rocketmq文件恢复源码深入的个人解析

文章详细解读了RocketMQ在启动时如何加载和恢复消息存储的关键步骤,包括检查临时文件判断系统是否异常关闭,加载commitLog和ConsumeQueue文件到内存,以及执行恢复操作。核心类DefaultMessageStore的load方法中,涉及commitLog和ConsumeQueue的加载与恢复流程,特别是recover方法对异常和正常关闭情况的处理策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

个人研读的rocketmq源码注释,由于后续的需要,所以研究rocketmq的文件存储原理,觉得有用的双击

核心入口代码DefaultMessageStore

 public boolean load() {
        boolean result = true;

        try {
            //判断临时文件是否存在,如果存在则说明系统是异常宕机  lastExitOK=false
            // 这个临时文件启动的时候会创建createTempFile  销毁的时候会删除。如果没有正常关闭就不会执行shutdown方法。则认为是异常关闭
            boolean lastExitOK = !this.isTempFileExist();
            log.info("last shutdown {}", lastExitOK ? "normally" : "abnormally");
            //这个是延时队列的操作 ,不看
            if (null != scheduleMessageService) {
                result = result && this.scheduleMessageService.load();
            }

            // load Commit Log   //把所有的日志文件加载到文件内存队列中
            result = result && this.commitLog.load();

            // load Consume Queue   //把所有的队列文件加载到文件内存队列中
            result = result && this.loadConsumeQueue();

            if (result) {
                //暂时不管
                this.storeCheckpoint =
                    new StoreCheckpoint(StorePathConfigHelper.getStoreCheckpoint(this.messageStoreConfig.getStorePathRootDir()));

                //索引文件 根据key查找消息。暂时不管
                this.indexService.load(lastExitOK);

                this.recover(lastExitOK);

                log.info("load over, and the max phy offset = {}", this.getMaxPhyOffset());
            }
        } catch (Exception e) {
            log.error("load exception", e);
            result = false;
        }

        if (!result) {
            this.allocateMappedFileService.shutdown();
        }

        return result;
    }

commitLog.load() 和loadConsumeQueue比较简单可以自己读

public boolean load() {
        //把所有的文件加载到文件内存队列中
        boolean result = this.mappedFileQueue.load();
        log.info("load commit log " + (result ? "OK" : "Failed"));
        return result;
    }

recover主要恢复方法

  private void recover(final boolean lastExitOK) {
        //队列是commitLog的索引文件  返回值是所有consumeQueue文件的最大偏移量
        long maxPhyOffsetOfConsumeQueue = this.recoverConsumeQueue();
        //commitlog存储的是真实消息   恢复commitLog  是rocketmq的核心消息存储数据。核心流程和功能点
        if (lastExitOK) {
            // 没有临时文件,是正常退出
            this.commitLog.recoverNormally(maxPhyOffsetOfConsumeQueue);
        } else {
            //异常关机退出
            this.commitLog.recoverAbnormally(maxPhyOffsetOfConsumeQueue);
        }

        this.recoverTopicQueueTable();
    }

recoverConsumeQueue

    private long recoverConsumeQueue() {
        long maxPhysicOffset = -1;
        for (ConcurrentMap<Integer, ConsumeQueue> maps : this.consumeQueueTable.values()) {
            for (ConsumeQueue logic : maps.values()) {
                //调用恢复方法 获取这个队列中所有的文件mappedFiles进行恢复
                logic.recover();
                if (logic.getMaxPhysicOffset() > maxPhysicOffset) {
                    maxPhysicOffset = logic.getMaxPhysicOffset();
                }
            }
        }
        //返回所有队列中最大的偏移量
        return maxPhysicOffset;
    }

logic.recover();

    public void recover() {
        //获取队列中所有文件
        final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();
        if (!mappedFiles.isEmpty()) {
        //从前三个文件开始找。可能是为了预热
            int index = mappedFiles.size() - 3;
            if (index < 0)
                index = 0;
        //文件的大小 应该是1024MB
            int mappedFileSizeLogics = this.mappedFileSize;
            MappedFile mappedFile = mappedFiles.get(index);
            ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();
            long processOffset = mappedFile.getFileFromOffset();
            //临时维护读取的文件大小的偏移量
            long mappedFileOffset = 0;
            long maxExtAddr = 1;
            while (true) {
                for (int i = 0; i < mappedFileSizeLogics; i += CQ_STORE_UNIT_SIZE) {
                    //每次读取20字节的数据,并且向后移动20字节。consumeQueue数据结构
                    long offset = byteBuffer.getLong();
                    int size = byteBuffer.getInt();
                    long tagsCode = byteBuffer.getLong();

                    //如果读取出来有值。则临时偏移量增加20.   并且真实偏移量和size进行记录
                    if (offset >= 0 && size > 0) {
                        mappedFileOffset = i + CQ_STORE_UNIT_SIZE;
                        //真实偏移量和size进行追加
                        this.maxPhysicOffset = offset + size;
                        if (isExtAddr(tagsCode)) {
                            maxExtAddr = tagsCode;
                        }
                    } else {
                        //false说明文件读到一半没数据了,这个文件没写完
                        log.info("recover current consume queue file over,  " + mappedFile.getFileName() + " "
                            + offset + " " + size + " " + tagsCode);
                        break;
                    }
                }
                //读取的临时偏移量size到了文件的最大size。说明这个文件读完了 否则停止循环
                if (mappedFileOffset == mappedFileSizeLogics) {
                    //如果还有下一个文件。设置下一个文件的数据继续读
                    index++;
                    if (index >= mappedFiles.size()) {

                        log.info("recover last consume queue 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 consume queue file, " + mappedFile.getFileName());
                    }
                } else {
                    //   停止循环打印日志
                    log.info("recover current consume queue queue over " + mappedFile.getFileName() + " "
                        + (processOffset + mappedFileOffset));
                    break;
                }
            }
            //当前的文件偏移量也就=fileFromOffset+读取到的文件偏移量
            processOffset += mappedFileOffset;
            this.mappedFileQueue.setFlushedWhere(processOffset);
            this.mappedFileQueue.setCommittedWhere(processOffset);
            //清理脏数据
            this.mappedFileQueue.truncateDirtyFiles(processOffset);
            //需要开启才去执行,非主流程,暂不研究
            if (isExtReadEnable()) {
                this.consumeQueueExt.recover();
                log.info("Truncate consume queue extend file by max {}", maxExtAddr);
                this.consumeQueueExt.truncateByMaxAddress(maxExtAddr);
            }
        }
    }

recoverNormally 读懂了recoverConsumeQueue后这里就很好理解

  public void recoverNormally(long maxPhyOffsetOfConsumeQueue) {
        //校验crc
        boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover();
        final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();
        if (!mappedFiles.isEmpty()) {
            //从最后第三个文件开始恢复
            // Began to recover from the last third file
            int index = mappedFiles.size() - 3;
            if (index < 0)
                index = 0;

            MappedFile mappedFile = mappedFiles.get(index);
            ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();
            //这个值不是0开始的,而是文件的名字的偏移量。所以下面是从文件的名字偏移量开始,不断自增消息的大小,就能得到这个文件目前的偏移量
            long processOffset = mappedFile.getFileFromOffset();
            //临时文件偏移
            long mappedFileOffset = 0;
            while (true) {
                //循环读取文件的
                DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover);
                int size = dispatchRequest.getMsgSize();
                // Normal data  成功并且有消息。当前临时文件偏移量增加
                if (dispatchRequest.isSuccess() && size > 0) {
                    mappedFileOffset += size;
                }
                // Come the end of the file, switch to the next file Since the
                // return 0 representatives met last hole,
                // this can not be included in truncate offset
                //说明当前读完了,要读下一个文件
                else if (dispatchRequest.isSuccess() && size == 0) {
                    index++;
                    if (index >= mappedFiles.size()) {
                        //完全读完了  这是正常读完的流程
                        // Current branch can not happen
                        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());
                    }
                }
                // Intermediate file read error   异常
                else if (!dispatchRequest.isSuccess()) {
                    log.info("recover physics file end, " + mappedFile.getFileName());
                    break;
                }
            }

            processOffset += mappedFileOffset;
            this.mappedFileQueue.setFlushedWhere(processOffset);
            this.mappedFileQueue.setCommittedWhere(processOffset);
            //清理超出的文件
            this.mappedFileQueue.truncateDirtyFiles(processOffset);

            // Clear ConsumeQueue redundant data   consumeQueue是conmitLog的索引文件。consumeQueue的偏移量如果超过了commitLog的偏移量的话。要清理consumeQueue的文件索引
            if (maxPhyOffsetOfConsumeQueue >= processOffset) {
                log.warn("maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, processOffset);
                this.defaultMessageStore.truncateDirtyLogicFiles(processOffset);
            }
        } else {
            // Commitlog case files are deleted
            log.warn("The commitlog files are deleted, and delete the consume queue files");
            this.mappedFileQueue.setFlushedWhere(0);
            this.mappedFileQueue.setCommittedWhere(0);
            this.defaultMessageStore.destroyLogics();
        }
    }

recoverAbnormally

这个方法只多了一个异常恢复代码,已经是过期代码。可以自己研读。后续有看会补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值