博客
关于我
源码分析RocketMQ文件清除机制
阅读量:132 次
发布时间:2019-02-25

本文共 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:解释一下这个三个配置属性的含义。

  • 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在如下三种情况任意满足之一的情况下将继续执行删除文件操作。

  • 到了删除文件的时间点,RocketMQ通过deleteWhen设置一天的固定时间执行一次删除过期文件操作,默认为凌晨4点。
  • 判断磁盘空间是否充足,如果不充足,则返回true,表示应该触发过期文件删除操作。
  • 预留,手工触发,可以通过调用excuteDeleteFilesManualy方法手工触发过期文件删除,目前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另外提供了两个与磁盘空间使用率相关的系统级参数:

  • -Drocketmq.broker.diskSpaceWarningLevelRatio=0.90:如果磁盘分区使用率超过该阔值,将设置磁盘不可写,此时会拒绝新消息的写入。
  • -Drocketmq.broker.diskSpaceCleanForciblyRatio=0.85:如果磁盘分区使用超过该阔值,建议立即执行过期文件清除,但不会拒绝新消息的写入。

判断磁盘是否可用,用当前已使用物理磁盘率maxUsedSpaceRatio、diskSpaceWarningLevelRatio、diskSpaceCleanForciblyRatio,如果当前磁盘使用率达到上述阔值,将返回true表示磁盘已满,需要进行过期文件删除操作。

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与获取磁盘分区总容量与剩余容量的方法。


备注:本文是《RocketMQ技术内幕》的前期素材,建议关注笔者的书籍:《RocketMQ技术内幕》。

见文如面,我是威哥,热衷于成体系剖析JAVA主流中间件,关注公众号『中间件兴趣圈』,回复专栏可获取成体系专栏导航,回复资料可以获取笔者的学习思维导图

在这里插入图片描述

转载地址:http://drq.baihongyu.com/

你可能感兴趣的文章
mysql 多字段删除重复数据,保留最小id数据
查看>>
MySQL 多表联合查询:UNION 和 JOIN 分析
查看>>
MySQL 大数据量快速插入方法和语句优化
查看>>
mysql 如何给SQL添加索引
查看>>
mysql 字段区分大小写
查看>>
mysql 字段合并问题(group_concat)
查看>>
mysql 字段类型类型
查看>>
MySQL 字符串截取函数,字段截取,字符串截取
查看>>
MySQL 存储引擎
查看>>
mysql 存储过程 注入_mysql 视图 事务 存储过程 SQL注入
查看>>
MySQL 存储过程参数:in、out、inout
查看>>
mysql 存储过程每隔一段时间执行一次
查看>>
mysql 存在update不存在insert
查看>>
Mysql 学习总结(86)—— Mysql 的 JSON 数据类型正确使用姿势
查看>>
Mysql 学习总结(87)—— Mysql 执行计划(Explain)再总结
查看>>
Mysql 学习总结(88)—— Mysql 官方为什么不推荐用雪花 id 和 uuid 做 MySQL 主键
查看>>
Mysql 学习总结(89)—— Mysql 库表容量统计
查看>>
mysql 实现主从复制/主从同步
查看>>
mysql 审核_审核MySQL数据库上的登录
查看>>
mysql 导入 sql 文件时 ERROR 1046 (3D000) no database selected 错误的解决
查看>>