智能协图云图库学习笔记day6-主流图片优化技术

图片优化主要有以下四点:查询优化,上传优化,加载优化,存储优化。形成的逻辑链如下:

复制代码
用户上传图片 → 后端处理/存储 → 前端查询图片 → 前端加载展示 → 长期存储维护
(上传优化)    (存储优化)    (查询优化)    (加载优化)    (存储优化)

图片查询优化

**方法:**主要使用缓存进行优化,降低数据库压力,提高系统性能。读多写少的情况适合缓存。

以缓存key设计-缓存value设计-缓存过期时间设置的思维链展开。

Redis 分布式缓存

节点 Redis 的读写 QPS 可达 10w 次每秒

缓存设计

缓存key设计

查询条件+项目业务前缀作为key的实现,再通过mad5哈希算法压缩JSON字符串

缓存value设计

选择合适的数据结构

从数据库中读取的Page分页对象->JSON格式字符串/二进制->redis的string数据结构

缓存过期时间设置

根据业务具体选择,为避免缓存雪崩可加入随机数

后端开发

引入依赖-redis配置-业务代码逻辑

java 复制代码
@PostMapping("/list/page/vo/cache")
public BaseResponse<Page<PictureVO>> listPictureVOByPageWithCache(@RequestBody PictureQueryRequest pictureQueryRequest,
                                                         HttpServletRequest request) {
    long current = pictureQueryRequest.getCurrent();
    long size = pictureQueryRequest.getPageSize();
    
    ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
    
    pictureQueryRequest.setReviewStatus(PictureReviewStatusEnum.PASS.getValue());

    
    String queryCondition = JSONUtil.toJsonStr(pictureQueryRequest);
    String hashKey = DigestUtils.md5DigestAsHex(queryCondition.getBytes());
    String redisKey = "yupicture:listPictureVOByPage:" + hashKey;
    
    ValueOperations<String, String> valueOps = stringRedisTemplate.opsForValue();
    String cachedValue = valueOps.get(redisKey);
    if (cachedValue != null) {
        
        Page<PictureVO> cachedPage = JSONUtil.toBean(cachedValue, Page.class);
        return ResultUtils.success(cachedPage);
    }

    
    Page<Picture> picturePage = pictureService.page(new Page<>(current, size),
            pictureService.getQueryWrapper(pictureQueryRequest));
    
    Page<PictureVO> pictureVOPage = pictureService.getPictureVOPage(picturePage, request);

    
    String cacheValue = JSONUtil.toJsonStr(pictureVOPage);
    
    int cacheExpireTime = 300 +  RandomUtil.randomInt(0, 300);
    valueOps.set(redisKey, cacheValue, cacheExpireTime, TimeUnit.SECONDS);

    
    return ResultUtils.success(pictureVOPage);
}

要点:

1.通过StringRedisTemplate操作redis

2.ValueOperations操作字符串

3.DigestUtils.md5DigestAsHex()生成hashkey

4.JSONUtil.toBean(cachedValue,Page.class)实现value与java对象转换(反序列化)

5.JSONUtil.toJsonStr()实现java对象转换成json字符串(序列化)

6.从50ms优化到30ms

Caffeine 本地缓存

将这些数据缓存到应用的内存中(比如 JVM 中),适用于数据访问量比较小的单机应用。

缓存设计

与分布式缓存类似,但由于在本地服务器内存,因此key应该再精简一点。

后端开发

引入依赖-构建本地缓存数据结构(容量与过期时间)-后端业务代码逻辑

由于和本地缓存流程相同(查缓存-命中返回-未命中查数据库-添加缓存),可使用模板方法进行优化

多级缓存

这部分在黑马点评里有更详细的介绍。

要点:

优化成果从50ms->10ms,提高80%。

其它查询优化策略

数据库查询优化,识别热点图片缓存

图片上传优化

**方法:**图片压缩,图片秒传、分片上传、断点续传

图片压缩

方案设计

压缩格式:

WebP格式与AVIF格式

AVIF格式兼容性不如WebP,但比WebP体积更小。

压缩方案:

本地图片处理类库or第三方云服务(数据万象)

数据万象可在访问时实时压缩也可上传图片时实时压缩。

后端开发

CosManager修改-处理存入Cos的逻辑

