[RockertMQ] Broker启动加载消息文件以及恢复数据源码 (三)

news2024/12/23 1:21:02

Broker的启动过程中, 在DefaultMessageStore实例化后, 会调用load方法将磁盘中的commitLog、ConsumeQueue、IndexFile文件的数据加载到内存中, 还有数据恢复的操作。

在这里插入图片描述

  1. 调用isTempFileExist方法判断上次broker是否是正常退出, 如果是正常退出不会保留abort文件, 异常退出则会。

  2. 加载CommitLog日志文件。CommitLog文件是真正存储消息内容的地方。

  3. 加载ConsumeQueue文件。ConsumeQueue文件可以看作是CommitLog的消息偏移量索引文件。

  4. 加载 index 索引文件。Index文件可以看作是CommitLog的消息时间范围索引文件。

  5. 恢复ConsumeQueue文件和CommitLog文件, 将正确的的数据恢复至内存中, 删除错误数据和文件。

  6. 加载RocketMQ延迟消息的服务, 包括延时等级、配置文件等等。

/**
 * DefaultMessageStore的方法
 * 加载Commit Log、Consume Queue、index file等文件,将数据加载到内存中,并完成数据的恢复
 *
 * @throws IOException
 */
public boolean load() {
    boolean result = true;

    try {
        /*
         * 1 判断上次broker是否是正常退出,如果是正常退出不会保留abort文件,异常退出则会
         *
         * Broker在启动时会创建{storePathRootDir}/abort文件,并且注册钩子函数:在JVM退出时删除abort文件。
         * 如果下一次启动时存在abort文件,说明Broker是异常退出的,文件数据可能不一直,需要进行数据修复。
         */
        boolean lastExitOK = !this.isTempFileExist();
        log.info("last shutdown {}", lastExitOK ? "normally" : "abnormally");

        /*
         * 2 加载Commit Log日志文件,目录路径取自broker.conf文件中的storePathCommitLog属性
         * Commit Log文件是真正存储消息内容的地方,单个文件默认大小1G。
         */
        // load Commit Log
        result = result && this.commitLog.load();
        /*
         * 2 加载Consume Queue文件,目录路径为{storePathRootDir}/consumequeue,文件组织方式为topic/queueId/fileName
         * Consume Queue文件可以看作是Commit Log的索引文件,其存储了它所属Topic的消息在Commit Log中的偏移量
         * 消费者拉取消息的时候,可以从Consume Queue中快速的根据偏移量定位消息在Commit Log中的位置。
         */
        // load Consume Queue
        result = result && this.loadConsumeQueue();

        if (result) {
            /*
             * 3 加载checkpoint 检查点文件,文件位置是{storePathRootDir}/checkpoint
             * StoreCheckpoint记录着commitLog、ConsumeQueue、Index文件的最后更新时间点,
             * 当上一次broker是异常结束时,会根据StoreCheckpoint的数据进行恢复,这决定着文件从哪里开始恢复,甚至是删除文件
             */
            this.storeCheckpoint =
                    new StoreCheckpoint(StorePathConfigHelper.getStoreCheckpoint(this.messageStoreConfig.getStorePathRootDir()));
            /*
             * 4 加载 index 索引文件,目录路径为{storePathRootDir}/index
             * index 索引文件用于通过时间区间来快速查询消息,底层为HashMap结构,实现为hash索引。后面会专门出文介绍
             * 如果不是正常退出,并且最大更新时间戳比checkpoint文件中的时间戳大,则删除该 index 文件
             */
            this.indexService.load(lastExitOK);
            /*
             * 4 恢复ConsumeQueue文件和CommitLog文件,将正确的的数据恢复至内存中,删除错误数据和文件。
             */
            this.recover(lastExitOK);

            log.info("load over, and the max phy offset = {}", this.getMaxPhyOffset());

            /*
             * 5 加载RocketMQ延迟消息的服务,包括延时等级、配置文件等等。
             */
            if (null != scheduleMessageService) {
                result = this.scheduleMessageService.load();
            }
        }

    } catch (Exception e) {
        log.error("load exception", e);
        result = false;
    }

    if (!result) {
        //如果上面的操作抛出异常,则文件服务停止
        this.allocateMappedFileService.shutdown();
    }

    return result;
}

文章目录

      • 1.isTempFileExist是否存在临时文件
      • 2.commitLog#load加载消息日志文件
        • 2.1 load加载文件
        • 2.2 commitlog文件简介
      • 3.loadConsumeQueue加载消费队列文件
        • 3.1 load加载ConsumeQueue文件
        • 3.2 ConsumeQueue文件简介
      • 4.创建StoreCheckpoint检查点对象
      • 5.加载index索引文件
        • 5.1 index索引文件介绍
      • 6.恢复CommitLog和ConsumeQueue数据
        • 6.1 recoverConsumeQueue恢复ConsumeQueue
        • 6.2 recoverNormally正常恢复commitLog
        • 6.3 recoverAbnormally异常恢复commitlog
        • 6.4 recoverTopicQueueTable恢复consumeQueueTable

1.isTempFileExist是否存在临时文件

  1. 判断是否出现临时文件, abort文件, Broker在启动时会创建{ROCKET_HOME}/store/abort文件, 并注册钩子函数, 在jvm退出时候删除abort文件。
  2. 如果下一次启动时候不存在abort文件, 表示钩子函数执行了, broker是正常退出的, 不需要恢复文件数据, 如果存在abort文件, 此时数据可能会不一致, 需要恢复文件数据。

在这里插入图片描述

private boolean isTempFileExist() {
    //获取临时文件路径,路径为:{storePathRootDir}/abort
    String fileName = StorePathConfigHelper.getAbortFile(this.messageStoreConfig.getStorePathRootDir());
    //构建file文件对象
    File file = new File(fileName);
    //判断文件是否存在
    return file.exists();
}

在这里插入图片描述

2.commitLog#load加载消息日志文件

CommitLog的load方法实际上是委托内部的mappedFileQueue的load方法进行加载。

在这里插入图片描述

/**
 * CommitLog的方法
 */
public boolean load() {
    //调用mappedFileQueue的load方法
    boolean result = this.mappedFileQueue.load();
    log.info("load commit log " + (result ? "OK" : "Failed"));
    return result;
}

