云存储优化实战:记一次 Spring Boot 启动任务重复生成 OSS 缩略图的调试之旅!!!

云存储优化实战:记一次 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 流程图) 🗺️

下面是原始代码逻辑的工作流程图,清晰地展示了为什么会做重复的工作:

获取一个对象 key
是 (跳过)
否 (认为是原始图片)
当前页处理完
开始处理 fake-strategy 目录
分页列出 OSS 对象 (ListObjects)
遍历获取的对象
key 包含 thumb_ 或 medium_ ?
创建缩略图生成任务 (Runnable)
(异步) OssUtil.generateThumbnailsForExistingImage(key)
下载原始图片到本地
生成 thumb_ 缩略图到本地
上传 thumb_ 文件到 OSS (覆盖!)
生成 medium_ 中尺寸图到本地
上传 medium_ 文件到 OSS (覆盖!)
标记原始图片处理成功
还有下一页吗?
结束

(注意:流程图中的所有文本都按要求用双引号包裹)

可以看到,从 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 时序图):

这个时序图展示了增加检查后的交互过程:

ThumbnailService OSS Client (共享实例) ThumbnailThreadPool OssUtil listObjects("fake-strategy/") 返回对象列表 (含 imageKey) doesObjectExist(thumbKey)? thumbExists (true/false) doesObjectExist(mediumKey)? mediumExists (true/false) 记录 DEBUG 日志 (跳过) continue (处理下一个 key) execute(生成任务 for imageKey) generateThumbnailsForExistingImage(imageKey) 内部可能仍会下载、生成、上传 (覆盖) 返回结果 (隐式完成) 记录成功/失败 alt [thumbExists AND mediumExists is true] [thumbExists OR mediumExists is false (或两者都 false)] loop [对每个 imageKey (非缩略图)] ThumbnailService OSS Client (共享实例) ThumbnailThreadPool OssUtil

(注意:时序图中的文本按要求没有加双引号)

验证!眼见为实 👀📝

代码改完就大功告成了吗?不!验证是必不可少的环节。我们需要确认修改确实生效了。

  1. 开启 DEBUG 日志:在 logback-spring.xml 中为 ThumbnailService 添加 <logger name="com.productQualification.api.service.thumbnailService.ThumbnailService" level="DEBUG"/> 配置。
  2. 重启应用
  3. 观察日志
    • 期待看到的 (✅):控制台大量输出 DEBUG - Thumbnails already exist for ..., skipping generation.。最终完成时,successful 计数为 0 或接近 0。
    • 期待不再看到的 (❌):对于已处理过的图片,不再出现 INFO - Generating thumbnails for image: ... 以及后续的大量 OssUtil 上传日志。

经过验证,日志完全符合预期!大量的 DEBUG skipping 日志出现了,而 INFO 级别的生成和上传日志几乎消失了,最终成功计数为 0。问题解决!🎉

经验总结与反思 🔑

这次调试虽然花了点时间,但也收获了不少:

  1. 幂等性思维:对于可能重复执行的操作(尤其是启动任务、定时任务),一定要考虑幂等性。确保操作执行一次和执行多次的效果是一样的,或者至少后续执行不会产生副作用或资源浪费。检查目标状态是否存在是实现幂等性的常用手段。
  2. 云资源成本意识:云服务(如 OSS)的 API 调用和存储覆盖通常都是计费的。不必要的重复操作会直接导致成本增加。优化代码逻辑,避免冗余操作,就是节省真金白银 💰。
  3. 日志是破案关键:详细且级别分明的日志是快速定位问题的利器。INFO 级别记录关键流程,DEBUG 级别记录判断逻辑和详细步骤,ERROR 级别记录异常情况。这次如果没有 DEBUG 日志来确认跳过逻辑,验证起来会困难得多 📊。
  4. 性能优化无止境:即使修复了重复生成的问题,还发现 OssUtil 内部频繁创建/销毁 OSS 客户端。对于批量任务,共享客户端实例是进一步提升性能的方向 ✨。

知识点回顾 (Markdown 思维导图) 🧠

在这里插入图片描述


希望这次的调试经历分享对你有所帮助!如果你也遇到过类似的问题,或者有更好的解决思路,欢迎在评论区交流!👇👇👇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值