java 复制代码
public PutObjectResult putPictureObject(String key, File file) {
    PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key,
            file);
    
    PicOperations picOperations = new PicOperations();
    
    picOperations.setIsPicInfo(1);
    List<PicOperations.Rule> rules = new ArrayList<>();
    
    String webpKey = FileUtil.mainName(key) + ".webp";
    PicOperations.Rule compressRule = new PicOperations.Rule();
    compressRule.setRule("imageMogr2/format/webp");
    compressRule.setBucket(cosClientConfig.getBucket());
    compressRule.setFileId(webpKey);
    rules.add(compressRule);
    
    picOperations.setRules(rules);
    putObjectRequest.setPicOperations(picOperations);
    return cosClient.putObject(putObjectRequest);
}

要点:

1.前缀修改为WebP

2.通过数据万象转换图片格式,设置规则

3.实际存储会存原图和压缩图两种。可选择删除原图

PictureUploadTemplate修改-处理存入数据库(前端的修改)

java 复制代码
try {
    
    file = File.createTempFile(uploadPath, null);
    
    processFile(inputSource, file);
    
    PutObjectResult putObjectResult = cosManager.putPictureObject(uploadPath, file);
    ImageInfo imageInfo = putObjectResult.getCiUploadResult().getOriginalInfo().getImageInfo();
    ProcessResults processResults = putObjectResult.getCiUploadResult().getProcessResults();
    List<CIObject> objectList = processResults.getObjectList();
    if (CollUtil.isNotEmpty(objectList)) {
        CIObject compressedCiObject = objectList.get(0);
        
        return buildResult(originFilename, compressedCiObject);
    }
    
    return buildResult(originFilename, file, uploadPath, imageInfo);
} catch (Exception e) {
    log.error("图片上传到对象存储失败", e);
    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败");
}

private UploadPictureResult buildResult(String originFilename, CIObject compressedCiObject) {
    UploadPictureResult uploadPictureResult = new UploadPictureResult();
    int picWidth = compressedCiObject.getWidth();
    int picHeight = compressedCiObject.getHeight();
    double picScale = NumberUtil.round(picWidth * 1.0 / picHeight, 2).doubleValue();
    uploadPictureResult.setPicName(FileUtil.mainName(originFilename));
    uploadPictureResult.setPicWidth(picWidth);
    uploadPictureResult.setPicHeight(picHeight);
    uploadPictureResult.setPicScale(picScale);
    uploadPictureResult.setPicFormat(compressedCiObject.getFormat());
    uploadPictureResult.setPicSize(compressedCiObject.getSize().longValue());
    
    uploadPictureResult.setUrl(cosClientConfig.getHost() + "/" + compressedCiObject.getKey());
    return uploadPictureResult;

要点:

复制代码
1.如果转换成功,则返回转换后的图片信息,否则返回原始图片信息

文件秒传

文件秒传是一种基于文件的唯一标识(如 MD5、SHA-256)对文件内容进行快速校验,避免重复上传的方法,在大型文件传输场景下非常重要。可以提高性能、节约带宽和存储资源。

原理:

1)客户端生成文件唯一标识:上传前,通过客户端计算文件的哈希值(如 MD5、SHA-256),生成文件的唯一指纹。

2)服务端校验文件指纹:后端接收到文件指纹后,在存储中查询是否已存在相同文件。

  • 若存在相同文件,则直接返回文件的存储路径。
  • 若不存在相同文件,则接收并存储新文件,同时记录其指纹信息。
java 复制代码
String md5 = SecureUtil.md5(file);

List<Picture> pictureList = pictureService.lambdaQuery()
        .eq(Picture::getMd5, md5)
        .list();

if (CollUtil.isNotEmpty(pictureList)) {
    
    Picture existPicture = pictureList.get(0);
} else {
    
}

为什么不用:

1.文件小,重复图片少,性能优化不明显

2.本项目使用腾讯云 COS 的对象存储,只能通过唯一地址去取文件,无法完全自定义文件的存储结构、也不支持文件快捷方式的概念,因此秒传的文件地址必须使用和原文件相同的对象路径,可能导致用户 A 上传的图片地址等同于用户 B 上传的地址。

分片上传和断点续传

分片上传: 将一个大文件 切割成多个固定大小的小分片,逐个上传到服务端,最后由服务端合并分片

断点续传: 基于分片上传,在上传中断后(网络断、客户端崩溃),下次上传时跳过已传成功的分片,只传未完成的

原理(腾讯云COS):

