第十五章-Broker-定期清理文件

前面的几章已经讲了Broker会创建commitlog、cq和index三类文件来存储消息,使用户能够快速找到消息内容。为了防止存储内容日渐庞大,得定期给清理掉历史文件,本章就是从源码角度来讲解Broker如何清理历史文件。还是先从方法调用链开始:

DefaultMessageStore.start

->DefaultMessageStore.addScheduleTask

​ ->DefaultMessageStore.this.cleanFilesPeriodically() // 定时任务异步执行

​ ->cleanCommitLogService.run() // commitlog文件的清理

​ ->cleanConsumeQueueService.run() // cq文件清理

addScheduleTask 定时任务异步执行

private void addScheduleTask() {

    // 初始延迟60s,每10秒执行一次清理历史文件的操作
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            // 历史文件清理
            DefaultMessageStore.this.cleanFilesPeriodically();
        }
    }, 1000 * 60, this.messageStoreConfig.getCleanResourceInterval(), TimeUnit.MILLISECONDS);
    // 初始延迟1ms,每10分钟执行一次操作
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            // Broker自身的一些合法性检查,比如commitlog文件名,有兴趣的读者可以自行进入研究
            DefaultMessageStore.this.checkSelf();
        }
    }, 1, 10, TimeUnit.MINUTES);
    // 初始延迟1ms,每1秒执行一次操作
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            if (DefaultMessageStore.this.getMessageStoreConfig().isDebugLockEnable()) {
                try {
                    if (DefaultMessageStore.this.commitLog.getBeginTimeInLock() != 0) {
                        // 针对写文件时,加锁时间太久的操作,打印线程堆栈信息,方便debug调试
                        long lockTime = System.currentTimeMillis() - DefaultMessageStore.this.commitLog.getBeginTimeInLock();
                        if (lockTime > 1000 && lockTime < 10000000) {

                            String stack = UtilAll.jstack();
                            final String fileName = System.getProperty("user.home") + File.separator + "debug/lock/stack-"
                                + DefaultMessageStore.this.commitLog.getBeginTimeInLock() + "-" + lockTime;
                            MixAll.string2FileNotSafe(stack, fileName);
                        }
                    }
                } catch (Exception e) {
                }
            }
        }
    }, 1, 1, TimeUnit.SECONDS);

    // this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    // @Override
    // public void run() {
    // DefaultMessageStore.this.cleanExpiredConsumerQueue();
    // }
    // }, 1, 1, TimeUnit.HOURS);
}

15.1 commitlog过期清理

private void deleteExpiredFiles() {
    int deleteCount = 0;
    // 文件保留时长,默认72小时
    long fileReservedTime = DefaultMessageStore.this.getMessageStoreConfig().getFileReservedTime();
    // 删除文件的时间间隔,默认100ms
    int deletePhysicFilesInterval = DefaultMessageStore.this.getMessageStoreConfig().getDeleteCommitLogFilesInterval();
    // 强制销毁映射文件的时间间隔,默认是2分钟(1000 * 120ms),这个值是针对映射文件被多次引用的情况
    int destroyMapedFileIntervalForcibly = DefaultMessageStore.this.getMessageStoreConfig().getDestroyMapedFileIntervalForcibly();
	// 决定什么时候开始执行删除,默认是04,代表凌晨4点
    boolean timeup = this.isTimeToDelete();
    // 判断磁盘空间是否不足,默认使用大于85%
    boolean spacefull = this.isSpaceToDelete();
    // 手动设置的删除次数,默认是0
    boolean manualDelete = this.manualDeleteFileSeveralTimes > 0;
	// 时间到了、磁盘空间不够了、手动删除,三者有一个满足,就可以继续执行
    if (timeup || spacefull || manualDelete) {

        if (manualDelete)
            this.manualDeleteFileSeveralTimes--;
		// 立即清除标识
        boolean cleanAtOnce = DefaultMessageStore.this.getMessageStoreConfig().isCleanFileForciblyEnable() && this.cleanImmediately;
		// 日志打印
        log.info("begin to delete before {} hours file. timeup: {} spacefull: {} manualDeleteFileSeveralTimes: {} cleanAtOnce: {}",
                 fileReservedTime,
                 timeup,
                 spacefull,
                 manualDeleteFileSeveralTimes,
                 cleanAtOnce);
		// 72 小时换算成毫秒
        fileReservedTime *= 60 * 60 * 1000;
		// 删除过期文件
        deleteCount = DefaultMessageStore.this.commitLog.deleteExpiredFile(fileReservedTime, deletePhysicFilesInterval,
                                                                           destroyMapedFileIntervalForcibly, cleanAtOnce);
        if (deleteCount > 0) {
        } else if (spacefull) {
            log.warn("disk space will be full soon, but delete file failed.");
        }
    }
}

