智能协同云图库升级:Redis、Caffeine联动腾讯云图片服务优化操作并以分布式Session保登录态

智能协同云图库的升级:Redis与Caffeine协同腾讯云图片服务优化及分布式Session维持登录态

此处插入图片描述

此处插入图片描述

此处插入图片描述


图片优化相关技术


图片优化技术概览

在云图库项目上线前,仍有较大的优化空间。此节将分享近10种主流图片优化技术,涵盖:

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

一、图片查询优化


缓存机制

对于频繁访问的数据,若每次都从数据库(硬盘)获取会较慢,可借助性能更优的存储提升系统响应速度,即缓存。合理运用缓存能显著减轻数据库压力、提升系统性能。

那么,哪些数据适合缓存呢?通常是“读多写少”的情况,具体而言:

  1. 高频访问的数据:像系统首页、热门推荐内容等。
  2. 计算成本较高的数据:例如复杂查询结果、大量数据的统计结果。
  3. 允许短时间延迟的数据:比如无需实时更新的排行榜、图片列表等。

在我们的项目中,主页是用户高频访问的内容,获取图片列表的接口也是高频访问的。且即便数据更新存在一定延迟,对用户体验影响不大,因而十分适合缓存。


Redis分布式缓存

分布式缓存是将缓存数据分布存储于多台服务器上,以在高并发场景下提供更高吞吐量与更好容错性。Redis是实现分布式缓存的主流方案,也是后端开发必学技能。其具备以下优势:

  • 高性能:基于内存操作,访问速度极快。单节点Redis的读写QPS可达10w次每秒!
  • 丰富的数据结构:支持字符串、列表、集合、哈希、位图等,适用于各类数据结构存储。
  • 分布式支持:可通过Redis Cluster构建高可用、高性能的分布式缓存,还提供哨兵集群机制提升可用性、分片集群机制提高可扩展性。

缓存设计

需缓存首页的图片列表数据,即对listPictureVOByPage接口进行缓存。首先依据缓存三要素“key、value、过期时间”设计。

(1) 缓存key设计

由于接口支持传入不同查询条件,对应数据不同,所以需将查询条件作为缓存key的一部分。可将查询条件对象转为JSON字符串,不过该JSON可能较长,可利用哈希算法(如MD5)压缩key。此外,因使用分布式缓存,可能由多个项目和业务共享,故需在key开头拼接前缀隔离。设计出的key如下:

yupicture:listPictureVOByPage:${查询条件key}
(2) 缓存value设计

缓存从数据库查到的Page分页对象,存储为何种格式?有两种选择:一是为可读性,转为JSON结构字符串;二是为压缩空间,存为二进制等其他结构。但对应的Redis数据结构均为string

(3) 缓存过期时间设置

必须设置缓存过期时间!依据实际业务场景、缓存空间大小及数据一致性要求设置,合适即可。此处因查询条件多,且考虑图片持续更新,设为5~60分钟。


操作Redis的方式

Java有众多Redis操作库,如Jedis、Lettuce等。为便于与Spring项目集成,Spring提供了Spring Data Redis作为操作Redis的高层抽象(默认用Lettuce作底层客户端)。因项目用Spring Boot,推荐用Spring Data Redis,开发成本低。其使用简便,直接上手项目实战。

(1) 引入Maven依赖,整合Redis
<!-- Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

下载Redis:

Redis-x64-5.0.14.msi_免费高速下载

提取码:vmty,一路next即可;

连接Redis:下载Redis客户端后,可直接用IDEA连接Redis,若IDEA社区版或版本较老无Redis选项,可用Redis可视化工具代替操作Redis:

image-20250715114420071

(2) 在application.yml中添加Redis配置
spring:
  # Redis配置
  redis:
    database: 0       # 指定使用的redis库, redis共有16个库
    host: 127.0.0.1
    port: 6379
    timeout: 5000     # 超时时间, 在超时时间内连接失败, 回报错