分片上传:

  1. 切割文件

    • 客户端把大文件(比如 1GB 的视频)切割成多个小分片,比如按 1MB 一个分片,最终切成 1000 个分片。
    • 给每个分片编唯一序号(从 1 到 1000),同时计算整个文件的 MD5(用于校验文件完整性)、每个分片的 MD5(用于校验分片是否传错)。
  2. 初始化分片上传

    • 客户端调用 COS 的 InitiateMultipartUpload API,告诉服务端:"我要上传一个大文件,文件名是 xxx,分片数量是 xxx"。
    • 服务端返回一个 UploadId(分片上传的唯一标识),后续所有分片上传都要带这个 ID,服务端才知道这些分片属于同一个文件。
  3. 并行上传分片

    • 客户端并发上传 所有分片(比如同时传 5 个分片),每个分片上传时要带 3 个关键信息:
      • UploadId:分片所属的文件标识;
      • PartNumber:分片序号(保证服务端合并顺序正确);
      • 分片的 MD5:服务端校验分片是否完整。
    • 服务端接收分片后,会保存每个分片,并记录 "哪个 UploadId 的哪个序号分片已上传成功"。
  4. 合并分片

    • 当所有分片都上传成功后,客户端调用 COS 的 CompleteMultipartUpload API,告诉服务端:"所有分片都传完了,麻烦合并成完整文件"。
    • 服务端根据 UploadId 找到所有对应的分片,按 PartNumber 顺序合并,生成最终文件;如果有分片丢失 / 损坏,会拒绝合并并提示错误。

断点续传(基于分片的 "进度记录")

  • 上传前:查询已传分片

    • 客户端在上传大文件前,先调用 COS 的 ListParts API,传入 UploadId,查询:"这个文件之前已经传了哪些分片?"
    • 服务端返回已上传成功的分片序号列表(比如已传 1-500 分片)。
  • 跳过已传分片,只传未完成的

    • 客户端对比本地分片列表和服务端返回的列表,只上传未传的分片(比如 501-1000 分片)。
    • 如果是第一次上传,服务端返回空列表,就正常上传所有分片。
  • 上传中断:自动保存进度

    • 上传过程中如果网络断开 / 客户端崩溃,已上传的分片会保存在服务端,进度不会丢失。
    • 下次上传时重复步骤 1-2,继续未完成的部分。

直接使用对象存储的SDK即可

为什么不用

文件较小,性能优化不明显

图片加载优化

**方法:**缩略图、懒加载、CDN 加速、浏览器缓存

缩略图

上传图片时,同时生成一份较小尺寸的缩略图。用户浏览图片列表时加载缩略图,只有在进入详情页或下载时才加载原图。

方案设计:

与图片压缩类似,可使用本地图像处理or第三方云服务

第三方云服务就是使用数据万象增添规则。

后端开发:

增加缩略图url字段-CosManager修改-PictureUploadTemplate修改

要点:

1**.**仅对 > 20 KB 的图片生成缩略图

2.如果没有生成缩略图,则缩略图等于压缩图

懒加载

后端:优化分页

前端:

1)使用 HTML5 原生的 loading="lazy" 属性。

2)使用 JS 的 Intersection Observer,这个 API 能够检测元素是否进入视口,参考实现如下:

  1. 将图片的真实 src 替换为一个占位属性(如 data-src)。
  2. 使用 Intersection Observer 监听图片是否进入视口。
  3. 当图片进入视口时,将 data-src 的值赋给 src,触发加载。

3)使用 JS 监听页面滚动事件实现。每次页面滚动时,判断图片是否进入可视区域;如果是,则给图片增加 src 属性,触发图片加载。

4)使用现成的组件库或类库实现,比如 lazysizes 库

渐进式加载:和懒加载技术类似,先加载低分辨率或低质量的占位资源(如模糊的图片缩略图),在用户访问或等待期间逐步加载高分辨率的完整资源,加载完成后再替换掉占位资源。

CDN 加速

CDN(内容分发网络)是通过将图片文件分发到全球各地的节点,用户访问时从离自己最近的节点获取资源的技术,常用于文件资源或后端动态请求的网络加速,也能大幅分摊源站的压力、支持更多请求同时访问,是性能提升的利器。

  1. 图片文件由 源站(如 COS 对象存储、或者服务器)上传至 CDN 服务进行缓存。
  2. 当用户请求图片时,CDN 会根据用户的地理位置,返回离用户 最近的 CDN 节点缓存的图片资源。
  3. 未命中缓存的图片将从源站获取,并缓存在 CDN 节点,供后续用户访问,俗称 回源

如何使用:在腾讯云开通就好了,CDN 结合 COS 的文档

最佳实践方案:

1)缓存策略:为静态资源(如图片、CSS、JS)设置长期缓存时间,可以减少回源的次数和消耗。

2)防盗链:配置 Referer 防盗链保护资源,比如仅允许自己的域名可以加载图片(在COS里设置)

3)IP 限制:根据需要配置 IP 黑白名单,限制不必要的访问。

