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

在项目中如何巧妙使用缓存

缓存

对于经常访问的数据,每次都从数据库(硬盘)中获取是比较慢,可以利用性能更高的存储来提高系统响应速度,俗称缓存 。合理使用缓存可以显著降低数据库的压力、提高系统性能。
那么,什么样的数据适合缓存呢?一般情况下就4个字“读多写少 ”,要频繁查询的、不怎么修改的。
具体来说:

  1. 高频访问的数据:如系统首页、热门推荐内容等。
  2. 计算成本较高的数据:如复杂查询结果、大量数据的统计结果。
  3. 允许短时间延迟的数据:如不需要实时更新的排行榜、图片列表等。
    在我们的项目中,主页是用户高频访问的内容,调用的获取图片列表的接口也是高频访问的。而且即使数据更新存在一定延迟,也不会对用户体验造成明显影响,因此非常适合缓存。

Redis分布式缓存

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

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

缓存设计

需要缓存首页的图片列表数据,也就是对 listPictureVOByPage接口进行缓存。首先按照缓存3要素"key、value、过期时间”进行设计。

  1. 缓存 key 设计
    由于接口支持传入不同的查询条件,对应的数据不同,因此需要将查询条件作为缓存key的一部分。
    可以将查询条件对象转换为JSON字符串,但这个JSON会比较长,可以利用哈希算法(md5)来压缩key。
    此外,由于使用分布式缓存,可能由多个项目和业务共享,因此需要在key的开头拼接前缀进行隔离。设计出的key如下:
    yunpicture:listPictureVOByPage:${查询条件key}
  2. 缓存 value 设计
    缓存从数据库中查到的Page分页对象,存储为什么格式呢?这里有2种选择:
  • 为了可读性,可以转换为JSON结构的字符串
  • 为了压缩空间,可以存为二进制等其他结构
    但是对应的 Redis 数据结构都是 string。
  1. 缓存过期时间设置
    必须设置缓存过期时间! 根据实际业务场景和缓存空间的大小、数据的一致性的要求设置,合适即可,此处由于查询条件较多、而且考虑到图片会持续更新,设置为5~60分钟即可。

Caffeine 本地缓存

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

  • 数据访问量有限的小型数据集
  • 不需要服务器间共享数据的单机应用
  • 高频、低延迟的访问场景(如用户临时会话信息、短期热点数据)。
    对于Java项目,Caffeine是主流的本地缓存技术,拥有极高的性能和丰富的功能。比如可以精确控制缓存数量和大小、支持缓存过期、支持多种缓存淘汰策略、支持异步操作、线程安全等。
    由于本地缓存不需要引入额外的中间件,成本更低。因此如果只是要提升数据访问性能,优先考虑本地缓存而不是分布式缓存。

缓存设计

本地缓存的设计和分布式缓存基本一致,不再赞述。但有2个区别:

  1. 本地缓存需要自己创建初始化缓存结构(可以简单理解为要自己new一个HashMap)。
  2. 由于本地缓存本身就是服务器隔离的,而且占用服务器的内存,key可以更精简一些,不用再添加项目前缀。

多级缓存

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

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

相关文章:

  • nginx基本使用 linux(mac下的)
  • 【SpringBoot高级】SpringBoot与Kafka深度整合:从入门到企业级实战
  • PB应用变为Rust语言方案
  • 【Actix Web】构建高性能 Rust API:Actix Web 最佳实践与进阶指南
  • 【学习笔记】深入理解Java虚拟机学习笔记——第13章 线程安全与锁优化
  • 力扣网C语言编程题:在数组中查找目标值位置之二分查找法
  • Vue.js 中的数字格式化组件:`FormattedNumber`
  • C++ STL深度剖析:Stack、queue、deque容器适配器核心接口
  • LLM 安全防护解决方案,使用 Roberta 训练 LLM 提示词注入攻击判决模型
  • 案例开发 - 日程管理系统 - 第一期
  • Hadoop集群异常:两个NameNode全部为StandBy状态
  • uniapp image引用本地图片不显示问题
  • Centos 8设置固定IP
  • AlphaGenome:基因组学领域的人工智能革命
  • 【C++进阶】--- 继承
  • pytest 中的重试机制
  • 深入详解:决策树算法的概念、原理、实现与应用场景
  • 数据库(MYsql)
  • 可编程逻辑器件的演进与对比分析
  • Flutter基础(Future和async/await)