(3) 编写JUnit单元测试文件,测试基础操作
@SpringBootTest
public class RedisStringTest {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    public void testRedisStringOperations() {
        // 获取操作对象
        ValueOperations<String, String> valueOps = stringRedisTemplate.opsForValue();

        // Key和Value
        String key = "testKey";
        String value = "testValue";

        // 1. 测试新增或更新操作
        valueOps.set(key, value);
        String storedValue = valueOps.get(key);
        assertEquals(value, storedValue, "存储的值与预期不一致");

        // 2. 测试修改操作
        String updatedValue = "updatedValue";
        valueOps.set(key, updatedValue);
        storedValue = valueOps.get(key);
        assertEquals(updatedValue, storedValue, "更新后的值与预期不一致");

        // 3. 测试查询操作
        storedValue = valueOps.get(key);
        assertNotNull(storedValue, "查询的值为空");
        assertEquals(updatedValue, storedValue, "查询的值与预期不一致");

        // 4. 测试删除操作
        stringRedisTemplate.delete(key);
        storedValue = valueOps.get(key);
        assertNull(storedValue, "删除后的值不为空");
    }
}

运行单元测试方法:

image-20250715140542217

通过JUnit单元测试文件,测试了使用StringRedisTemplate对Redis的基础增删改查操作。

(4)编写带缓存的分页查询图片列表接口

注入Redis操作对象:通过@Resource注解,将StringRedisTemplate对象注入Spring容器,使其在当前类可用。

/**
 * 分页获取图片列表(封装类, 这个接口的区别在于带上缓存 listPictureVOByPageWithCache, 但是前端依旧是使用没有缓存的接口 listPictureVOByPage)
 */
@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, value, 过期时间

    // 将查询请求对象转为JSON格式
    String queryCondition = JSONUtil.toJsonStr(pictureQueryRequest);

    // 将查询条件JSON转为字节数组, 再将字节数组转为md5, 得到的结果作为缓存的哈希key
    String hashKey = DigestUtils.md5DigestAsHex(queryCondition.getBytes());

    // 根据方案设计, redisKey = 项目名 + 接口名(去掉WithCache) + 哈希key拼接
    String redisKey = String.format("yupicture:listPictureVOByPage:%s", hashKey);

    // 操作redis, 从缓存中查询拿到value
    ValueOperations<String, String> opsedForValue = stringRedisTemplate.opsForValue();

    // 如果有缓存, 直接作为接口响应返回前端
    String cachedValue = opsedForValue.get(redisKey);
    if(cachedValue != null){
        // 如果缓存命中, 将缓存结果反序列化为JSON对象
        Page<PictureVO> cachedPage = JSONUtil.toBean(cachedValue, Page.class);
        // Page.class Page可以不带泛型, 泛型只是为了方便我们编码时使用的
        return ResultUtils.success(cachedPage);
    }

    // 查询缓存的结果为空, 接下来进行查询数据库操作
    Page<Picture> picturePage = pictureService.page(new Page<>(current, size),
            pictureService.getQueryWrapper(pictureQueryRequest));

    // 对数据库查询结果进行封装
    Page<PictureVO> pictureVOPage = pictureService.getPictureVOPage(picturePage, request);

    // 将查询数据库得到的结果, 存入redis缓存中, 下次就可以直接通过查询缓存得到结果
    // 将数据库封装结果进行JSON序列化, 作为存入缓存的value
    String cacheValue = JSONUtil.toJsonStr(pictureVOPage);

    // 设置缓存的过期时间, 5~10 minute 过期
    int  cachedExpireTime = 300 + RandomUtil.randomInt(0, 300);
    // 设置缓存过期时间为一个区间, 是为了解决缓存雪崩的问题

    // 将key , value, 过期时间, 时间单位(s) 存入缓存
    opsedForValue.set(redisKey, cacheValue, cachedExpireTime, TimeUnit.SECONDS);

    // 对查询结果进行脱敏后, 统一封装返回
    return ResultUtils.success(pictureVOPage);
}

缓存过期时间区间设置目的

