智能协同云图库升级: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 日

相关推荐

  • 🔥2025最新PyCharm永久激活码分享 | 亲测可用至2099年(附详细破解教程)🔥

    🚀本教程适用于IDEA、PyCharm、DataGrip、Goland等Jetbrains全家桶软件,小白也能轻松搞定! 先给大家看看我的PyCharm最新版破解成果✨,有效期直接拉到2099年,简直不要太爽! 下面我就手把手教大家如何激活PyCharm,这个方法适用于所有版本哦~ 💻Windows/Mac/Linux全平台通用 🔄新旧版本统统支持 💯成功率…

    PyCharm激活码 2025 年 6 月 20 日
    29800
  • 【2025破解攻略】IntelliJ IDEA 2025.1最新激活码完美破解方法

    IntelliJ IDEA是JetBrains公司开发的一款功能强大的集成开发环境,特别适合Java开发。它提供了智能代码补全、强大的代码分析和重构工具、便捷的项目管理功能以及与各种流行框架的无缝集成。然而,专业版的价格对于学生和个人开发者来说可能是一笔不小的支出。 本文为大家带来经过实测的IDEA 2025.1版本永久激活教程,现已成功破解最新版本!可以尽…

    IDEA破解教程 2025 年 4 月 27 日
    75300
  • 2024 GoLand最新激活码,GoLand永久免费激活码2025-02-05 更新

    GoLand 2024最新激活码 以下是最新的GoLand激活码,更新时间:2025-02-05 🔑 激活码使用说明 1️⃣ 复制下方激活码 2️⃣ 打开 GoLand 软件 3️⃣ 在菜单栏中选择 Help -> Register 4️⃣ 选择 Activation Code 5️⃣ 粘贴激活码,点击 Activate ⚠️ 必看!必看! 🔥 获取最新激活…

    2025 年 2 月 5 日
    54800
  • 2025年最新DataGrip激活码与永久破解教程(支持2099年)

    完美兼容JetBrains全系列开发工具 本教程适用于JetBrains旗下所有开发工具,包括DataGrip、PyCharm、IDEA、Goland等专业软件。首先展示DataGrip最新版本成功激活至2099年的效果图: 下面将详细介绍如何实现DataGrip永久激活,该方法同样适用于旧版本! 全平台支持:Windows/Mac/Linux均可使用 版本…

    DataGrip激活码 2025 年 7 月 18 日
    15600
  • 🚀 2025最新PyCharm永久激活教程|破解补丁+激活码一键搞定(支持2099年)

    还在为PyCharm的激活问题发愁?🤔 本教程将手把手教你如何轻松破解PyCharm至2099年!无论你是Windows、Mac还是Linux用户,无论使用哪个版本,这个方法都100%有效!💯 🌟 效果预览 先来看看成功激活后的效果吧!如图所示,PyCharm已成功授权到2099年,简直不要太爽! 📥 下载PyCharm安装包 如果已经安装可跳过此步 访问P…

    2025 年 5 月 30 日
    64300

发表回复

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

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

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

关注微信