跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaSaaSjava算法

基于 Redis+Caffeine+ 腾讯云的图片库查询上传加载存储优化与分布式 Session 登录

通过 Redis 与 Caffeine 构建多级缓存提升查询性能,利用腾讯云对象存储实现 WebP 压缩与缩略图生成,结合 CDN 加速与浏览器缓存优化加载体验,并通过分布式 Session 管理登录态。

霸天发布于 2026/3/20更新于 2026/10/370 浏览
基于 Redis+Caffeine+ 腾讯云的图片库查询上传加载存储优化与分布式 Session 登录

图片展示

动图演示

图片优化技术

在云图库项目上线前,针对图片处理进行了深度优化。主要涵盖以下四个维度:

  • 图片查询优化:分布式缓存、本地缓存、多级缓存
  • 图片上传优化:压缩、秒传、分片上传、断点续传
  • 图片加载优化:懒加载、缩略图、CDN 加速、浏览器缓存
  • 图片存储优化:降频存储(冷热数据分离)、清理策略

一、图片查询优化

缓存机制

对于高频访问的数据,直接从数据库获取效率较低。利用性能更高的存储介质作为缓存,可以显著降低数据库压力并提升系统响应速度。

适合缓存的数据通常具备'读多写少'的特征,例如:

  1. 高频访问数据:如首页推荐内容。
  2. 计算成本较高数据:如复杂统计结果。
  3. 允许短暂延迟数据:如排行榜、图片列表。

本项目中,主页及图片列表接口属于高频访问场景,且对实时性要求不高,非常适合引入缓存。

Redis 分布式缓存

分布式缓存将数据存储于多台服务器,适用于高并发场景。Redis 因其高性能和丰富的数据结构成为主流选择。

  • 高性能:基于内存操作,单节点读写 QPS 可达 10 万。
  • 丰富结构:支持字符串、列表、哈希等。
  • 分布式支持:通过 Cluster 或哨兵模式实现高可用与扩展。
缓存设计

针对 listPictureVOByPage 接口进行缓存设计,遵循 key、value、过期时间三要素。

(1) 缓存 Key 设计

由于查询条件不同,需将其纳入 Key。为避免 JSON 过长,可使用 MD5 哈希压缩。同时添加项目前缀隔离业务。

yupicture:listPictureVOByPage:${查询条件 key}

(2) 缓存 Value 设计

存储分页对象 Page。为了可读性可转为 JSON 字符串,为了空间压缩可选用二进制,Redis 底层均为 String 结构。

(3) 过期时间设置

必须设置过期时间以防缓存堆积。考虑到图片持续更新,设置为 5~60 分钟较为合适。为了解决缓存雪崩问题,建议设置随机过期时间区间。

后端开发

Spring Boot 推荐使用 Spring Data Redis 集成,默认使用 Lettuce 客户端。

(1) 引入依赖

<!-- Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>

(2) 配置文件

在 application.yml 中添加 Redis 配置:

spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    timeout: 5000

(3) 单元测试验证

使用 StringRedisTemplate 测试基础增删改查操作。

@SpringBootTest
public class RedisStringTest {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    public void testRedisStringOperations() {
        ValueOperations<String, String> valueOps = stringRedisTemplate.opsForValue();
        String key = "testKey";
        String value = "testValue";

        // 新增或更新
        valueOps.set(key, value);
        assertEquals(value, valueOps.get(key));

        // 修改
        valueOps.set(key, "updatedValue");
        assertEquals("updatedValue", valueOps.get(key));

        // 删除
        stringRedisTemplate.delete(key);
        assertNull(valueOps.get(key));
    }
}

(4) 实现带缓存的查询接口

注入 StringRedisTemplate,在查库前先查缓存。若命中直接返回,未命中则查库并回填缓存。

@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());

    // 构建缓存 Key
    String queryCondition = JSONUtil.toJsonStr(pictureQueryRequest);
    String hashKey = DigestUtils.md5DigestAsHex(queryCondition.getBytes());
    String redisKey = String.format("yupicture:listPictureVOByPage:%s", hashKey);

    ValueOperations<String, String> opsedForValue = stringRedisTemplate.opsForValue();
    String cachedValue = opsedForValue.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 cachedExpireTime = 300 + RandomUtil.randomInt(0, 300);
    opsedForValue.set(redisKey, cacheValue, cachedExpireTime, TimeUnit.SECONDS);

    return ResultUtils.success(pictureVOPage);
}

测试表明,引入缓存后平均响应时间显著下降。注意序列化过程中 null 字段可能被过滤,导致前后端数据结构略有差异。

Caffeine 本地缓存