在缓存系统中,缓存雪崩是常见问题。缓存雪崩指大量缓存的key在同一时间集中失效,导致接下来所有请求直接打到数据库。由于数据库需处理大量原本由缓存承担的请求,可能因压力过大崩溃。为避免此问题,需避免缓存同一时间集中过期,将缓存过期时间设为时间区间,增加过期时间随机性,使不同缓存过期时间分散,避免大量缓存同时失效。


Caffeine本地缓存

当应用需频繁访问某些数据时,可将数据缓存到应用内存中(如JVM中),下次访问直接从内存读取,无需经网络或其他存储系统。相比分布式缓存,本地缓存速度更快,但无法在多服务器间共享数据、不便扩容。其应用场景一般为:

  • 数据访问量有限的小型数据集
  • 不需要服务器间共享数据的单机应用
  • 高频、低延迟的访问场景(如用户临时会话信息、短期热点数据)

对于Java项目,Caffeine是主流本地缓存技术,性能高、功能丰富。可精确控制缓存数量和大小、支持缓存过期、多种淘汰策略、异步操作、线程安全等。

💡 建议,若仅提升数据访问性能,优先考虑本地缓存而非分布式缓存,因其无需引入额外中间件,成本更低。


缓存设计

本地缓存设计与分布式缓存基本一致,有两点区别:一是本地缓存需自己创建初始化缓存结构(可简单理解为自己new一个HashMap);二是因本地缓存本身服务器隔离且占用服务器内存,key可更精简,无需添加项目前缀。


后端开发
(1)引入Caffeine的Maven依赖

注意:若引入3.x版本Caffeine,Java版本需>=11!若不想升级JDK,可引入2.x版本。

<!-- 本地缓存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) // 缓存5分钟后移除
            .build();
(3)参考分布式缓存代码,修改为本地缓存

在查询数据库前先查询本地缓存,若有数据则直接返回:

// 构建缓存key
String queryCondition = JSONUtil.toJsonStr(pictureQueryRequest);
String hashKey = DigestUtils.md5DigestAsHex(queryCondition.getBytes());
String cacheKey = "listPictureVOByPage:" + hashKey;

// 从本地缓存中查询
String cachedValue = LOCAL_CACHE.getIfPresent(cacheKey);
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);
LOCAL_CACHE.put(cacheKey, cacheValue);

性能测试与多级缓存优化
性能测试

通过Swagger测试返回结果是否正常,对比查数据库、查Redis的性能提升。

  • 有缓存:最快可达12ms,性能进一步提升约1倍,相比数据库提升数倍;
  • 当前环境:数据库和Redis均在本地,访问较快。若用远程数据库或Redis,性能提升更明显。
扩展思考

若想灵活切换本地缓存或分布式缓存,可采用策略模式或模板方法模式(利用变量灵活切换)。


多级缓存

多级缓存结合本地缓存和分布式缓存优点,构建两级缓存系统,兼顾本地缓存高性能、分布式缓存数据一致性和可靠性。

多级缓存工作流程
  1. 第一级(Caffeine本地缓存):优先从本地缓存读取数据,命中则直接返回。
  2. 第二级(Redis分布式缓存):本地缓存未命中则查询Redis分布式缓存,命中则返回数据并更新本地缓存。
  3. 数据库查询:Redis未命中则查询数据库,并将结果写入Redis和本地缓存。

流程图:

image-20250715161834075

多级缓存提升系统容错性,即使Redis故障,本地缓存仍可提供服务,减少对数据库直接依赖。


后端开发
(1) 优先从本地缓存读取数据,命中则返回
// 构建缓存key
String queryCondition = JSONUtil.toJsonStr(pictureQueryRequest);
String hashKey = DigestUtils.md5DigestAsHex(queryCondition.getBytes());
String cacheKey = "yupicture:listPictureVOByPage:" + hashKey;