2.1 load加载文件

MappedFileQueue#load方法会就是将commitLog目录路径下的commotlog文件进行全部的加载为MappedFile对象。

在这里插入图片描述

/**
 * MappedFileQueue的方法
 */
public boolean load() {
    //获取commitlog文件的存放目录,目录路径取自
    //broker.conf文件中配置的storePathCommitLog属性,默认为$HOME/store/commitlog/
    File dir = new File(this.storePath);
    //获取内部的文件集合
    File[] ls = dir.listFiles();
    if (ls != null) {
        //如果存在commitlog文件,那么进行加载
        return doLoad(Arrays.asList(ls));
    }
    return true;
}
/**
 * MappedFileQueue的方法
 */
public boolean doLoad(List<File> files) {
    // 对commitlog文件按照文件名生序排序
    files.sort(Comparator.comparing(File::getName));

    for (File file : files) {
        //校验文件实际大小是否等于预定的文件大小,如果不想等,则直接返回false,不再加载其他文件
        if (file.length() != this.mappedFileSize) {
            log.warn(file + "\t" + file.length()
                    + " length not matched message store config value, please check it manually");
            return false;
        }

        try {
            /*
             * 核心代码
             * 每一个commitlog文件都创建一个对应的MappedFile对象
             *
             */
            MappedFile mappedFile = new MappedFile(file.getPath(), mappedFileSize);
            //将wrotePosition 、flushedPosition、committedPosition 默认设置为文件大小
            //当前文件所映射到的消息写入page cache的位置
            mappedFile.setWrotePosition(this.mappedFileSize);
            //刷盘的最新位置
            mappedFile.setFlushedPosition(this.mappedFileSize);
            //已提交的最新位置
            mappedFile.setCommittedPosition(this.mappedFileSize);
            //添加到MappedFileQueue内部的mappedFiles集合中
            this.mappedFiles.add(mappedFile);
            log.info("load " + file.getPath() + " OK");
        } catch (IOException e) {
            log.error("load file " + file + " error", e);
            return false;
        }
    }
    return true;
}
  • commotlog目录下面是一个个的commitlog文件, java进行了3次映射, CommitLog - MappedFileQueue - MappedFile。

  • CommitLog中包含MappedFileQueue, 和commitlog的其他服务, 比如刷盘服务。MappedFileQueue中包含MappedFile集合, 以及单个commitlog的文件大小属性。MappedFile才是真正的一个commotlog文件在Java中的映射, 包含文件名, 大小, mmap对象mappedByteBuffer等属性。

  • commitLog、consumerQueue、indexFile3种文件磁盘的读写都是通过MappedFile操作的。

public MappedFile(final String fileName, final int fileSize) throws IOException {
    //调用init初始化
    init(fileName, fileSize);
}
private void init(final String fileName, final int fileSize) throws IOException {
    //文件名。长度为20位,左边补零,剩余为起始偏移量,比如00000000000000000000代表了第一个文件,起始偏移量为0
    this.fileName = fileName;
    //文件大小。默认1G=1073741824
    this.fileSize = fileSize;
    //构建file对象
    this.file = new File(fileName);
    //构建文件起始索引,就是取自文件名
    this.fileFromOffset = Long.parseLong(this.file.getName());
    boolean ok = false;
    //确保文件目录存在
    ensureDirOK(this.file.getParent());

    try {
        //对当前commitlog文件构建文件通道fileChannel
        this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();
        //把commitlog文件完全的映射到虚拟内存,也就是内存映射,即mmap,提升读写性能
        this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);
        //记录数据
        TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(fileSize);
        TOTAL_MAPPED_FILES.incrementAndGet();
        ok = true;
    } catch (FileNotFoundException e) {
        log.error("Failed to create file " + this.fileName, e);
        throw e;
    } catch (IOException e) {
        log.error("Failed to map file " + this.fileName, e);
        throw e;
    } finally {
        //释放fileChannel,注意释放fileChannel不会对之前的mappedByteBuffer映射产生影响
        if (!ok && this.fileChannel != null) {
            this.fileChannel.close();
        }
    }
}

2.2 commitlog文件简介

  • Commit Log文件是RocketMQ真正存储消息内容的地方, 是消息主体以及元数据的存储主体, 存储Producer端写入的消息主体内容, 消息内容不是定长的。

  • 单个文件大小默认为1G, 文件名长度为20位, 左边补0, 剩余为起始偏移量, 1G=1073741824, 当第一个文件写满了, 第二个文件为00000000001073741824。

  • 消息顺序写入日志文件, 效率很高, 文件满了, 写入下一个文件。

V

RocketMQ的优化, commitlog文件预创建, 如果启动了MappedFile预分配服务, 那么创建MappedFile时候同时创建两个MappedFile, 一个用于同步创建并返回本次实际使用, 一个用于后台异步创建用于下次使用。可以避免当前文件用完后才创建下一个文件, 提高了性能。

3.loadConsumeQueue加载消费队列文件

  1. 加载消费队列文件, ConsumerQueue文件是CommitLog的索引文件, 存储它属于的topic消息在CommitLog中的偏移量。

  2. 消费者拉取消息的时候, 可以从ConsumerQueue中的偏移量定位到消息在CommitLog的位置。

  3. 一个队列id目录对应着一个ConsumeQueue对象, 内部保存了mappedFileQueue对象, 表示当前队列id目录下的ConsumerQueue文件集合, 一个ConsumeQueue文件被映射为一个MappedFile对象。

  4. ConsumeQueue及其topic和queueId的对应关系被存入DefaultMessageStore的consumeQueueTable属性集合中。

/**
 * DefaultMessageStore的方法
 */