本地缓存将数据存储在 JVM 内存中,速度更快但无法跨节点共享。适用于小型数据集或单机高频访问场景。 Caffeine 是 Java 生态中主流的本地缓存库,支持容量控制、过期策略和异步操作。

后端开发

(1) 引入依赖

<!-- 本地缓存 Caffeine --><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>3.1.8</version></dependency>

(2) 初始化缓存

private final Cache<String, String> LOCAL_CACHE = Caffeine.newBuilder()
    .initialCapacity(1024)
    .maximumSize(10000L)
    .expireAfterWrite(5L, TimeUnit.MINUTES)
    .build();

(3) 替换逻辑

参考分布式缓存逻辑,将 Redis 操作替换为 LOCAL_CACHE.getIfPresent 和 put。

多级缓存

结合本地缓存的高性能和分布式缓存的一致性,构建两级缓存系统。

工作流程:

  1. 第一级(Caffeine):优先读取,命中直接返回。
  2. 第二级(Redis):未命中则查 Redis,命中则回写本地缓存。
  3. 数据库:均未命中则查库,结果同时写入 Redis 和本地缓存。

这种架构提升了容错性,即使 Redis 故障,本地缓存仍可提供部分服务。

代码实现
// 1. 查询本地缓存
String cachedValue = LOCAL_CACHE.getIfPresent(cacheKey);
if (cachedValue != null) {
    return ResultUtils.success(JSONUtil.toBean(cachedValue, Page.class));
}

// 2. 查询分布式缓存
cachedValue = valueOps.get(cacheKey);
if (cachedValue != null) {
    LOCAL_CACHE.put(cacheKey, cachedValue);
    return ResultUtils.success(JSONUtil.toBean(cachedValue, Page.class));
}

// 3. 查询数据库并回写
Page<Picture> picturePage = pictureService.page(...);
Page<PictureVO> pictureVOPage = pictureService.getPictureVOPage(picturePage, request);
String cacheValue = JSONUtil.toJsonStr(pictureVOPage);
LOCAL_CACHE.put(cacheKey, cacheValue);
valueOps.set(cacheKey, cacheValue, 5, TimeUnit.MINUTES);
return ResultUtils.success(pictureVOPage);

常见问题与扩展

  • 缓存击穿:热点数据过期时大量请求打库。解决方案:互斥锁或超长过期时间。
  • 缓存穿透:查询不存在的数据。解决方案:布隆过滤器或缓存空值。
  • 缓存雪崩:大量缓存同时过期。解决方案:随机过期时间或多级缓存。

二、图片上传优化

图片压缩

压缩能显著减少文件体积,降低带宽成本。推荐转换为 WebP 格式,相比 JPEG/PNG 体积更小且支持透明通道。

方案选择

利用腾讯云 COS 对象存储的数据万象服务,可在上传时自动处理图片规则,无需额外开发。

后端开发

(1) 修改 CosManager

在上传请求中增加图片处理规则,将后缀转为 .webp。

PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, key, file);
PicOperations picOperations = new PicOperations();
picOperations.setIsPicInfo(1);

List<PicOperations.Rule> rules = new ArrayList<>();
PicOperations.Rule compressRule = new PicOperations.Rule();
compressRule.setFileId(webpKey);
compressRule.setRule("imageMogr2/format/webp");
compressRule.setBucket(bucket);
rules.add(compressRule);

picOperations.setRules(rules);
putObjectRequest.setPicOperations(picOperations);

(2) 解析处理结果

从上传结果中提取压缩后的图片信息,包括宽高、大小、格式等。

ProcessResults processResults = putObjectResult.getCiUploadResult().getProcessResults();
List<CIObject> objectList = processResults.getObjectList();
if (CollUtil.isNotEmpty(objectList)) {
    CIObject compressCiObject = objectList.get(0);
    return buildResult(originalFilename, compressCiObject);
}

(3) 封装返回结果

根据压缩对象构建 UploadPictureResult,确保前端获取到的是 WebP 地址。

测试显示,压缩效果显著,且原图与压缩图同名便于管理。

文件秒传

基于文件指纹(MD5)校验,避免重复上传。虽然本项目因 COS 限制未完全实现,但原理通用:

  1. 客户端计算文件 MD5。
  2. 服务端查询是否存在该 MD5。
  3. 存在则直接返回路径,不存在则上传。

分片上传与断点续传

大文件传输建议使用 SDK 提供的分片功能,记录进度中断后可恢复,无需自行开发底层逻辑。

三、图片加载优化

缩略图

首页直接加载原图会导致加载慢、流量浪费。解决方案是在上传时生成缩略图,列表页仅加载缩略图。

