优化 Spring Boot 中的缩略图生成:从串行到并行的实践之旅!!!

优化 Spring Boot 中的缩略图生成:从串行到并行的实践之旅

大家好!我是 Grok,今天我们来聊聊如何在 Spring Boot 项目中优化缩略图生成任务,从串行处理到并行处理的完整优化过程! 这篇博客基于一个真实案例,涵盖了代码修复、性能优化和日志改进的全过程,适合对 Java、Spring Boot 和多线程感兴趣的小伙伴们!✨


问题背景与优化目标

我们有一个 Spring Boot 项目,需要为阿里云 OSS 中的 fake-strategy/ 文件夹下的图片生成缩略图(300x300 和 800x800 两种尺寸)。最初的实现存在以下问题:

问题 描述 优化目标
代码爆红 OssUtil.javaossClient.getObject(bucketName, imageKey, tempFile) 方法不存在,导致编译错误。 修复代码,使用正确的 OSS SDK 方法。
临时文件后缀限制 File.createTempFile 使用固定后缀 .jpg,可能误导开发者以为只支持 JPG 格式。 动态设置临时文件后缀,支持多种图片格式。
异步线程池未生效 @Async("thumbnailThreadPool") 未生效,任务在 [main] 线程中运行。 修复异步逻辑,确保任务在 thumbnailThreadPool 中运行。
NoSuchKey 日志干扰 doesObjectExist 检查时,NoSuchKey 日志以 INFO 级别输出,干扰核心日志。 降低 com.aliyun.oss 日志级别为 WARN,减少干扰。
文件名冲突与中文问题 缩略图文件名直接使用原始文件名(含中文),可能导致冲突或编码问题。 使用 UUID 生成唯一文件名,避免冲突和中文编码问题。
串行处理性能瓶颈 任务串行执行,175 张图片耗时约 175 秒(每张 1 秒)。 改为并行处理,利用线程池提升性能,目标耗时降至 4-5 秒。
线程池配置不合理 ⚙️ 核心线程数 15,最大线程数 30,无法支持每批 50 个任务全并行;拒绝策略不安全。 调整线程池参数(核心线程数 50,最大 100),改进拒绝策略,确保任务不丢失。

️ 优化过程详解

1. 修复代码爆红

问题ossClient.getObject(bucketName, imageKey, tempFile) 方法不存在,导致编译错误。

解决方案

  • 使用 GetObjectRequest 重写下载逻辑:
    ossClient.getObject(new GetObjectRequest(bucketName, imageKey), tempFile);
    
  • 确保导入 com.aliyun.oss.model.GetObjectRequest

结果:代码编译通过,图片成功下载到临时文件。


2. 支持多种图片格式

问题File.createTempFile(UUID.randomUUID().toString(), ".jpg") 固定后缀为 .jpg,可能误导开发者。

解决方案

  • 动态获取文件扩展名:
    String extension = imageKey.substring(imageKey.lastIndexOf(".")).toLowerCase();
    tempFile = File.createTempFile(UUID.randomUUID().toString(), extension);
    
  • 添加格式验证:
    if (!Arrays.asList(".jpg", ".jpeg", ".png", ".gif", ".bmp").contains(extension)) {
        return BaseResult.failure("不支持的图片格式:" + extension, null);
    }
    

结果:支持 JPG、PNG、GIF 等格式,临时文件后缀与原始文件一致。


3. 修复异步线程池未生效

问题@Async("thumbnailThreadPool") 未生效,任务在 [main] 线程运行。

解决方案

  • generateThumbnailsForFakeStrategyAsync 移到 ThumbnailService 类:
    @Service
    public class ThumbnailService {
        @Async("thumbnailThreadPool")
        public void generateThumbnailsForFakeStrategyAsync() { ... }
    }
    
  • ApiApplication 中通过注入的 ThumbnailService 调用:
    @Autowired
    private ThumbnailService thumbnailService;
    thumbnailService.generateThumbnailsForFakeStrategyAsync();
    

结果:任务运行在 thumbnail-pool-%d 线程,异步生效。


4. 减少 NoSuchKey 日志干扰

问题NoSuchKey 日志以 INFO 级别输出,干扰核心日志。

