当前位置: 首页 > 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.lryc.cn/news/573375.html

相关文章:

  • AWS-SAA 第二部份:安全性和权限管理
  • 《map和set的使用介绍》
  • Linux TCP/IP协议栈中的TCP输入处理:net/ipv4/tcp_input.c解析
  • TCP 三次握手与四次挥手全流程详解
  • 【智能体】n8n聊天获取链接后爬虫知乎
  • 设计模式精讲 Day 9:装饰器模式(Decorator Pattern)
  • 【RTP】基于mediasoup的RtpPacket的H.264打包、解包和demo 1:不含扩展
  • 2D曲线点云平滑去噪
  • 雨声_锦程_时年
  • linux生产环境下根据关键字搜索指定日志文件命令
  • 软件工程期末试卷选择题版带答案(共214道)
  • 借助ChatGPT快速开发图片转PDF的Python工具
  • Java大厂面试攻略:Spring Boot与微服务架构深度剖析
  • `shallowReactive` 与 `shallowRef`:浅层响应式 API
  • 网络编程及原理(六):三次握手、四次挥手
  • .Net Core 获取与bin目录相同文件路径的文件
  • MinIO入门教程:从零开始搭建方便快捷的分布式对象存储服务
  • verilog HDLBits刷题“Module addsub”--模块 addsub---加法器-减法器
  • python版halcon环境配置
  • 59-Oracle 10046事件-知识准备
  • 1.23Node.js 中操作 mongodb
  • Django中为api自定义一些装饰器:如参数校验等
  • 获取 Git 仓库
  • npm包冲突install失败
  • 深入浅出:Go语言中的Cookie、Session和Token认证机制
  • Snapchat矩阵运营新策略:亚矩阵云手机打造高效社交网络
  • SiteAzure:解决数据库服务器内存频繁吃满
  • 【Flutter】状态管理框架Provider和Get对比分析(面试常用)
  • 57-Oracle SQL Profile(23ai)实操
  • 编程基础:耦合