本文共 5886 字,大约阅读时间需要 19 分钟。
由于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:解释一下这个三个配置属性的含义。
DefaultMessageStore$CleanCommitLogService#deleteExpiredFiles:
boolean timeup = this.isTimeToDelete();boolean spacefull = this.isSpaceToDelete();boolean manualDelete = this.manualDeleteFileSeveralTimes > 0;if (timeup || spacefull || manualDelete) { //继续执行删除逻辑 return;} else { // 本次删除任务无作为。}
Step2:RocketMQ在如下三种情况任意满足之一的情况下将继续执行删除文件操作。
重点分析一下磁盘不足的判断依据。
DefaultMessageStore$CleanCommitLogService#isSpaceToDelete
double ratio = DefaultMessageStore.this.getMessageStoreConfig().getDiskMaxUsedSpaceRatio() / 100.0; // @1cleanImmediately = 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另外提供了两个与磁盘空间使用率相关的系统级参数:
判断磁盘是否可用,用当前已使用物理磁盘率maxUsedSpaceRatio、diskSpaceWarningLevelRatio、diskSpaceCleanForciblyRatio,如果当前磁盘使用率达到上述阔值,将返回true表示磁盘已满,需要进行过期文件删除操作。
Step3:然后根据文件的最后一次更新时间与当前时间做比较,判断是否过期,如果已过期,调用MappedFile的destory。
MappedFile#shutdownpublic 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与获取磁盘分区总容量与剩余容量的方法。备注:本文是《RocketMQ技术内幕》的前期素材,建议关注笔者的书籍:《RocketMQ技术内幕》。
见文如面,我是威哥,热衷于成体系剖析JAVA主流中间件,关注公众号『中间件兴趣圈』,回复专栏可获取成体系专栏导航,回复资料可以获取笔者的学习思维导图。
转载地址:http://drq.baihongyu.com/