实现步骤
  1. 数据库变更:picture 表新增 thumbnailUrl 字段。
  2. MyBatis 映射:XML 中增加字段映射。
  3. 实体类同步:Picture, PictureVO 等类增加对应属性。
  4. 处理逻辑:在上传规则中增加缩放参数 /thumbnail/128x128>。

注意:小图片生成缩略图可能反而变大,建议仅对大于 20KB 的图片生成缩略图。若无缩略图,则复用压缩图 URL。

CDN 加速

CDN(内容分发网络)将资源分发至全球节点,用户就近访问,大幅降低源站压力。

核心流程:

  • 源站(COS)上传资源至 CDN。
  • 用户请求命中 CDN 节点缓存,未命中则回源。

注意事项:

  • 缓存策略:静态资源设置长期缓存。
  • HTTPS:启用 SSL 证书保障安全。
  • 防盗链:配置 Referer 白名单。
  • 监控告警:设置流量阈值,防止费用超额。

浏览器缓存

通过 HTTP 头信息(如 Cache-Control)让浏览器缓存资源。配合 CDN 配置,可实现本地瞬间加载。

四、图片存储优化

数据沉降与清理

随着时间推移,数据热度降低。可通过生命周期规则将冷数据迁移至低频存储,降低成本。

清理策略:

  • 立即清理:删除记录时同步删除文件。
  • 定期清理:定时任务扫描过期数据。
  • 手动清理:管理员触发特定范围清理。
后端开发

利用 Spring 的 @Async 异步删除文件,避免阻塞主线程。

@Async
public void clearPictureFile(Picture oldPicture) {
    String pictureUrl = oldPicture.getUrl();
    Long count = this.lambdaQuery().eq(Picture::getUrl, pictureUrl).count();
    if (count <= 1) {
        cosManager.deleteObject(pictureUrl);
        if (StrUtil.isNotBlank(oldPicture.getThumbnailUrl())) {
            cosManager.deleteObject(oldPicture.getThumbnailUrl());
        }
    }
}

启动类需开启异步支持 @EnableAsync。

五、登录态自动保持

重启服务器后重新登录体验不佳,可利用 Redis 管理 Session 实现分布式登录态保持。

(1) 引入依赖

<!-- Spring Session + Redis --><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency>

(2) 配置 Session

spring:
  session:
    store-type: redis
    timeout: 2592000 # 30 天
server:
  servlet:
    session:
      cookie:
        max-age: 2592000

配置完成后,重启服务无需重新登录,Session 信息持久化在 Redis 中。

目录

  1. 图片优化技术
  2. 一、图片查询优化
  3. 缓存机制
  4. Redis 分布式缓存
  5. 缓存设计
  6. 后端开发
  7. Caffeine 本地缓存
  8. 后端开发
  9. 多级缓存
  10. 代码实现
  11. 常见问题与扩展
  12. 二、图片上传优化
  13. 图片压缩
  14. 方案选择
  15. 后端开发
  16. 文件秒传
  17. 分片上传与断点续传
  18. 三、图片加载优化
  19. 缩略图
  20. 实现步骤
  21. CDN 加速
  22. 浏览器缓存
  23. 四、图片存储优化
  24. 数据沉降与清理
  25. 后端开发
  26. 五、登录态自动保持

更多推荐文章

查看全部
  • Android 面试高频问答与核心知识点解析
  • AI Agent Skills 资源合集:支持 Cursor、Claude Code 与 Copilot
  • Arrow 游戏叙事设计工具:基于 Godot 4 的可视化剧情编辑平台
  • 转行 Python 开发需知的 11 个核心常识
  • C++ 进阶核心知识点整理
  • Python openpyxl 和 pandas 使用详解
  • 从零实现 LLaMA 架构:构建轻量级大语言模型
  • 基于 ComfyUI 工作流的 Stable Diffusion 服装替换指南
  • 模拟算法专题:替换问号、提莫攻击、Z 字形变换、外观数列与数青蛙
  • Ubuntu 20.04 快速安装 Miniconda 指南
  • 基于 LLaMA-Factory 微调 Qwen3.5-4B 构建医疗 AI 助手
  • Qwen3-VL-32B 多卡部署:vLLM 通信瓶颈与 llama.cpp 实践对比
  • LeetCode 滑动窗口算法进阶解析
  • FPGA SPI Flash 配置模式:硬件设计与约束文件协作
  • Python 爬虫基础:requests 库详解
  • 本地大模型运行工具 Ollama 安装与使用指南
  • Python PyQt 上位机设计与实战指南
  • 跨平台宏定义陷阱与优化:C/C++至HarmonyOS实战解析
  • 基于 RAG 技术的 AI 知识库构建与智能升级指南
  • CPU、MCU、SoC 与 FPGA 核心概念对比

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online