// 1. 查询本地缓存(Caffeine)
String cachedValue = LOCAL_CACHE.getIfPresent(cacheKey);
if (cachedValue != null) {
    Page<PictureVO> cachedPage = JSONUtil.toBean(cachedValue, Page.class);
    return ResultUtils.success(cachedPage);
}
(2) 本地缓存未命中则查询Redis,命中则返回并更新本地缓存
// 2. 查询分布式缓存(Redis)
ValueOperations<String, String> valueOps = stringRedisTemplate.opsForValue();
cachedValue = valueOps.get(cacheKey);
if (cachedValue != null) {
    // 如果命中Redis,存入本地缓存并返回
    LOCAL_CACHE.put(cacheKey, cachedValue);
    Page<PictureVO> cachedPage = JSONUtil.toBean(cachedValue, Page.class);
    return ResultUtils.success(cachedPage);
}
(3) Redis未命中则查询数据库,并更新缓存

```java
// 3. 查询数据库
Page picturePage = pictureService.page(new Page<>(current, size),
pictureService.getQueryWrapper(pictureQueryRequest));
Page<Picture

文章整理自互联网,只做测试使用。发布者:Lomu,转转请注明出处:https://www.it1024doc.com/13109.html

(0)
LomuLomu
上一篇 2025 年 8 月 12 日
下一篇 2025 年 8 月 12 日

相关推荐

  • 算法奇趣阁——递归中二叉树的精妙裁剪

    文章标题: 算法妙趣屋——递归下二叉树的精巧剪裁 1.前言 二叉树里的深度搜索(展开阐述) 深度优先遍历(DFS,即DepthFirstTraversal),是我们在树或者图这类数据结构中常用的一种遍历手段。该算法会尽可能深入地搜索树或者图的分支,直至一条路径上的所有节点都被遍历完毕,之后回溯到上一层,再去探寻另一条路径进行遍历。在二叉树中,常见的深度优先遍…

    2025 年 7 月 25 日
    4200
  • manim边做边学–动画联动

    今天介绍Manim中的动画联动的技巧,在数学动画中,动画联动是常用的功能, 比如讲解平面几何中三角形与圆的位置关系变化,通过动画联动可以让圆沿着三角形的边滚动,或者让三角形的顶点在圆上移动,从而直观地展示内切、外接等几何关系。 总之,通过动画联动,可以将复杂的概念、关系或变化过程以动态的方式展示出来。 这种动态展示比静态的图像或文字描述更具吸引力,能让观众更…

    2025 年 1 月 16 日
    38100
  • 2025年最新PyCharm激活码及永久破解教程(支持2099年)

    适用于Jetbrains全家桶的完美破解方案 先来看一张成功破解后的效果图,可以看到我的PyCharm已经顺利激活到2099年了! 下面我将用详细的图文教程,手把手教你如何永久激活PyCharm。这个方法不仅适用于最新版本,对旧版本也同样有效! Windows/Mac/Linux全平台支持 所有版本通用 成功率高达100% 第一步:获取PyCharm安装包 …

    PyCharm激活码 2025 年 7 月 8 日
    9900
  • 🚀 2025最新PyCharm永久激活码分享|100%破解成功教程(支持2099年)

    本教程适用于JetBrains全家桶,包括IDEA、PyCharm、DataGrip、Goland等所有产品!💯 先给大家看看最新PyCharm版本破解成功的实锤截图👇 有效期直接拉到2099年,简直不要太爽! 下面就用最详细的图文步骤,手把手教你激活PyCharm到2099年。这个方法对旧版本同样有效哦✨ 不限系统:Windows/Mac/Linux通吃 …

    PyCharm激活码 2025 年 6 月 19 日
    69900
  • 高性能MySQL(第4版)PDF、EPUB免费下载

    适读人群 :不但适合数据库管理员(DBA)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获 领域经典十年后全版更新||全面拥抱8.0||重磅剖析现代云数据库与大规模运维实践||中国首批DBA精琢翻译5大头部国产数据库创始人联合力荐 电子版仅供预览,下载后24小时内务必删除,支持正版,喜欢的请购买正版书籍 点击原文去下载 书籍信息…

    2025 年 1 月 6 日
    33200

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信