private boolean loadConsumeQueue() {
    //获取ConsumeQueue文件所在目录,目录路径为{storePathRootDir}/consumequeue
    File dirLogic = new File(StorePathConfigHelper.getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir()));
    //获取目录下文件列表,实际上下面页是topic目录列表
    File[] fileTopicList = dirLogic.listFiles();
    if (fileTopicList != null) {
        //遍历topic目录
        for (File fileTopic : fileTopicList) {
            //获取topic名字
            String topic = fileTopic.getName();
            //获取topic目录下面的队列id目录
            File[] fileQueueIdList = fileTopic.listFiles();
            if (fileQueueIdList != null) {
                for (File fileQueueId : fileQueueIdList) {
                    int queueId;
                    try {
                        //获取队列id
                        queueId = Integer.parseInt(fileQueueId.getName());
                    } catch (NumberFormatException e) {
                        continue;
                    }
                    //创建ConsumeQueue对象,一个队列id目录对应着一个ConsumeQueue对象
                    //其内部保存着
                    ConsumeQueue logic = new ConsumeQueue(
                            topic,
                            queueId,
                            StorePathConfigHelper.getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir()),
                            //大小默认30w数据
                            this.getMessageStoreConfig().getMappedFileSizeConsumeQueue(),
                            this);
                    //将当然ConsumeQueue对象及其对应关系存入consumeQueueTable中
                    this.putConsumeQueue(topic, queueId, logic);
                    //加载ConsumeQueue文件
                    if (!logic.load()) {
                        return false;
                    }
                }
            }
        }
    }

3.1 load加载ConsumeQueue文件

ConsumeQueue对象建立后, 会对自己管理的队列id目录下的ConsumerQueue文件进行加载。内部就是调用mappedFileQueue的load方法。

在这里插入图片描述

在这里插入图片描述

public boolean load() {
    //调用mappedFileQueue的load方法,会对每个ConsumeQueue文件床创建一个MappedFile对象并且进行内存映射mmap操作。
    boolean result = this.mappedFileQueue.load();
    log.info("load consume queue " + this.topic + "-" + this.queueId + " " + (result ? "OK" : "Failed"));
    if (isExtReadEnable()) {
        //扩展加载,扩展消费队列用于存储不重要的东西,如消息存储时间、过滤位图等。
        result &= this.consumeQueueExt.load();
    }
    return result;
}

3.2 ConsumeQueue文件简介

消息消费队列, 理解为Topic队列, 目的是提高消息消费的性能。RocketMQ是基于主题Topic的订阅模式, 消息消费是针对Topic进行的, 如果遍历commitlog文件中根据topic检索消息的话是很低效的。

Consumer即可根据ConsumeQueue来查找待消费的消息, ConsumeQueue保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset, 消息大小size和消息Tag的HashCode值, 以及ConsumerOffset(每个消费者组的消费位置)。

consumequeue文件可以看成是基于topic的commitlog索引文件, consumequeue文件夹的组织方式为topic/queue/file三层组织结构, $HOME/store/consumequeue/{topic}/{queueId}/{fileName}。

consumequeue文件中的条目采取定长设计, 每一个条目共20字节, 分别为8字节的commitlog物理偏移量、4字节的消息长度、8字节tag hashcode, 单个文件由30W个条目组成, 可以像数组一样随机访问每一个条目, 每一个ConsumerQueue文件大小约为5.72M。

ConsumerQueue文件名称长度为20位, 00000000000000000000代表第一个文件, 起始偏移量为0, 文件大小为600w, 当第一个文件满之后创建的第二个文件的名字为00000000000006000000, 起始偏移量为6000000。

4.创建StoreCheckpoint检查点对象

在这里插入图片描述

在commitlog和consumequeue文件都加载成功之后, 加载checkpoint检查点文件, 创建storeCheckpoint对象, 文件位置是{storePathRootDir}/checkpoint。

StoreCheckpoint记录着commitLog、consumeQueue、index文件的最后更新时间点, 当上一次broker是异常结束时, 根据StoreCheckpoint的数据进行恢复, 文件从哪里开始恢复和删除文件的地方。

StoreCheckpoint记录了三个关键属性:

  1. physicMsgTimestamp: 最新commitlog文件的刷盘时间戳。
  2. logicsMsgTimestamp: 最新consumeQueue文件的刷盘时间戳。
  3. indexMsgTimestamp: 创建最新indexfile文件的时间戳。
public StoreCheckpoint(final String scpPath) throws IOException {
    File file = new File(scpPath);
    //判断存在当前文件
    MappedFile.ensureDirOK(file.getParent());
    boolean fileExists = file.exists();
    //对checkpoint文件同样执行mmap操作
    this.randomAccessFile = new RandomAccessFile(file, "rw");
    this.fileChannel = this.randomAccessFile.getChannel();
    //mmap大小为OS_PAGE_SIZE,即OS一页,4k
    this.mappedByteBuffer = fileChannel.map(MapMode.READ_WRITE, 0, MappedFile.OS_PAGE_SIZE);

    if (fileExists) {
        log.info("store checkpoint file exists, " + scpPath);
        //获取commitlog文件的时间戳,即最新commitlog文件的刷盘时间戳
        this.physicMsgTimestamp = this.mappedByteBuffer.getLong(0);
        //获取consumeQueue文件的时间戳,即最新consumeQueue文件的刷盘时间戳
        this.logicsMsgTimestamp = this.mappedByteBuffer.getLong(8);
        //获取index文件的时间戳,即创建最新indexfile文件的时间戳
        this.indexMsgTimestamp = this.mappedByteBuffer.getLong(16);

        log.info("store checkpoint file physicMsgTimestamp " + this.physicMsgTimestamp + ", "
            + UtilAll.timeMillisToHumanString(this.physicMsgTimestamp));
        log.info("store checkpoint file logicsMsgTimestamp " + this.logicsMsgTimestamp + ", "
            + UtilAll.timeMillisToHumanString(this.logicsMsgTimestamp));
        log.info("store checkpoint file indexMsgTimestamp " + this.indexMsgTimestamp + ", "
            + UtilAll.timeMillisToHumanString(this.indexMsgTimestamp));
    } else {
        log.info("store checkpoint file not exists, " + scpPath);
    }
}

5.加载index索引文件

目录路径为{storePathRootDir}/index, index 索引文件用于通过时间区间来快速查询消息, 底层为HashMap结构。

最终一个index文件对应着一个IndexFile实例, 并且会加到indexFileList集合中。并判断上一次broker是不是正常退出, 并且当index文件中最后一个消息的落盘时间戳大于StoreCheckpoint中最后一个index的索引文件创建时间, 那么该索引文件删除。

