RocketMQ源码分析之文件过期删除机制

撸了今年阿里、腾讯和美团的面试,我有一个重要发现…….

作者:dingwpmz

出处:https://blog.csdn.net/prestigeding/article/details/78888290


1、由于RocketMQ操作CommitLog、ConsumeQueue文件,都是基于内存映射方法并在启动的时候,会加载commitlog、ConsumeQueue目录下的所有文件,为了避免内存与磁盘的浪费,不可能将消息永久存储在消息服务器上,所以需要一种机制来删除已过期的文件。RocketMQ顺序写Commitlog、ConsumeQueue文件,所有写操作全部落在最后一个CommitLog或ConsumeQueue文件上,之前的文件在下一个文件创建后,将不会再被更新,RocketMQ清除过期文件的方法是:如果非当前写文件在一定时间间隔内没有再次被更新,则认为是过期文件,可以被删除,RocketMQ不会管这个这个文件上的消息是否被全部消费。默认每个文件的过期时间为72小时。通过在Broker配置文件中设置fileReservedTime来改变过期时间,单位为小时。接下来详细分析RocketMQ是如何设计与实现上述机制的。
DefaultMessageStore#addScheduleTask:

    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    DefaultMessageStore.this.cleanFilesPeriodically();
                }
            }, 1000 * 60, this.messageStoreConfig.getCleanResourceInterval(), TimeUnit.MILLISECONDS);

RocketMQ会每隔10s调度一次cleanFilesPeriodically,已检测是否需要清除过期文件。执行频率可以通过设置cleanResourceInterval,默认为10s。
DefaultMessageStore#cleanFilesPeriodically

    private void cleanFilesPeriodically() {
            this.cleanCommitLogService.run();
            this.cleanConsumeQueueService.run();
        }

主要清除CommitLog、ConsumeQueue的过期文件。
CommitLog与ConsumeQueue对于过期文件的删除算法、逻辑大同小异,本文将以CommitLog过期文件为例来详细分析其实现原理。

DefaultMessageStore$CleanCommitLogService#run

    public void run() {
        try {
               this.deleteExpiredFiles();
               this.redeleteHangedFile();
         } catch (Throwable e) {
                DefaultMessageStore.log.warn(this.getServiceName() + " service has
                        exception. ", e);
         }
    }

整个执行过程分为两个大的步骤,第一个步骤:尝试删除过期文件;第二个步骤:重试删除被hange(由于被其他线程引用在第一阶段未删除的文件),在这里再重试一次。
DefaultMessageStore$CleanCommitLogService#deleteExpiredFiles

    long fileReservedTime = DefaultMessageStore.this.getMessageStoreConfig().getFileReservedTime();
    int deletePhysicFilesInterval = DefaultMessageStore.this.getMessageStoreConfig().getDeleteCommitLogFilesInterval();
    int destroyMapedFileIntervalForcibly = DefaultMessageStore.this.getMessageStoreConfig().getDestroyMapedFileIntervalForcibly();

Step1:解释一下这个三个配置属性的含义。
fileReservedTime:文件保留时间,也就是从最后一次更新时间到现在,如果超过了该时间,则认为是过期文件,可以被删除。
deletePhysicFilesInterval:删除物理文件的间隔,因为在一次清除过程中,可能需要删除的文件不止一个,该值指定两次删除文件的间隔时间。
destroyMapedFileIntervalForcibly:在清除过期文件时,如果该文件被其他线程所占用(引用次数大于0,比如读取消息),此时会阻止此次删除任务,
同时在第一次试图删除该文件时记录当前时间戳,destroyMapedFileIntervalForcibly表示第一次拒绝删除之后能保留的最大时间,在此时间内,同样可以被拒绝删除,同时会将引用减少1000个,超过该时间间隔后,文件将被强制删除。
DefaultMessageStore$CleanCommitLogService#deleteExpiredFiles:

    boolean timeup = this.isTimeToDelete();
    boolean spacefull = this.isSpaceToDelete();
    boolean manualDelete = this.manualDeleteFileSeveralTimes > 0;
    if (timeup || spacefull || manualDelete) {
        //继续执行删除逻辑
       return;
    } else {
       // 本次删除任务无作为。
    }

Step2:RocketMQ在如下三种情况任意满足之一的情况下将继续执行删除文件操作。
1)到了删除文件的时间点,RocketMQ通过deleteWhen设置一天的固定时间执行一次删除过期文件操作,默认为凌晨4点。
2)判断磁盘空间是否充足,如果不充足,则返回true,表示应该触发过期文件删除操作。
3)预留,手工触发,可以通过调用excuteDeleteFilesManualy方法手工触发过期文件删除,目前RocketMQ暂未封装手工触发文件删除的命令。
重点分析一下磁盘不足的判断依据。
DefaultMessageStore$CleanCommitLogService#isSpaceToDelete

    double ratio = DefaultMessageStore.this.getMessageStoreConfig().getDiskMaxUsedSpaceRatio() / 100.0;   // @1
    cleanImmediately = false;
    {
        String storePathPhysic = DefaultMessageStore.this.getMessageStoreConfig().getStorePathCommitLog();
        double physicRatio = UtilAll.getDiskPartitionSpaceUsedPercent(storePathPhysic);   // @2
        if (physicRatio > diskSpaceWarningLevelRatio) {  // @3
               boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskFull();
               if (diskok) {
                     DefaultMessageStore.log.error("physic disk maybe full soon " + physicRatio + ", so mark disk full");
               }
                 cleanImmediately = true;
         } else if (physicRatio > diskSpaceCleanForciblyRatio) {
               cleanImmediately = true;
         } else {
                boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskOK();
                if (!diskok) {
                       DefaultMessageStore.log.info("physic disk space OK " + physicRatio + ", so mark disk ok");
                 }
          }
          if (physicRatio < 0 || physicRatio > ratio) {
                DefaultMessageStore.log.info("physic disk maybe full soon, so reclaim space, " + physicRatio);
                return true;
          }
    }