4)HTTPS 配置:配置有效的 SSL 证书,启用 HTTPS 传输,提高请求的安全性。

5)** 监控告警:这点尤为重要!** 一定要给 CDN 配置监控告警,比如设置一段时间内最多消耗的流量,超出时会自动发短信告警,避免费用超额;或者限制单个 IP 的请求频率,防止突发流量影响服务。

6)CDN 节点选择:国内业务选择覆盖中国大陆的节点就足够了,非必要的话,不要开通全球 CDN 节点,容易遭受海外攻击。

7)访问日志:开启访问日志,分析用户行为和流量来源,这个能力更适合业务访问量较大的场景。

浏览器缓存

**强缓存:**给资源设置 "保质期",过期前直接用本地缓存,完全不请求服务器

协商缓存资源过期后,浏览器带着 "资源标识" 问服务器「这个资源更新了吗?」,没更新就继续用本地缓存,更新了才下载新资源

**具体实现:后端配置,**腾讯云 COS/CDN 配置缓存规则

后端配置

java 复制代码
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CacheControlInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 只对图片等静态资源设置缓存
        String requestURI = request.getRequestURI();
        if (requestURI.endsWith(".jpg") || requestURI.endsWith(".png") || requestURI.endsWith(".webp")) {
            // 强缓存:设置图片缓存30天(2592000秒)
            response.setHeader("Cache-Control", "public, max-age=2592000");
            // 兼容HTTP 1.0的Expires(可选)
            response.setHeader("Expires", String.valueOf(System.currentTimeMillis() + 2592000 * 1000));
            // 协商缓存:设置ETag(基于文件内容的哈希值)
            // 可以用Spring的ShallowEtagHeaderFilter自动生成,或自己计算文件MD5
        }
        return true;
    }
}

拦截器中设置Cache-Control。

这个浏览器中f12的禁用缓存是什么?

只是浏览器本身设置,如果不开启浏览器默认启发式缓存不可靠,因此需要开发者在后端或COS配置增添缓存规则。

图片存储优化

方法:数据沉降,清理策略

数据沉降

将长时间未访问的数据自动迁移到低频访问存储,从而降低存储成本。

数据沉降和 冷热数据分离 的概念是比较接近的,冷热数据分离是根据数据的访问热度,将访问频繁的数据(热数据)和访问较少的数据(冷数据)存储在不同的存储层中。

通过腾讯云cos的生命周期实现

清理策略

1)立即清理:在删除图片记录时,立即关联删除对象存储中已上传的图片文件,确保数据库记录与存储文件保持一致。

这里还有个小技巧,可以使用异步清理降低对删除操作性能的影响,并且记录一些日志,避免删除失败的情况。

2)手动清理:由管理员手动触发清理任务,可以筛选要清理的数据,按需选择需要清理的文件范围。

3)定期清理:通过定时任务自动触发清理操作。系统预先设置规则(如文件未访问时间超过一定期限)自动清理不需要的数据。

4)惰性清理:清理任务不会主动执行,而是等到资源需求增加(存储空间不足)或触发特定操作时才清理,适合存储空间紧张但清理任务优先级较低的场景

后端实现

CosManager 补充删除对象的方法-在 PictureService 中开发图片清理方法

要点:

1.判断该图片地址是否还存在于其他记录里,确认没有才能删除。

2.@Async 注解,可以使得方法被异步调用,启动类上添加 @EnableAsync 注解才会生效。

Redis 分布式 Session

把session保存到Redis里

依赖-配置

这里需要USER类实现序列化接口。

相关推荐
静小谢2 小时前
前端mock假数据工具JSON Server使用笔记
前端·笔记·json
2501_942326442 小时前
科学记忆法:从关联到睡眠的高效学习
学习
Coovally AI模型快速验证2 小时前
Meta ShapeR重磅开源:多模态3D生成,从真实杂乱视频中稳健重建
人工智能·学习·算法·yolo·3d·人机交互
QiZhang | UESTC2 小时前
学习日记day69
学习
进阶小白猿2 小时前
Java技术八股学习Day25
java·jvm·学习
白白白飘2 小时前
【书籍课程】强化学习的数学原理
笔记
LaoZhangGong1232 小时前
学习TCP/IP的第7步:设计TCPIP程序要注意的事项
网络协议·学习·tcp/ip·以太网
今儿敲了吗3 小时前
计算机网络第四章笔记(三)
笔记·计算机网络
好奇龙猫3 小时前
【日语学习-日语知识点小记-日本語体系構造-JLPT-N2前期阶段-第一阶段(8):単語文法】
学习