硬核實(shí)戰(zhàn)!SpringBoot + Minio 定時(shí)清理,輕松奪回海量存儲(chǔ)空間!
在日常開(kāi)發(fā)中,我們往往選擇 MinIO 作為項(xiàng)目的圖片或文件存儲(chǔ)服務(wù)。它不僅兼容 S3 協(xié)議,還能在本地快速搭建分布式存儲(chǔ)環(huán)境,方便又高效。 但隨著業(yè)務(wù)增長(zhǎng),存儲(chǔ)在 MinIO 中的圖片會(huì)呈現(xiàn) 指數(shù)級(jí)上漲:活動(dòng)頁(yè)上傳的 Banner、用戶頭像歷史版本、報(bào)表導(dǎo)出的臨時(shí)文件……一段時(shí)間后,它們大多不再被使用,卻依舊占據(jù)存儲(chǔ)空間。
如果我們不對(duì)這些“過(guò)期文件”進(jìn)行定期清理,不僅存儲(chǔ)成本會(huì)增加,還可能影響系統(tǒng)長(zhǎng)期運(yùn)行的可維護(hù)性。因此,本文將通過(guò) SpringBoot + MinIO + 定時(shí)任務(wù) 的方式,實(shí)現(xiàn)一個(gè)自動(dòng)清理歷史文件的功能。
最終效果:
- 文件按日期目錄(yyyy-MM-dd/)存儲(chǔ)
- 每月定時(shí)任務(wù)執(zhí)行,清理掉早于指定時(shí)間的目錄
- 自動(dòng)釋放存儲(chǔ)空間,降低成本
項(xiàng)目依賴
在 pom.xml 中添加核心依賴即可:
<!-- MinIO SDK -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.1</version>
</dependency>說(shuō)明:
- MinIO SDK:與 MinIO 服務(wù)交互,支持上傳、下載、刪除等操作
- Spring Boot Starter:內(nèi)置定時(shí)任務(wù)支持,免額外引入依賴
核心刪除邏輯
文件的目錄結(jié)構(gòu)約定為:
/bucketName/yyyy-MM-dd/xxx.jpeg也就是說(shuō),每天的文件會(huì)放到一個(gè)獨(dú)立的日期目錄下。 因此我們的目標(biāo)是:刪除早于指定日期的整個(gè)目錄。
工具類(lèi) MinioUtil
項(xiàng)目路徑:
/src/main/java/com/icoderoad/utils/MinioUtil.java核心方法:
方法簽名 | 作用 | 返回值 | 冪等性 |
| 刪除截止日期前的所有日期目錄 | 實(shí)際刪除對(duì)象數(shù) | 多次調(diào)用結(jié)果一致 |
| 刪除單個(gè)日期目錄下的對(duì)象 | 刪除數(shù)量 | 同上 |
關(guān)鍵代碼:
/**
* 刪除早于指定日期的所有日期目錄(yyyy-MM-dd/)
*
* @param endExclusive 截止日期(不含)
* @return 實(shí)際刪除的對(duì)象總數(shù)
*/
public int deleteDateFoldersBefore(LocalDate endExclusive) {
if (endExclusive == null) {
throw new IllegalArgumentException("指定日期不能為空");
}
LocalDate today = LocalDate.now();
if (!endExclusive.isBefore(today)) {
return 0;
}
int totalDeleted = 0;
for (LocalDate d = endExclusive.minusDays(1); !d.isBefore(retainSince); d = d.minusDays(1)) {
totalDeleted += deleteSingleFolder(d.format(DateTimeFormatter.ISO_LOCAL_DATE) + "/");
}
return totalDeleted;
}刪除單個(gè)目錄;
private int deleteSingleFolder(String prefix) {
try {
List<DeleteObject> objects = new ArrayList<>();
minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(true).build()
).forEach(r -> {
try {
objects.add(new DeleteObject(r.get().objectName()));
} catch (Exception ignored) {
log.warn("文件名獲取失敗");
}
});
if (objects.isEmpty()) {
return 0;
}
Iterable<Result<DeleteError>> results = minioClient.removeObjects(
RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build()
);
for (Result<DeleteError> res : results) {
res.get(); // 必須觸發(fā)懶加載請(qǐng)求
}
return objects.size();
} catch (Exception e) {
log.warn("刪除目錄 {} 失敗: {}", prefix, e.toString());
return 0;
}
}性能與容錯(cuò)
- 懶加載陷阱:listObjects 與 removeObjects 均是延遲執(zhí)行,必須遍歷結(jié)果才會(huì)真正觸發(fā)請(qǐng)求
- 批量刪除限制:MinIO 單次請(qǐng)求最多刪除 1000 個(gè)對(duì)象
- 冪等性設(shè)計(jì):重復(fù)刪除同一路徑不會(huì)報(bào)錯(cuò),已刪除的對(duì)象會(huì)被跳過(guò)
- 常見(jiàn)錯(cuò)誤處理:
NoSuchBucket → 啟動(dòng)時(shí)校驗(yàn)桶
AccessDenied → 確認(rèn) AK/SK 權(quán)限
SlowDown → 增加退避重試策略
單元測(cè)試
路徑:/src/test/java/com/icoderoad/MinioTest.java
@SpringBootTest
public class MinioTest {
@Autowired
private MinioUtil minioUtil;
@Test
public void testDelete() {
int count = minioUtil.deleteDateFoldersBefore(LocalDate.of(2025, 8, 2));
System.out.println("刪除文件數(shù)量:" + count);
}
}定時(shí)任務(wù)配置
啟用定時(shí)任務(wù)
在啟動(dòng)類(lèi)中開(kāi)啟:
@SpringBootApplication
@EnableScheduling
public class StorageApplication {
public static void main(String[] args) {
SpringApplication.run(StorageApplication.class, args);
}
}定時(shí)任務(wù)類(lèi)
路徑:/src/main/java/com/icoderoad/task/MinioCleanTask.java
@Component
@RequiredArgsConstructor
@Slf4j
public class MinioCleanTask {
private final MinioUtil minioUtil;
/**
* 每月 1 號(hào)凌晨 3 點(diǎn)清理早于當(dāng)天的目錄
*/
@Scheduled(cron = "0 0 3 1 * ?")
public void minioClean() {
try {
LocalDate today = LocalDate.now();
log.info("清理任務(wù)開(kāi)始,清理日期:{}", today);
int deleteCount = minioUtil.deleteDateFoldersBefore(today);
log.info("任務(wù)完成,共清理 {} 個(gè)文件", deleteCount);
} catch (Exception e) {
log.error("MinIO 清理任務(wù)失敗", e);
}
}
}Cron 表達(dá)式快速回顧
表達(dá)式 | 含義 |
| 每分鐘執(zhí)行一次 |
| 每 5 分鐘執(zhí)行一次 |
| 每天凌晨 1 點(diǎn)執(zhí)行 |
| 每月 1 日凌晨 3 點(diǎn)執(zhí)行 |
結(jié)論
通過(guò) SpringBoot + MinIO + 定時(shí)任務(wù) 的組合,我們實(shí)現(xiàn)了一個(gè)高效的存儲(chǔ)清理方案:
- 自動(dòng)化:無(wú)需人工干預(yù),定時(shí)任務(wù)定期清理
- 可控性:基于日期前綴,刪除邏輯清晰,冪等性保證安全
- 擴(kuò)展性:可靈活配置保留日期與清理策略
這不僅幫助我們 節(jié)省了大量存儲(chǔ)成本,還提升了系統(tǒng)的長(zhǎng)期可維護(hù)性。 對(duì)于任何依賴對(duì)象存儲(chǔ)的系統(tǒng)而言,這種清理機(jī)制都是必不可少的。
