代码@1:获取maxUsedSpaceRatio,表示commitlog、consumequeue文件所在磁盘分区的最大使用量,如果超过该值,则需要立即清除过期文件。
代码@2:通过File#getTotalSpace()获取commitlog所在磁盘分区总的存储容量,通过File#getFreeSpace()获取commitlog目录所在磁盘文件剩余容量并得出当前该分区的物理磁盘使用率physicRatio 。
代码@3:RocketMQ另外提供了两个与磁盘空间使用率相关的系统级参数:
-Drocketmq.broker.diskSpaceWarningLevelRatio=0.90:如果磁盘分区使用率超过该阔值,将设置磁盘不可写,此时会拒绝新消息的写入。
-Drocketmq.broker.diskSpaceCleanForciblyRatio=0.85:如果磁盘分区使用超过该阔值,建议立即执行过期文件清除,但不会拒绝新消息的写入。
判断磁盘是否可用,用当前已使用物理磁盘率maxUsedSpaceRatio、diskSpaceWarningLevelRatio、diskSpaceCleanForciblyRatio,如果当前磁盘使用率达到上述阔值,将返回true表示磁盘已满,需要进行过期文件删除操作。

MappedFile#destroy
Step3:然后根据文件的最后一次更新时间与当前时间做比较,判断是否过期,如果已过期,调用MappedFile的destory。
MappedFile#shutdown

    public void shutdown(final long intervalForcibly) {
            if (this.available) {
                this.available = false;
                this.firstShutdownTimestamp = System.currentTimeMillis();
                this.release();
            } else if (this.getRefCount() > 0) {
                if ((System.currentTimeMillis() - this.firstShutdownTimestamp) >= intervalForcibly) {
                    this.refCount.set(-1000 - this.getRefCount());
                    this.release();
                }
            }
        }

如果available为true,表示第一次执行shutdown方法,首先设置available为false,并记录firstShutdownTimestamp时间戳,如果当前该文件被其他线程引用,则本次不强制删除,如果没有其他线程在使用该文件,则清除MappedFile相关资源,并最终执行File#delete()方法清除文件。在拒绝被删除保护期内(destroyMapedFileIntervalForcibly)每执行一次清理任务,将引用次数减去1000,引用数小于1后,该文件最终将被删除。
关于ConsumeQueue的过期文件删除机制与Commitlog文件机制类似,本文就不重复讲解。
本文重点是理解如下参数的含义:
fileReservedTime、deletePhysicFilesInterval、destroyMapedFileIntervalForcibly、-Drocketmq.broker.diskSpaceWarningLevelRatio
-Drocketmq.broker.diskSpaceCleanForciblyRatio与获取磁盘分区总容量与剩余容量的方法。

赞(0) 打赏

如未加特殊说明,此网站文章均为原创,转载必须注明出处。Java 技术驿站 » RocketMQ源码分析之文件过期删除机制
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

关注【Java 技术驿站】公众号,每天早上 8:10 为你推送一篇技术文章

扫描二维码关注我!


关注【Java 技术驿站】公众号 回复 “VIP”,获取 VIP 地址永久关闭弹出窗口

免费获取资源

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