云存储优化实战:记一次 Spring Boot 启动任务重复生成 OSS 缩略图的调试之旅 🧐💡☁️
哈喽,各位开发者伙伴们!👋 在日常开发中,我们经常会遇到一些看似诡异的问题。程序明明上次运行得好好的,怎么这次启动就感觉哪里不对劲?🤔 最近,我就遇到了一个关于 Spring Boot 应用启动时处理阿里云 OSS 图片缩略图的任务表现异常的情况。每次应用启动,它都“勤勤恳恳”地把一个文件夹里的所有图片缩略图重新生成并上传了一遍,即使这些缩略图早就存在了!这不仅拖慢了启动速度 ⏳,还造成了不必要的 OSS 请求和覆盖操作 💸。
经过一番排查,最终定位并解决了问题。今天,就和大家分享一下这次“破案”的全过程,希望能给大家带来一些启发。✨
问题现象与分析 (表格总结) 📊
首先,我们用一个表格来梳理一下遇到的情况:
方面 | 描述 | 表情符号 |
---|---|---|
问题表现 | Spring Boot 应用每次启动时,后台任务都会处理 fake-strategy 目录下的所有图片,生成并上传缩略图。 | 🔁⏳ |
日志症状 | 控制台充斥着大量 Generating thumbnails for image... 、开始使用 ossClient 上传文件... (thumb_ 和 medium_ ) 日志。 | 📜 |
初步怀疑 | 是不是任务调度逻辑有问题?或者是有新文件加入了? | 🤔❓ |
核心疑点 | 为什么不检查缩略图是否已存在就直接生成和上传? | 🎯 |
根本原因 | ThumbnailService 在触发生成逻辑前,未检查目标缩略图文件 (thumb_... , medium_... ) 是否已存在于 OSS。 | ❌🔍 |
解决方案 | 在 ThumbnailService 中,调用生成方法前,增加使用 ossClient.doesObjectExist() 检查目标文件存在性的逻辑。 | ✅💡 |
验证方法 | 开启 ThumbnailService 的 DEBUG 日志,观察是否打印跳过信息,以及原有的生成/上传 INFO 日志是否消失。 | 👀📝 |
原始的“冤枉路”流程 (Mermaid 流程图) 🗺️
下面是原始代码逻辑的工作流程图,清晰地展示了为什么会做重复的工作:
(注意:流程图中的所有文本都按要求用双引号包裹)
可以看到,从 D 到 E 这一步,只要判断是原始图片,就直接去执行 F 及后续步骤了,完全没有检查 K 和 I 对应的文件是否已经存在于 OSS!
修复:增加“前置检查”环节 (代码与时序图) 🛠️
为了解决这个问题,我们在 ThumbnailService
的循环中,决定处理一个原始图片之前,加入了关键的存在性检查。
修改后的核心代码片段 (ThumbnailService.java
):
// ... (循环和过滤逻辑) ...
for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) {
String imageKey = objectSummary.getKey();
if (imageKey.endsWith("/") || imageKey.contains("thumb_") || imageKey.contains("medium_")) { // 优化了过滤
continue;
}
// ---- 新增:检查目标缩略图是否存在 ----
String thumbKey = generateThumbnailKey(imageKey, "thumb_");
String mediumKey = generateThumbnailKey(imageKey, "medium_");
boolean thumbExists = false;
boolean mediumExists = false;
try {
// 使用同一个 ossClient 实例进行检查
thumbExists = ossClient.doesObjectExist(ossBucketName, thumbKey);
mediumExists = ossClient.doesObjectExist(ossBucketName, mediumKey);
} catch (OSSException oe) {
logger.error("OSS error checking existence for key {}: {}", imageKey, oe.getErrorMessage());
failureCount.incrementAndGet();
logFailedImage(imageKey, "OSS error checking existence: " + oe.getErrorMessage());
continue; // 出错则跳过
} catch (Exception ex) {
logger.error("Error checking existence for key {}: {}", imageKey, ex.getMessage());
failureCount.incrementAndGet();
logFailedImage(imageKey, "Error checking existence: " + ex.getMessage());
continue; // 出错则跳过
}
// 如果两个缩略图都已经存在,则跳过
if (thumbExists && mediumExists) {
logger.debug("Thumbnails already exist for {}, skipping generation.", imageKey); // DEBUG 日志用于验证
continue; // !! 关键的跳过 !!
}
// ---- 检查结束 ----
// 只有在需要时才创建任务
tasks.add(() -> {
logger.info("Generating thumbnails for image: {}", imageKey); // 这个 INFO 日志现在只会在需要生成时打印
BaseResult result = ossUtil.generateThumbnailsForExistingImage(imageKey, 1); // 传入 ossClient 实例会更好
// ... (处理结果) ...
});
// ... (其他逻辑) ...
}
修正后的检查与执行时序 (Mermaid 时序图):
这个时序图展示了增加检查后的交互过程:
(注意:时序图中的文本按要求没有加双引号)
验证!眼见为实 👀📝
代码改完就大功告成了吗?不!验证是必不可少的环节。我们需要确认修改确实生效了。
- 开启 DEBUG 日志:在
logback-spring.xml
中为ThumbnailService
添加<logger name="com.productQualification.api.service.thumbnailService.ThumbnailService" level="DEBUG"/>
配置。 - 重启应用。
- 观察日志:
- 期待看到的 (✅):控制台大量输出
DEBUG - Thumbnails already exist for ..., skipping generation.
。最终完成时,successful
计数为 0 或接近 0。 - 期待不再看到的 (❌):对于已处理过的图片,不再出现
INFO - Generating thumbnails for image: ...
以及后续的大量OssUtil
上传日志。
- 期待看到的 (✅):控制台大量输出
经过验证,日志完全符合预期!大量的 DEBUG skipping 日志出现了,而 INFO 级别的生成和上传日志几乎消失了,最终成功计数为 0。问题解决!🎉
经验总结与反思 🔑
这次调试虽然花了点时间,但也收获了不少:
- 幂等性思维:对于可能重复执行的操作(尤其是启动任务、定时任务),一定要考虑幂等性。确保操作执行一次和执行多次的效果是一样的,或者至少后续执行不会产生副作用或资源浪费。检查目标状态是否存在是实现幂等性的常用手段。
- 云资源成本意识:云服务(如 OSS)的 API 调用和存储覆盖通常都是计费的。不必要的重复操作会直接导致成本增加。优化代码逻辑,避免冗余操作,就是节省真金白银 💰。
- 日志是破案关键:详细且级别分明的日志是快速定位问题的利器。INFO 级别记录关键流程,DEBUG 级别记录判断逻辑和详细步骤,ERROR 级别记录异常情况。这次如果没有 DEBUG 日志来确认跳过逻辑,验证起来会困难得多 📊。
- 性能优化无止境:即使修复了重复生成的问题,还发现
OssUtil
内部频繁创建/销毁 OSS 客户端。对于批量任务,共享客户端实例是进一步提升性能的方向 ✨。
知识点回顾 (Markdown 思维导图) 🧠
希望这次的调试经历分享对你有所帮助!如果你也遇到过类似的问题,或者有更好的解决思路,欢迎在评论区交流!👇👇👇