/**
 * IndexService的方法
 * @param lastExitOK 上次是否正常推出
 */
public boolean load(final boolean lastExitOK) {
    //获取上级目录路径,{storePathRootDir}/index
    File dir = new File(this.storePath);
    //获取内部的index索引文件
    File[] files = dir.listFiles();
    if (files != null) {
        // 按照文件名字中的时间戳排序
        Arrays.sort(files);
        for (File file : files) {
            try {
                //一个index文件对应着一个IndexFile实例
                IndexFile f = new IndexFile(file.getPath(), this.hashSlotNum, this.indexNum, 0, 0);
                //加载index文件
                f.load();
                //如果上一次是异常推出,并且当前index文件中最后一个消息的落盘时间戳大于最后一个index索引文件创建时间,则该索引文件被删除
                if (!lastExitOK) {
                    if (f.getEndTimestamp() > this.defaultMessageStore.getStoreCheckpoint()
                        .getIndexMsgTimestamp()) {
                        f.destroy(0);
                        continue;
                    }
                }

                log.info("load index file OK, " + f.getFileName());
                //加入到索引文件集合
                this.indexFileList.add(f);
            } catch (IOException e) {
                log.error("load file {} error", file, e);
                return false;
            } catch (NumberFormatException e) {
                log.error("load file {} error", file, e);
            }
        }
    }

    return true;
}

5.1 index索引文件介绍

IndexFile提供一种可以提供key或时间区去查询消息的方法。index文件存储的位置为HOME/store/index{fileName}, 文件名fileName是以创建时间戳命名的, 固定的单个IndexFile文件大小为400M, 一个IndexFile可以存储2000W个索引, IndexFile的底层存储设计为在文件系统中实现HashMap结构, 所以RocketMQ的索引文件底层为hash索引。

在这里插入图片描述

6.恢复CommitLog和ConsumeQueue数据

恢复CommitLog和ConsumeQueue数据到内存中:

  1. 首先恢复所有的ConsumeQueue文件, 返回在ConsumeQueue有效区域存储的最大的commitlog偏移量。
  2. 随后对于commitlog文件进行恢复, 根据上次broker是否正常退出, 正常恢复和异常恢复。
  3. 最后再对topicQueueTable进行恢复。

在这里插入图片描述

在这里插入图片描述

/**
 * DefaultMessageStore的方法
 * @param lastExitOK 上次是否正常退出
 */
private void recover(final boolean lastExitOK) {
    //恢复所有的ConsumeQueue文件,返回在ConsumeQueue存储的最大有效commitlog偏移量
    long maxPhyOffsetOfConsumeQueue = this.recoverConsumeQueue();

    if (lastExitOK) {
        //正常恢复commitLog
        this.commitLog.recoverNormally(maxPhyOffsetOfConsumeQueue);
    } else {
        //异常恢复commitLog
        this.commitLog.recoverAbnormally(maxPhyOffsetOfConsumeQueue);
    }
    //最后恢复topicQueueTable
    this.recoverTopicQueueTable();
}

6.1 recoverConsumeQueue恢复ConsumeQueue

恢复ConsumeQueue文件和删除无效的ConsumerQueue文件, 最后返回ConsumeQueue有效区域存储的最大commitlog物理偏移量, 该值表示消息在commitlog文件中最后的指针, 即commitlog的有效消息数据最大文件偏移量。

在这里插入图片描述

/**
 * 恢复所有的ConsumeQueue文件,返回在ConsumeQueue有效区域存储的最大的commitlog偏移量
 */
private long recoverConsumeQueue() {
    long maxPhysicOffset = -1;
    //遍历consumeQueueTable的value集合,即queueId到ConsumeQueue的map映射
    for (ConcurrentMap<Integer, ConsumeQueue> maps : this.consumeQueueTable.values()) {
        //遍历所有的ConsumeQueue
        for (ConsumeQueue logic : maps.values()) {
            //恢复ConsumeQueue,删除无效ConsumeQueue文件
            logic.recover();
            //如果当前queueId目录下的所有ConsumeQueue文件的最大有效物理偏移量,大于此前记录的最大有效物理偏移量
            //则更新记录的ConsumeQueue文件的最大commitlog有效物理偏移量
            if (logic.getMaxPhysicOffset() > maxPhysicOffset) {
                maxPhysicOffset = logic.getMaxPhysicOffset();
            }
        }
    }
    //返回ConsumeQueue文件的最大commitlog有效物理偏移量
    return maxPhysicOffset;
}

recover恢复ConsumeQueue

  • 恢复每一个ConsumeQueue, 一个队列id目录对应着一个ConsumeQueue对象, 因此ConsumeQueue内部保存着多个属于同一queueId的ConsumeQueue文件。

  • RocketMQ不会也没必要对所有的ConsumeQueue文件进行恢复校验, 如果ConsumeQueue文件数量大于等于3个, 取最新的3个ConsumerQueue文件执行恢复, 否则对全部的ConsumerQueue进行恢复。

  • 恢复就是找出当前queueId的ConsumeQueue下的所有ConsumeQueue文件中的最大的有效的commitlog消息日志文件的物理偏移量, 以及该索引文件自身的最大有效数据偏移量, 对文件自身的最大有效数据偏移量processOffset之后的所有文件和数据进行更新或者删除。

  • 判断ConsumeQueue索引文件中的一个索引条目有效, 只需要该条目保存的对应的消息在commitlog文件中的物理偏移量和该条目保存的对应的消息在commitlog文件中的总长度都大于0则表示当前条目有效, 否则表示该条目无效, 并不会对后续的条目和文件进行恢复。

  • 最大的有效的commitlog消息物理偏移量, 就是最后一个有效条目中保存的commitlog文件中的物理偏移量, 而文件自身的最大有效数据偏移量processOffset, 指的是最后一个有效条目在自身文件中的偏移量。

/**
 * ConsumeQueue的方法
 */
