当前位置: 首页 > news >正文

首页实现多级缓存

缓存设计

缓存对象:需要缓存首先的图片列表数据,也就是对 listPictureVOByPage 接口进行缓存。

缓存三要素:“key、value、过期时间”。

1)缓存 key 设计

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

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

2)缓存 value 设计

缓存从数据库中查到的 Page 分页对象,存储为什么格式呢?这里有2中选择:

  • 为了可读性,可以转换为 JSON 结构的字符串 。例:{"id":"1","name":"pgs"}
  • 为了压缩空间,可以存为二进制等其他结构

但是对应的 Redis 数据结构都是 string.

3)缓存过期的时间设置

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

如何操作 Redis?

Java 中有很多的 Redis 操作库,比如 Jedis、Lettuce 等。为了便于和 Spring 项目集成,Spring还提供了 Spring Data Redis 作为操作 Redis 的更高层抽象(默认使用 Lettuce 作为底层客户端)。由于我们的项目使用 Spring Boot,也推荐使用 Spring Data Redis,开发成本更低。

Caffeine 本地缓存

当应用需要频繁访问某些数据时,可以将这些缓存存到应用内存中(比如 JVM中);下次访问时,直接从内存读取,而不需要经过网络或其他存储系统。

相比于分布式缓存,本地缓存的速度更快,但是无法在多个服务器间共享数据、而且不方便扩容。

所以本地缓存的应用场景一般是:

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

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

多级缓存

多级缓存是指结合本地缓存和分布式缓存的优点,在同一业务场景下构建两级缓存系统,这样可以兼顾本地缓存的高性能、以及分布式缓存的数据一致性和可靠性。

多级缓存的工作流程:

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

多级缓存还有‎一个优势,就是提升了系统的容错性。即使 Re⁢dis 出现故障,本地‍缓存仍可提供服务,减少⁠对数据库的直接依赖。

后端开发

1)引入 Maven 依赖,使用 Spring Boot Stater 快速整合 Redis,引入Caffeine:

 <!-- Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
<!-- 本地缓存 Caffeine -->
<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>3.1.8</version>
</dependency>

2)在 application.yml 中添加 Redis 配置:

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

3)新写一个使用缓存的分页查询图片列表的接口。在查询数据库前先查询缓存,如果已有数据则直接返回缓存,如果没有数据则查询数据库,并且将结果设置到缓存中。

构造本地缓存,设置缓存容量和过期时间:

private final Cache<String, String> LOCAL_CACHE =Caffeine.newBuilder().initialCapacity(1024).maximumSize(10000L)// 缓存 5 分钟移除.expireAfterWrite(5L, TimeUnit.MINUTES).build();
 @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());//构建缓存 keyString queryCondition = JSONUtil.toJsonStr(pictureQueryRequest);String hashKey = DigestUtils.md5DigestAsHex(queryCondition.getBytes());String cacheKey = String.format("pgspicture:listPictureVOByPage:%s", hashKey);//从 Redis 缓存中查询//1.先从本地缓存中查询String cacheValue = LOCAL_CACHE.getIfPresent(cacheKey);if(cacheValue != null){//如果缓存命中,返回结果Page<PictureVO> cachePage = JSONUtil.toBean(cacheValue, Page.class);return ResultUtils.success(cachePage);}//2.本地缓存未命中,查询Redis 分布式缓存ValueOperations<String,String> valueOps = stringRedisTemplate.opsForValue();String cachedValue = valueOps.get(cacheKey);if(cachedValue != null){//如果缓存命中,更新本地缓存,返回结果LOCAL_CACHE.put(cacheKey,cachedValue);Page<PictureVO> cachePage = JSONUtil.toBean(cachedValue, Page.class);return ResultUtils.success(cachePage);}//3.查询数据库Page<Picture> picturePage = pictureService.page(new Page<>(current, size),pictureService.getQueryWrapper(pictureQueryRequest));//获取封装类Page<PictureVO> pictureVOPage = pictureService.getPictureVOPage(picturePage, request);//4.更新缓存//存入 Redis 缓存LOCAL_CACHE.put(cacheKey,cacheValue);cacheValue = JSONUtil.toJsonStr(pictureVOPage);// 5 - 10 分钟随机过期,防止雪崩int cacheExpireTime = 300 + RandomUtil.randomInt(0,300);valueOps.set(cacheKey,cacheValue,cacheExpireTime, TimeUnit.SECONDS);//返回结果return ResultUtils.success(pictureVOPage);}

测试:

没有缓存

有缓存

有缓存的情况下明显访问速度快了十来倍。

http://www.lqws.cn/news/464041.html

相关文章:

  • 【信号与系统四】采样和通信系统
  • rent8_wechat-最常用出租屋管理系统-微信小程序
  • Mac Parallels Desktop Kali 2025 代理设置
  • 外卖之后再度进军酒旅,京东多线出击的逻辑是什么?
  • Electron 开发桌面应用概述
  • 跟着AI学习C# Day27
  • Pytorch3D 中涉及的知识点汇总
  • 【Flutter】状态管理框架Provider和Get对比分析(面试常用)
  • Python内存使用分析工具深度解析与实践指南(下篇)
  • 江科大STM32入门:DMA传输数据
  • java 找出两个json文件的不同之处
  • 神经网络中的均方误差(Mean Squared Error)详解
  • 自定义OceanBase集群安装并使用OCP接管集群
  • 【DCS开源项目】—— Lua 如何调用 DLL、DLL 与 DCS World 的交互
  • LeetCode第279题_完全平方数
  • Vue3 的生命周期:从 Composition API 视角看
  • DeepEP开源MoE模型分布式通信库
  • Linux运维新人自用笔记(Ubuntu磁盘命名规则、新磁盘分区、主流文件系统类型、mkfs命令格式化文件系统、临时和永久挂载、挂载报错、dd指令)
  • 2.7 Python方法调用机制解析:从描述符到字节码执行
  • 5.2 Qt Creator 使用FFmpeg库
  • win环境使用openssl创建p12证书
  • 微前端MFE:(React 与 Angular)框架之间的通信方式
  • word-spacing 属性
  • Kubernetes控制平面组件:Kubelet详解(八):容器存储接口 CSI
  • C++链表的虚拟头节点
  • 课程目录:腾讯混元3D × Unity3D全流程开发
  • Python pytesseract【OCR引擎库】 简介
  • 【JVM|内存结构】第一天
  • 【论文笔记】【强化微调】TinyLLaVA-Video-R1:小参数模型也能视频推理
  • Spring-MyBatis基本操作