CommitLog.deleteExpiredFile

public int deleteExpiredFile(
    final long expiredTime,
    final int deleteFilesInterval,
    final long intervalForcibly,
    final boolean cleanImmediately
) {
    // 继续调用
    return this.mappedFileQueue.deleteExpiredFileByTime(expiredTime, deleteFilesInterval, intervalForcibly, cleanImmediately);
}

MappedFileQueue.deleteExpiredFileByTime

public int deleteExpiredFileByTime(final long expiredTime,
                                   final int deleteFilesInterval,
                                   final long intervalForcibly,
                                   final boolean cleanImmediately) {
    // 把所有的commitlog的映射文件对象,复制成Object数组
    Object[] mfs = this.copyMappedFiles(0);
	// 没有文件,直接返回
    if (null == mfs)
        return 0;

    int mfsLength = mfs.length - 1;
    int deleteCount = 0;
    List<MappedFile> files = new ArrayList<MappedFile>();
    if (null != mfs) {
        // 遍历执行
        for (int i = 0; i < mfsLength; i++) {
            // 取出映射文件对象
            MappedFile mappedFile = (MappedFile) mfs[i];
            // 该存活的最大时间戳,文件最后修改时间 + 过期时间(72小时)
            long liveMaxTimestamp = mappedFile.getLastModifiedTimestamp() + expiredTime;
            // 当前时间大于该存活的时间 或者 cleanImmediately标识为true
            if (System.currentTimeMillis() >= liveMaxTimestamp || cleanImmediately) {
                // destroy 主要做三件事:1.释放mmap的文件映射;2.关闭file channel;3.真实删除物理文件
                if (mappedFile.destroy(intervalForcibly)) {
                    // 释放完成,该文件就可以删除了,放入files列表中
                    files.add(mappedFile);
                    // 删除数量+1
                    deleteCount++;
					// 一次删除文件最大数量不能超过 DELETE_FILES_BATCH_MAX,默认是10
                    if (files.size() >= DELETE_FILES_BATCH_MAX) {
                        break;
                    }
					// 有间隔时间,就要睡眠间隔时间后再执行下一个
                    if (deleteFilesInterval > 0 && (i + 1) < mfsLength) {
                        try {
                            Thread.sleep(deleteFilesInterval);
                        } catch (InterruptedException e) {
                        }
                    }
                } else {
                    break;
                }
            } else {
                //avoid deleting files in the middle
                break;
            }
        }
    }
	// 批量删除文件
    deleteExpiredFile(files);

    return deleteCount;
}

批量删除文件

从下面的源码可以看出,这里应该叫清除缓存中的映射文件对象,真实的物理文件已经在destroy方法中删除了

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)) {
                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);
        }
    }
}

15.2 cq和index过期清理

class CleanConsumeQueueService {
    private long lastPhysicalMinOffset = 0;

    public void run() {
        try {
            // 执行删除过期文件操作
            this.deleteExpiredFiles();
        } catch (Throwable e) {
            DefaultMessageStore.log.warn(this.getServiceName() + " service has exception. ", e);
        }
    }

    private void deleteExpiredFiles() {
        // 每次执行的间隔时间,默认100ms
        int deleteLogicsFilesInterval = DefaultMessageStore.this.getMessageStoreConfig().getDeleteConsumeQueueFilesInterval();

        long minOffset = DefaultMessageStore.this.commitLog.getMinOffset();
        if (minOffset > this.lastPhysicalMinOffset) {
            this.lastPhysicalMinOffset = minOffset;

            ConcurrentMap<String, ConcurrentMap<Integer, ConsumeQueue>> tables = DefaultMessageStore.this.consumeQueueTable;

            for (ConcurrentMap<Integer, ConsumeQueue> maps : tables.values()) {
                for (ConsumeQueue logic : maps.values()) {
                    // 直接看这一行,里面的逻辑跟commitlog过期清理是一样的,唯一的区别是cq的删除是根据commitlog的最小物理偏移值来确定,所以就不冗余讲解了,有兴趣的读者自行研究
                    int deleteCount = logic.deleteExpiredFile(minOffset);

                    if (deleteCount > 0 && deleteLogicsFilesInterval > 0) {
                        try {
                            Thread.sleep(deleteLogicsFilesInterval);
                        } catch (InterruptedException ignored) {
                        }
                    }
                }
            }
			// index文件的删除,同样也要根据commitlog的最小物理偏移来确定删除操作
            DefaultMessageStore.this.indexService.deleteExpiredFile(minOffset);
        }
    }

    public String getServiceName() {
        return CleanConsumeQueueService.class.getSimpleName();
    }
}
  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

多栖码农

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

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

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

打赏作者

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

抵扣说明:

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

余额充值