public void recover() {
    //获取所有的ConsumeQueue文件映射的mappedFiles集合
    final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();
    if (!mappedFiles.isEmpty()) {
        //从倒数第三个文件开始恢复
        int index = mappedFiles.size() - 3;
        //不足3个文件,则直接从第一个文件开始恢复。
        if (index < 0)
            index = 0;
        //consumequeue映射文件的文件大小
        int mappedFileSizeLogics = this.mappedFileSize;
        //获取文件对应的映射对象
        MappedFile mappedFile = mappedFiles.get(index);
        //文件映射对应的DirectByteBuffer
        ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();
        //获取文件映射的初始物理偏移量,其实和文件名相同
        long processOffset = mappedFile.getFileFromOffset();
        //consumequeue映射文件的有效offset
        long mappedFileOffset = 0;
        long maxExtAddr = 1;
        while (true) {
            //校验每一个索引条目的有效性,CQ_STORE_UNIT_SIZE是每个条目的大小,默认20
            for (int i = 0; i < mappedFileSizeLogics; i += CQ_STORE_UNIT_SIZE) {
                //获取该条目对应的消息在commitlog文件中的物理偏移量
                long offset = byteBuffer.getLong();
                //获取该条目对应的消息在commitlog文件中的总长度
                int size = byteBuffer.getInt();
                //获取该条目对应的消息的tag哈希值
                long tagsCode = byteBuffer.getLong();
                //如果offset和size都大于0则表示当前条目有效
                if (offset >= 0 && size > 0) {
                    //更新当前ConsumeQueue文件中的有效数据偏移量
                    mappedFileOffset = i + CQ_STORE_UNIT_SIZE;
                    //更新当前queueId目录下的所有ConsumeQueue文件中的最大有效物理偏移量
                    this.maxPhysicOffset = offset + size;
                    if (isExtAddr(tagsCode)) {
                        maxExtAddr = tagsCode;
                    }
                } else {
                    //否则,表示当前条目无效了,后续的条目不会遍历
                    log.info("recover current consume queue file over,  " + mappedFile.getFileName() + " "
                            + offset + " " + size + " " + tagsCode);
                    break;
                }
            }
            //如果当前ConsumeQueue文件中的有效数据偏移量和文件大小一样,则表示该ConsumeQueue文件的所有条目都是有效的
            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 {
                //如果不相等,则表示当前ConsumeQueue有部分无效数据,恢复结束
                log.info("recover current consume queue queue over " + mappedFile.getFileName() + " "
                        + (processOffset + mappedFileOffset));
                break;
            }
        }
        //该文件映射的已恢复的物理偏移量
        processOffset += mappedFileOffset;
        //设置当前queueId下面的所有的ConsumeQueue文件的最新数据
        //设置刷盘最新位置,提交的最新位置
        this.mappedFileQueue.setFlushedWhere(processOffset);
        this.mappedFileQueue.setCommittedWhere(processOffset);
        /*
         * 删除文件最大有效数据偏移量processOffset之后的所有数据
         */
        this.mappedFileQueue.truncateDirtyFiles(processOffset);

        if (isExtReadEnable()) {
            this.consumeQueueExt.recover();
            log.info("Truncate consume queue extend file by max {}", maxExtAddr);
            this.consumeQueueExt.truncateByMaxAddress(maxExtAddr);
        }
    }
}

truncateDirtyFiles截断无效文件

如果文件最大数据偏移量大于最大有效数据偏移量:

  1. 将文件起始偏移量大于最大有效数据偏移量的文件进行整个删除。

  2. 否则设置该文件的有效数据位置为最大有效数据偏移量。

/**
 * MappedFileQueue的方法
 * @param offset 文件的最大有效数据偏移量
 */
public void truncateDirtyFiles(long offset) {
    //待移除的文件集合
    List<MappedFile> willRemoveFiles = new ArrayList<MappedFile>();
    //遍历内部所有的MappedFile文件
    for (MappedFile file : this.mappedFiles) {
        //获取当前文件自身的最大数据偏移量
        long fileTailOffset = file.getFileFromOffset() + this.mappedFileSize;
        //如果最大数据偏移量大于最大有效数据偏移量
        if (fileTailOffset > offset) {
            //如果最大有效数据偏移量大于等于该文件的起始偏移量,那么说明当前文件有一部分数据是有效的,那么设置该文件的有效属性
            if (offset >= file.getFileFromOffset()) {
                //设置当前文件的刷盘、提交、写入指针为当前最大有效数据偏移量
                file.setWrotePosition((int) (offset % this.mappedFileSize));
                file.setCommittedPosition((int) (offset % this.mappedFileSize));
                file.setFlushedPosition((int) (offset % this.mappedFileSize));
            } else {
                //如果如果最大有效数据偏移量小于该文件的起始偏移量,那么删除该文件
                file.destroy(1000);
                //记录到待删除的文件集合中
                willRemoveFiles.add(file);
            }
        }
    }
    //将等待移除的文件整体从mappedFiles中移除
    this.deleteExpiredFile(willRemoveFiles);
}

/**
 * MappedFileQueue的方法
 * @param files 待移除的文件集合
 */
void deleteExpiredFile(List<MappedFile> files) {

    if (!files.isEmpty()) {

        Iterator<MappedFile> iterator = files.iterator();
        while (iterator.hasNext()) {
            MappedFile cur = iterator.next();
            if (!this.mappedFiles.contains(cur)) {
                //从mappedFiles集合中删除当前MappedFile
                iterator.remove();
                log.info("This mappedFile {} is not contained by mappedFiles, so skip it.", cur.getFileName());
            }
        }

        try {
            //如果并没有完全移除这些无效文件,那么记录异常信息
            if (!this.mappedFiles.removeAll(files)) {
                log.error("deleteExpiredFile remove failed.");
            }
        } catch (Exception e) {
            log.error("deleteExpiredFile has exception.", e);
        }
    }
}

6.2 recoverNormally正常恢复commitLog

在这里插入图片描述

该方法用于Broker上次正常关闭的时候恢复commitlog, 最多获取最后三个commitlog文件进行校验恢复, 依次校验每一条消息的有效性, 跟新commitlog文件的最大有效区域的偏移量, 最后调用truncateDirtyFiles方法清除无效的commit文件。

/**
 * When the normal exit, data recovery, all memory data have been flush
 * 当正常退出、数据恢复时,所有内存数据都已刷到磁盘
 * @param maxPhyOffsetOfConsumeQueue consumequeue文件中记录的最大有效commitlog文件偏移量
 */
