【项目实战】从 0 到 1 构建智能协同云图库(六):多级缓存与图片查询优化深度总结
目录
一、 图片查询优化
1. 分布式缓存 (Redis)
2. 本地缓存 (Caffeine)
3. 多级缓存架构
4. 缓存常见问题防范与进阶扩展
二、 图片上传优化
1. 图片压缩格式选择
2. 图片压缩方案与后端实现
3. 扩展知识:文件秒传
4. 扩展知识:分片上传与断点续传
三、 图片加载优化
1. 缩略图 (Thumbnail)
2. 懒加载 (Lazy Loading)
3. 渐进式加载 (Progressive Loading) - 扩展知识
4. CDN 加速与浏览器缓存
四、 图片存储优化
1. 数据沉降(冷热数据分离)
2. 多维度图片清理策略
3. 异步立即清理机制(后端实现)
4. 扩展知识:定期清理与批量操作
一、 图片查询优化
图片查询优化主要是围绕“缓存技术”来展开的,对于经常访问的数据(如首页、热门推荐等“读多写少”的场景),利用缓存可以显著降低数据库压力并提高系统性能。
1. 分布式缓存 (Redis)
分布式缓存是指将缓存数据分布存储在多台服务器上,以便在高并发场景下提供更高的吞吐量和容错性。Redis 是主流方案。
核心优势:基于内存操作,读写极快(单节点 QPS 可达 10万次每秒);支持丰富的数据结构;支持通过 Redis Cluster 构建高可用、高性能的分布式缓存。
缓存设计三要素:
缓存 key:为了区分不同查询条件,将查询条件对象转为 JSON,再通过MD5 算法进行压缩。同时拼接项目和业务前缀(如
yupicture:listPictureVOByPage:{MD5串})以进行隔离。缓存 value:将数据库查出的 Page 分页对象转换为 JSON 结构字符串进行存储。
过期时间:通常设置为 5 ~ 60 分钟。为了防止缓存同时失效导致“雪崩”,在代码中还会额外增加一个随机的过期时间(如 0~300 秒内随机)。
开发实战:后端通过引入
spring-boot-starter-data-redis依赖,利用StringRedisTemplate进行基础的增删改查操作。获取操作对象:调用
stringRedisTemplate.opsForValue()获取ValueOperations实例。写入/修改数据 (Set):使用
valueOps.set(key, value)。读取数据 (Get):使用
valueOps.get(key)。删除数据 (Delete):直接调用
stringRedisTemplate.delete(key)。//获取操作对象 Valueoperations<String, String> valueOps = stringRedisTemplate.opsForValue
2. 本地缓存 (Caffeine)
相比分布式缓存,本地缓存直接将数据缓存在应用的内存中(如 JVM 中),免去了网络传输,速度更快。
适用场景:数据访问量有限的小型数据集、不需要服务器间共享数据的单机应用,以及高频低延迟的访问场景。
缓存设计与实现:
无需像 Redis 那样添加复杂的前缀隔离,因为数据本身就是服务器隔离的,key 可以更精简。
需要自己创建初始化缓存结构,通过
Caffeine.newBuilder()构建,可精确控制初始容量(initialCapacity)、最大缓存条数(maximumSize)以及过期时间(expireAfterWrite)。性能提升明显:引入缓存后,接口响应时间最快可达 12ms。
3. 多级缓存架构
结合本地缓存和分布式缓存的优点,在同一业务场景下构建两级缓存系统,兼顾本地缓存的高性能以及分布式缓存的数据一致性。
工作流程:
接收请求 -> 查询本地缓存 (Caffeine):如果命中,则直接返回本地缓存数据。
查询分布式缓存 (Redis):如果本地未命中,则查 Redis。如果 Redis 命中,更新本地缓存,并返回 Redis 缓存数据。
查询数据库:如果 Redis 也未命中,最后查询数据库。将结果依次更新到本地缓存和 Redis 缓存中,最后返回数据库数据。
4. 缓存常见问题防范与进阶扩展
使用缓存时,一般要注意防范以下几个问题,并进行相应的扩展:
缓存击穿(某些热点数据在缓存过期后,大量请求直接打到数据库):解决方案是设置热点数据的超长过期时间,或使用互斥锁(如 Redisson)控制缓存刷新。
缓存穿透(用户频繁请求不存在的数据,导致大量请求直接接触数据库查询):解决方案是对无效查询结果也进行缓存(如设置空值缓存),或者使用布隆过滤器。
缓存雪崩(大量缓存同时过期,导致请求打到数据库系统崩溃):解决方案是设置不同的缓存过期时间(避免同时过期);或者使用多级缓存,减少对数据库的依赖。
其他扩展优化:
手动刷新缓存:提供一个刷新缓存的接口,仅管理员可调用。
自动识别热点图片缓存:采用热 key 探测技术,实时统计图片的访问量,并自动将热点图片添加到内存缓存中。
查询与代码优化:获取图片列表时只查询必要的字段;如果缓存逻辑复杂,可单抽一个
CacheManager统一管理缓存操作。
没问题,按照您要求的排版格式,为您总结这部分关于图片上传优化的核心内容:
二、 图片上传优化
对于图库类网站,图片压缩和上传优化是最基础且最重要的操作,能够显著减少图片文件大小,从而降低带宽和流量消耗,在大幅降低存储成本的同时,提高图片的加载速度。主要是通过腾讯云提供的SDK接口。
1. 图片压缩格式选择
在不影响图片质量的前提下,将图片转换为体积更小的现代格式是首选方案。目前主流的现代图片格式有两种:
WebP:由 Google 开发,支持有损和无损压缩。体积比 PNG 小约 26%,比 JPEG 小约 25%-34%,且支持透明背景(Alpha 通道)。兼容性好,绝大部分主流浏览器均已支持,是当前最推荐的格式。
AVIF:基于 AV1 视频编码技术,压缩率更高,画质更优,且支持 HDR。但目前浏览器兼容性不如 WebP。
2. 图片压缩方案与后端实现
为了减少开发成本,项目中没有使用本地图像处理库,而是直接利用腾讯云 COS 的“数据万象”服务,在上传图片的同时自动进行压缩处理。
处理规则构建:在 Java 后端上传代码中,通过构造
PicOperations对象并添加处理规则(如imageMogr2/format/webp),指示云服务在接收文件时将其转换为 WebP 格式。获取压缩结果:修改上传模板代码,文件上传完成后,从腾讯云返回的
ProcessResults中提取压缩后图片的信息(包括压缩后的宽、高、大小、格式及新的图片 URL),并将其封装返回存入数据库。
3. 扩展知识:文件秒传
文件秒传是一种避免重复上传相同文件、节约带宽和存储资源的优化技术,常用于网盘类大文件上传场景。
工作流程:
客户端在上传前,先计算文件的唯一指纹(如 MD5 或 SHA-256 哈希值)。
服务端接收到哈希值后,在数据库中查询是否已存在相同指纹的文件。
若存在,则直接返回已有文件的存储路径,并建立数据关联(实现“秒传”);若不存在,才执行真实的上传逻辑。
项目局限性:本项目以小图片为主,重复率低,且受限于云存储路径隔离的要求(避免不同用户共享同一物理地址引发权限问题),因此秒传作为扩展知识了解即可。
4. 扩展知识:分片上传与断点续传
当需要上传大文件时,常规的单次上传容易因网络波动导致失败。
分片上传:将大文件切割成多个小数据块并发上传。
断点续传:记录已上传的分片,失败重试时只需上传未完成的部分。
实现方案:无需从零手写复杂逻辑,可以直接使用各大云厂商对象存储提供的高级 SDK 接口(如腾讯云的
TransferManager),只需在代码中配置好分块上传的触发阈值(例如文件大于 5MB 时开启)和分块大小(例如 1MB 一块),SDK 会自动在底层完成分片和续传工作。
三、 图片加载优化
这部分优化的核心目的是提升页面的加载速度、减少带宽消耗并改善用户体验。在技术实现上,主要结合了“云服务端的图像处理能力(腾讯云 COS/数据万象 SDK)”以及“前端浏览器的渲染机制与 API”来共同完成。
1. 缩略图 (Thumbnail)
系统首页如果直接加载原图,不仅加载时间极长,还会造成巨大的流量浪费。缩略图技术就是在图片列表页展示尺寸和体积极小的图片,仅在用户进入详情或下载时才加载原图。
实现方案(云端 SDK 处理):与之前讲的“图片压缩转 WebP”方法完全一致。我们无需在本地手写裁剪代码,而是继续利用腾讯云 COS 的“数据万象”服务。在后端 Java 代码上传文件时,向
PicOperations规则列表中再追加一条缩略图处理规则(如imageMogr2/thumbnail/%sx%s>)。云端就会在保存原图的同时,自动生成一张等比缩放的缩略图,并将缩略图 URL 返回给后端存入数据库(新增thumbnailUrl字段)。避坑与逻辑优化:如果上传的图片原本就非常小(比如只有十几 KB),强行生成缩略图反而可能导致生成后的文件比原图还大。因此,在后端代码中增加了一层拦截逻辑:仅当原图大小超过 20KB 时,才向云端下发生成缩略图的规则。优化后效果显著,例如 350KB 的原图,缩略图可降至约 3.6KB(缩小近百倍)。
2. 懒加载 (Lazy Loading)
懒加载是指避免在打开页面时一次性请求所有图片,而是只有当资源(图片)随着用户向下滚动页面,进入浏览器可视区域时,才去发起真实的网络请求。这完全依赖前端技术来实现。
实现方案:
HTML 原生属性:直接在
<img>标签上添加loading="lazy"属性。这是最简单的做法,但对部分老旧浏览器(如 IE)不兼容,且不支持复杂的触发机制。JS Intersection Observer API(推荐):这是现代浏览器提供的高效 API。思路是:初始时将真实的图片地址存在自定义属性(如
data-src)中,src放一个极小的透明图或留空。当 Observer 监测到该图片元素进入视口范围时,再通过 JS 将data-src的值赋给真实的src属性,从而触发加载。其他方式:使用传统的 JS 监听
scroll滚动事件计算位置,或者直接引入成熟的第三方前端库(如lazysizes)来代劳。
3. 渐进式加载 (Progressive Loading) - 扩展知识
渐进式加载主要用于超清大图加载,或者用户网络环境较差时的体验兜底。
工作原理:在真实的超大图片完全加载出来之前,先加载一张极低分辨率的模糊占位图。等背后真实的高清原图下载完毕后,再瞬间替换掉占位图,给用户一种“图片慢慢变清晰”的视觉过渡。
实现方案(前端 UI 组件):在现代前端开发中,通常不需要手写这套复杂的替换逻辑,可以直接利用现成的 UI 组件库。例如使用Ant Design Vue 的
Image图片组件,该组件原生支持placeholder属性,放入低清图地址即可轻松实现渐进式加载。
4. CDN 加速与浏览器缓存
虽然截图中未详细展示代码,但大纲中也提到了这两种必不可少的网络层优化手段:
CDN 加速:依托云厂商,将图片分发到离用户物理距离最近的边缘节点,大幅降低网络传输延迟。
浏览器缓存:通过配置 HTTP 响应头,让用户的浏览器在本地“记住”已经看过的图片,下次打开页面直接从本地磁盘秒读图片,彻底省去网络请求。
四、 图片存储优化
这部分优化的主要目的是严格控制云存储的成本,释放无用数据占用的空间。在技术实现上,这部分主要结合了“腾讯云 COS 控制台的原生规则配置(零代码实现)”以及“Spring Boot 的异步任务(@Async)与定时任务”来共同完成。
1. 数据沉降(冷热数据分离)
随着时间推移,大部分历史图片的访问热度会逐渐降低。为了节约成本,可以将久无人问津的“冷数据”从昂贵的标准存储转移到便宜的低频存储或归档存储中。
实现方案(云端可视化配置):这部分不需要编写复杂的业务代码,可以直接登录腾讯云 COS 控制台,利用其提供的“生命周期”功能添加规则。例如:设置“文件修改时间 30 天后沉降至低频存储”。云服务会自动在后台执行转移,大幅降低长期存储的成本。
2. 多维度图片清理策略
对于用户已删除的图片或临时文件,如果不去清理,云存储的费用会不断累加。项目中结合了以下几种常见的清理策略:
立即清理:在数据库删除图片记录时,立刻关联删除云存储中的图片文件(保证数据一致性)。
手动清理:提供后台接口,由管理员手动触发清理任务,筛选并清理冗余文件。
定期清理:通过定时任务(如 Spring Scheduler),每天/每周自动扫描并清理“未访问过”或“已废弃”的数据(兜底策略)。
惰性清理:平时不清理,只有当存储空间告急时才触发清理(类似 Redis 的内存淘汰机制,本项目为节省成本,未使用该策略)。
3. 异步立即清理机制(后端实现)
为了保证用户在前端点击“删除图片”时的响应速度,不能让网络请求(调用云服务删除文件)阻塞主线程。
实现方案(Spring 异步注解):
在 Spring Boot 启动类上添加
@EnableAsync开启异步功能。在图片清理方法(
clearPictureFile)上添加@Async注解,使其在独立线程中异步执行。调用腾讯云 SDK 的
cosClient.deleteObject方法,分别删除云端的原图和缩略图。
避坑逻辑:在执行真实删除前,代码中加入了一次数据库校验。统计该图片的 URL 是否还被其他记录引用(应对前面提到的“文件秒传”场景,多条记录共用一个物理文件)。只有当引用次数
<= 1时,才真正调用云服务删除文件,防止误删。
4. 扩展知识:定期清理与批量操作
定时任务兜底:如果在“立即清理”时刚好遇到网络抖动导致删除失败,会产生云端的“孤儿文件”。可以通过 Spring Scheduler 编写定时任务作为兜底,定期比对数据库和云存储的差异进行清理。
批量删除 API:如果一次性要清理大量文件,不要在
for循环中挨个调用单文件删除接口,应该直接使用云存储 SDK 提供的批量删除接口,这样可以极大减少网络 I/O 开销,提高清理效率。
补充说明(第五部分:其他优化)
Redis 分布式 Session。
解决痛点:项目后端每次重启后,存在本地 JVM 的用户登录状态(Session)就会丢失,导致开发者/用户频繁需要重新登录。
实现方案:引入
spring-session-data-redis依赖,并在application.yml中将 session 的store-type配置为redis,并设置过期时间(如 30 天)。效果:一行代码不用写,Spring Boot 就会自动将用户的 Session 状态接管并持久化到本地的 Redis 中。服务器无论怎么重启,只要 Redis 数据还在,用户就始终保持登录状态。