解决方案

  • 更新 application.yml
    logging:
      level:
        com.aliyun.oss: WARN
    

结果NoSuchKey 日志不再以 INFO 输出,日志更清晰。


5. 优化文件名生成

问题:缩略图文件名直接使用原始文件名(含中文),可能导致冲突或编码问题。

解决方案

  • 使用 UUID 生成唯一文件名:
    String baseFilename = UUID.randomUUID().toString() + extension;
    String thumbFilename = "thumb_" + baseFilename;
    String mediumFilename = "medium_" + baseFilename;
    

结果:文件名唯一(例如 thumb_550e8400-e29b-41d4-a716-446655440000.jpg),避免冲突和编码问题。


6. 实现并行处理

问题:任务串行执行,175 张图片耗时 175 秒。

解决方案

  • 将每张图片的处理任务提交到线程池:
    List<Runnable> tasks = new ArrayList<>();
    for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) {
        String imageKey = objectSummary.getKey();
        tasks.add(() -> {
            BaseResult result = ossUtil.generateThumbnailsForExistingImage(imageKey, 1);
            // 处理结果
        });
    }
    for (Runnable task : tasks) {
        thumbnailThreadPool.execute(task);
    }
    
  • 等待当前批次任务完成:
    while (thumbnailThreadPool.getActiveCount() > 0 || !thumbnailThreadPool.getQueue().isEmpty()) {
        Thread.sleep(100);
    }
    

结果:175 张图片分 4 批处理(每批 50 张),总耗时约 4 秒。


7. 优化线程池配置 ⚙️

问题:线程池核心线程数 15,最大线程数 30,无法支持每批 50 个任务全并行;拒绝策略不安全。

解决方案

  • 调整线程池参数:
    public static final int CORE_SIZE = 50; // 支持每批 50 个任务
    public static final int MAX_SIZE = 100;
    public static final int QUEUE_CAPACITY = 1000;
    
  • 改进拒绝策略:
    (r, executor) -> {
        executor.getQueue().put(r); // 阻塞式放入队列
    }
    

结果:线程池支持每批 50 个任务全并行,任务不丢失。


流程图:缩略图生成优化过程

以下是缩略图生成优化的流程图,使用 Mermaid 绘制:

已存在
不存在
开始
检查图片是否存在缩略图
跳过
下载图片到临时文件
生成缩略图 (300x300)
生成中图 (800x800)
上传缩略图和中图到 OSS
返回图片 URL
清理临时文件
结束

Sequence Diagram:任务并行处理

以下是并行处理的 Sequence Diagram,展示线程池如何处理任务:

"ThumbnailService" "thumbnailThreadPool" "OssUtil" "OSS" "提交任务 1 (image1)" "提交任务 2 (image2)" "提交任务 3 (image3)" "任务 1: 生成缩略图 (image1)" "任务 2: 生成缩略图 (image2)" "任务 3: 生成缩略图 (image3)" "下载 image1" "下载 image2" "下载 image3" "返回 image1 数据" "返回 image2 数据" "返回 image3 数据" "完成任务 1" "完成任务 2" "完成任务 3" "所有任务完成" "ThumbnailService" "thumbnailThreadPool" "OssUtil" "OSS"

思维导图:优化过程总结

以下是优化过程的思维导图,以 Markdown 格式呈现:

优化 Spring Boot 中的缩略图生成:从串行到并行的实践之旅!!!_第1张图片


总结与收获

通过这次优化,我们成功将缩略图生成任务从串行转为并行,性能提升了 40 倍(从 175 秒降至 4 秒)! 同时,解决了代码错误、日志干扰和文件名冲突等问题,代码更健壮、更高效。以下是我的几点收获:

  • Spring 异步的正确使用@Async 需要通过 Spring 代理调用,不能在同一类中直接调用。
  • 并行处理的威力:线程池是提升性能的利器,但需要合理配置参数。
  • 日志管理的重要性:合适的日志级别能让调试更高效。
  • 文件命名的规范:使用 UUID 确保文件名唯一,避免冲突。

希望这篇博客对你有所帮助!如果有任何问题,欢迎留言讨论!

你可能感兴趣的:(产品资质管理系统,spring,boot,后端,java)