public void recoverNormally(long maxPhyOffsetOfConsumeQueue) {
    //是否需要校验文件CRC32,默认true
    boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover();
    //获取commitlog文件集合
    final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();
    //如果存在commitlog文件
    if (!mappedFiles.isEmpty()) {
        //从倒数第三个文件开始恢复
        int index = mappedFiles.size() - 3;
        //不足3个文件,则直接从第一个文件开始恢复。
        if (index < 0)
            index = 0;
        //获取文件对应的映射对象
        MappedFile mappedFile = mappedFiles.get(index);
        //文件映射对应的DirectByteBuffer
        ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();
        //获取文件映射的初始物理偏移量,其实和文件名相同
        long processOffset = mappedFile.getFileFromOffset();
        //当前commitlog映射文件的有效offset
        long mappedFileOffset = 0;
        while (true) {
            //生成DispatchRequest,验证本条消息是否合法
            DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover);
            //获取消息大小
            int size = dispatchRequest.getMsgSize();
            //如果消息是正常的
            if (dispatchRequest.isSuccess() && size > 0) {
                //更新mappedFileOffset的值加上本条消息长度
                mappedFileOffset += size;
            }
            //如果消息正常但是size为0,表示到了文件的末尾,则尝试跳到下一个commitlog文件进行检测
            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;
            }
        }
        //commitlog文件的最大有效区域的偏移量
        processOffset += mappedFileOffset;
        //设置当前commitlog下面的所有的commitlog文件的最新数据
        //设置刷盘最新位置,提交的最新位置
        this.mappedFileQueue.setFlushedWhere(processOffset);
        this.mappedFileQueue.setCommittedWhere(processOffset);
        /*
         * 删除文件最大有效数据偏移量processOffset之后的所有数据
         */
        this.mappedFileQueue.truncateDirtyFiles(processOffset);

        //如果consumequeue文件记录的最大有效commitlog文件偏移量 大于等于 commitlog文件本身记录的最大有效区域的偏移量
        //那么以commitlog文件的有效数据为准,再次清除consumequeue文件中的脏数据
        if (maxPhyOffsetOfConsumeQueue >= processOffset) {
            log.warn("maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, processOffset);
            this.defaultMessageStore.truncateDirtyLogicFiles(processOffset);
        }
    } else {
        //如果不存在commitlog文件
        log.warn("The commitlog files are deleted, and delete the consume queue files");
        //那么重置刷盘最新位置,提交的最新位置,并且清除所有的consumequeue索引文件
        this.mappedFileQueue.setFlushedWhere(0);
        this.mappedFileQueue.setCommittedWhere(0);
        this.defaultMessageStore.destroyLogics();
    }
}

truncateDirtyLogicFiles截断无效consumequeue文件

在这里插入图片描述

如果consumequeue文件记录的最大有效commitlog文件偏移量大于等于 commitlog文件本身记录的最大有效区域的偏移量。那么以commitlog文件的有效数据为准, 再次清除consumequeue文件中的脏数据。

/**
 * DefaultMessageStore的方法
 * @param phyOffset  commitlog文件的最大有效区域的偏移量
 */
public void truncateDirtyLogicFiles(long phyOffset) {
    //获取consumeQueueTable
    ConcurrentMap<String, ConcurrentMap<Integer, ConsumeQueue>> tables = DefaultMessageStore.this.consumeQueueTable;
    //遍历
    for (ConcurrentMap<Integer, ConsumeQueue> maps : tables.values()) {
        for (ConsumeQueue logic : maps.values()) {
            //对每一个consumequeue文件的数据进行校验,可能会删除consumequeue文件,抑或是更新相关属性
            logic.truncateDirtyLogicFiles(phyOffset);
        }
    }
}

6.3 recoverAbnormally异常恢复commitlog

在这里插入图片描述

该方法用于Broker上次异常关闭的时候恢复commitlog, 对于异常恢复的commitlog, 不再是最多取后三个文件恢复, 而是倒序遍历所有commitlog文件执行校验和恢复操作。直到找到第一个消息正常存储的commitlog文件。

  1. 首先倒序遍历并通过调用isMappedFileMatchedRecover方法判断当前文件是否是一个正常的commitlog文件, 如果找到一个正确的commitlog文件,则停止遍历。
  2. 后从第一个正确的commitlog文件开始向后遍历, 恢复commitlog。如果某个消息是正常的, 通过defaultMessageStore.doDispatch方法调用CommitLogDipatcher重新构建当前的indexFile索引和consumerQueue索引。
  3. 恢复完毕之后的代码和commitlog文件正常恢复的流程是一样的。例如删除文件最大有效数据偏移量processOffset之后的所有commitlog数据, 清除consumequeue文件中的脏数据等等。
/**
 * CommitLog的方法
 * <p>
 * broker异常退出时的commitlog文件恢复,按最小时间戳恢复
 *
 * @param maxPhyOffsetOfConsumeQueue consumequeue文件中记录的最大有效commitlog文件偏移量
 */
@Deprecated
public void recoverAbnormally(long maxPhyOffsetOfConsumeQueue) {

    boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover();
    final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();
    if (!mappedFiles.isEmpty()) {
        // Looking beginning to recover from which file
        int index = mappedFiles.size() - 1;
        MappedFile mappedFile = null;
        //倒叙遍历所有的commitlog文件执行检查恢复
        for (; index >= 0; index--) {
            mappedFile = mappedFiles.get(index);
            //首先校验当前commitlog文件是否是一个正确的文件
            if (this.isMappedFileMatchedRecover(mappedFile)) {
                log.info("recover from this mapped file " + mappedFile.getFileName());
                break;
            }
        }
        /*
         * 从第一个正确的commitlog文件开始遍历恢复
         */
        if (index < 0) {
            index = 0;
            mappedFile = mappedFiles.get(index);
        }

        ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();
        //获取文件映射的初始物理偏移量,其实和文件名相同
        long processOffset = mappedFile.getFileFromOffset();
        //commitlog映射文件的有效offset
        long mappedFileOffset = 0;
        while (true) {
            //生成DispatchRequest,验证本条消息是否合法
            DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover);
            //获取消息大小
            int size = dispatchRequest.getMsgSize();
            //如果消息是正常的
            if (dispatchRequest.isSuccess()) {
                // Normal data
                if (size > 0) {
                    //更新mappedFileOffset的值加上本条消息长度
                    mappedFileOffset += size;
                    //如果消息允许重复复制,默认为 false
                    if (this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) {
                        //如果消息物理偏移量小于CommitLog的提交指针
                        //则调用CommitLogDispatcher重新构建当前消息的indexfile索引和consumequeue索引
                        if (dispatchRequest.getCommitLogOffset() < this.defaultMessageStore.getConfirmOffset()) {
                            this.defaultMessageStore.doDispatch(dispatchRequest);
                        }
                    } else {
                        //调用CommitLogDispatcher重新构建当前消息的indexfile索引和consumequeue索引
                        this.defaultMessageStore.doDispatch(dispatchRequest);
                    }
                }
                // 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
                //如果消息正常但是size为0,表示到了文件的末尾,则尝试跳到下一个commitlog文件进行检测
                else if (size == 0) {
                    index++;
                    //如果最后一个文件查找完毕,结束循环
                    if (index >= mappedFiles.size()) {
                        // The current branch under normal circumstances should
                        // not happen
                        log.info("recover 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 {
                //如果当前消息异常,那么不继续校验
                log.info("recover physics file end, " + mappedFile.getFileName() + " pos=" + byteBuffer.position());
                break;
            }
        }
        //commitlog文件的最大有效区域的偏移量
        processOffset += mappedFileOffset;
        //设置当前commitlog下面的所有的commitlog文件的最新数据
        //设置刷盘最新位置,提交的最新位置
        this.mappedFileQueue.setFlushedWhere(processOffset);
        this.mappedFileQueue.setCommittedWhere(processOffset);
        /*
         * 删除文件最大有效数据偏移量processOffset之后的所有数据
         */
        this.mappedFileQueue.truncateDirtyFiles(processOffset);

        // Clear ConsumeQueue redundant data
        //如果consumequeue文件记录的最大有效commitlog文件偏移量 大于等于 commitlog文件本身记录的最大有效区域的偏移量
        //那么以commitlog文件的有效数据为准,再次清除consumequeue文件中的脏数据
        if (maxPhyOffsetOfConsumeQueue >= processOffset) {
            log.warn("maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, processOffset);
            this.defaultMessageStore.truncateDirtyLogicFiles(processOffset);
        }
    }
    // Commitlog case files are deleted
    else {
        log.warn("The commitlog files are deleted, and delete the consume queue files");
        //如果不存在commitlog文件
        //那么重置刷盘最新位置,提交的最新位置,并且清除所有的consumequeue索引文件
        this.mappedFileQueue.setFlushedWhere(0);
        this.mappedFileQueue.setCommittedWhere(0);
        this.defaultMessageStore.destroyLogics();
    }
}

6.4 recoverTopicQueueTable恢复consumeQueueTable

在这里插入图片描述

topicQueueTable存储的是“topic-queueid”到当前queueId下面最大的相对偏移量的map。

/**
 * DefaultMessageStore的方法
 */
public void recoverTopicQueueTable() {
    HashMap<String/* topic-queueid */, Long/* offset */> table = new HashMap<String, Long>(1024);
    //获取commitlog的最小偏移量
    long minPhyOffset = this.commitLog.getMinOffset();
    //遍历consumeQueueTable,即consumequeue文件的集合
    for (ConcurrentMap<Integer, ConsumeQueue> maps : this.consumeQueueTable.values()) {
        for (ConsumeQueue logic : maps.values()) {
            String key = logic.getTopic() + "-" + logic.getQueueId();
            //将“topicName-queueId”作为key,将当前queueId下面最大的相对偏移量作为value存入table
            table.put(key, logic.getMaxOffsetInQueue());
            logic.correctMinOffset(minPhyOffset);
        }
    }
    //设置为topicQueueTable
    this.commitLog.setTopicQueueTable(table);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/674841.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL】关于自增id、雪花id还是uuid作为MySQL主键

在MySQL中设计表的时候&#xff0c;MySQL官方推荐不要使用uuid或者不连续不重复的雪花id(long型且唯一)&#xff0c;而是推荐连续自增的主键id&#xff0c;官方的推荐是auto_increment。那么为什么不使用雪花id或者uuid呢&#xff1f;让我们来探讨分析一下这个问题的原因。 关于…

【人工智能技术专题】「入门到精通系列教程」打好AI基础带你进军人工智能领域的全流程技术体系(机器学习知识导论)

零基础带你进军人工智能领域的全流程技术体系和实战指南&#xff08;机器学习基础知识&#xff09; 前言专栏介绍专栏说明学习大纲前提条件面向读者学习目标核心内容机器学习的概念定义回顾人工智能机器学习概念国外知名学者对机器学习的定义中文翻译 机器学习发展历程机器学习…

Oracle JSON_ARRAYAGG()函数的排序失效问题

引入&#xff1a; 在实际操作中&#xff0c;俺写了这样一个Funtcion&#xff1a; FUNCTION fun_get_xxx(v_param_one VARCHAR2) RETURN CLOB ASv_OUTPUT CLOB;BEGINWITH temp_table AS (SELECT * FROM (( SELECT one.action_id,two.log_timeFROM table_one oneLEFT JOIN table…

【深度学习】6-4 卷积神经网络 - CNN的实现

CNN的实现 网络的构成是“Convolution - ReLU - Pooling -Affine - ReLU - Affine - Softmax”&#xff0c;我们将它实现为名为 SimpleConvNet的类。 首先来看一下 SimpleConvNet的初始化&#xff08;init&#xff09;&#xff0c;取下面这些参数。 input_dim——输入数据的维…

七彩虹CN600+Meetiger N10C测评

七彩虹CN600这款M.2固态硬盘的参数就不多说了 本期采用为512版本 迷虎品牌&#xff0c;英文名Meetiger&#xff0c;Meetiger/迷虎品牌成立于2012年&#xff0c;品牌迷虎产品主要有硬盘底座,保护盒,硬盘座,移动硬盘盒子,硬盘盒子,... 以下就是本期的硬盘盒 当这两样东西在一起…

C++(10):泛型算法

泛型算法&#xff1a;可用于不同类型的容器和不同类型的元素的通用算法。 概述 大多数算法都定义在头文件algorithm 中。标准库在头文件 numeric 中定义了一组数值泛型算法。 一般情况下&#xff0c;泛型算法不直接操作容器&#xff0c;而是遍历由两个迭代器指定的一个元素范…

【Spring Boot学习】日志文件,Spring Boot也会写日记了,这些事你知道嘛 ? ? ?

前言: 大家好,我是良辰丫,在上一篇文章中我们已经学习了Spring Boot的配置,接下来我们要学习一些日志相关的东西,什么是日志呢?我们慢慢往下看.&#x1f48c;&#x1f48c;&#x1f48c; &#x1f9d1;个人主页&#xff1a;良辰针不戳 &#x1f4d6;所属专栏&#xff1a;javaE…

7.4_2B树的插入删除

我们先设置根节点 我们再往里面插入关键字 比如说&#xff1a;80 中间位置为49&#xff1a;&#xff08;5/2&#xff09;向上取整为3 新元素一定要插入到最底层”终端结点“&#xff0c;用”查找”来确定插入位置。 失败节点&#xff08;叶子节点不属于同一层&#xff09; 讲…

Vue-全局事件总线(GlobalEventBus)

全局事件总线(GlobalEventBus) 全局事件总线是vue中特别厉害的一种组件中通信的方式&#xff0c;它可以实现任意组件中通信&#xff0c;随便拿出来两个组件就能互通数据&#xff0c;就像对讲机一样&#xff0c;它在开发中用的也是特别的多 1 编写案例 首先准备两个组件&…

Python列表 (超详细举例加讲解)

得之我幸&#xff0c;失之我命 文章目录 1.列表的值 2.列表的定义 3.下标 4.列表长度 5.列表的加法和乘法 6.列表切片 7.操作方法&#xff08;一些基础的函数&#xff09; &#xff08;1&#xff09;append——向列表末尾添加元素 &#xff08;2&#xff09;insert——…

VUE3实现页面缓存,tab切换时不刷新

如上图所示&#xff0c;为了实现页面缓存&#xff0c;防止每次页面切换时重新刷新数据&#xff0c;前前后后尝试了多种写法&#xff0c;如上图被注释的那段写法&#xff0c;与没注释掉的写法&#xff0c;在router-view上主要是第一种写法有设置key属性&#xff0c;第二种没有&a…

常见面试题之框架篇

1.Spring框架中的单例bean是线程安全的吗&#xff1f; 不是线程安全的&#xff0c;是这样的。 当多用户同时请求一个服务时&#xff0c;容器会给每一个请求分配一个线程&#xff0c;这是多个线程会并发执行该请求对应的业务逻辑&#xff08;成员方法&#xff09;&#xff0c;…

模拟电路系列分享-负反馈电路稳定性分析

目录 概要 整体架构流程 技术名词解释 1.负反馈放大电路产生自激震荡的条件 2.从实际运放的幅频&#xff0c;相频特性看自激振荡的可能性 小结 概要 在卡拉0K歌厅中,我们会见到这样一种现象:当麦克风位置不合适或者音量过大时, 喇叭中会出现一种非常难听的啸叫,捂住麦克风、赶紧…

【无需显卡】AI绘画入门教程

前言 Hello&#xff0c;各位端午节快乐呀&#xff01;不好意思拖更两个月&#xff0c;最近实在是太忙了&#xff0c;也想不到有什么好玩的&#xff0c;之前介绍过了几个好玩的ai网站&#xff0c;非常适合新手尝鲜&#xff0c;但很多都有额度限制&#xff0c;而且还开始收费了&…

【C语言】内存你知多少?详解C语言动态内存管理

目录 一&#xff0c; 计算机中的内存 二&#xff0c;动态内存申请函数 2.1 头文件 2.2 malloc函数 2.3 free函数 2.3 calloc函数 2.4 realloc函数——调整空间函数 情况1&#xff1a;原有空间之后有足够大的空间 情况2&#xff1a;原有空间之后没有足够大的空间 2…

Android大图加载优化方案,避免程序OOM

我们在编写Android程序的时候经常要用到许多图片&#xff0c;不同图片总是会有不同的形状、不同的大小&#xff0c;但在大多数情况下&#xff0c;这些图片都会大于我们程序所需要的大小。比如微博长图&#xff0c;海报等等。所以我们就要对图片进行局部显示。 大图加载基本需求…

信号与系统复习笔记——信号与系统的时域和频域特性

信号与系统复习笔记——信号与系统的时域和频域特性 傅里叶变换的模和相位表示 一般来说&#xff0c;傅里叶变换的结果是复数&#xff0c;所以能够使用模和相位来表示&#xff0c;具体的有&#xff1a; X ( j ω ) ∣ X ( j ω ) ∣ e j ∡ X ( j ω ) X(j\omega) |X(j\ome…

浅尝Transformer和LLM

文章目录 TransformerTransformer的衍生BERTPre-trainingBERT与其他方法的关系怎么用BERT做生成式任务&#xff1f; GPTPre-trainingFine-Tuning Transformer工具开源库特点 LLM系列推理服务 大语言模型势不可挡啊。 哲学上来说&#xff0c;语言就是我们的一切&#xff0c;语言…

MySQL 高级(进阶) SQL 语句

创建两个表格 use awsl; create table location (Region char(20),Store_Name char(20)); insert into location values(East,Boston); insert into location values(East,New York); insert into location values(West,Los Angeles); insert into location values(West,Houst…

JMU20 软件工程经济学 复习总结

文章目录 碎碎念0. 基准收益率 i1. 现金流量图2. 净现值 NPV&#xff0c;内部收益率 IRR3. 单利&#xff0c;复利计算4. 等额年金NAV5. 动态回收期 P t ′ P_t Pt′​6. 固定资产折旧 [书P44]7. 增值税8. 软件行业增值税的即征即退9. 利息备付率 ICR&#xff0c;偿债备付率 DSC…