Springboot 内置缓存与整合Redis作为缓存

Spring Boot 的缓存注解允许开发者在不修改业务逻辑的情况下,将方法的计算结果缓存起来,从而减少重复计算和数据库查询,提高系统性能。

 1、Spring Boot Cache 的基本用法及常用注解

1. 引入依赖

首先,需要在项目中引入缓存相关依赖。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2. 启用缓存

在 Spring Boot 主程序类上添加 @EnableCaching 注解,开启缓存支持:

@SpringBootApplication
@EnableCaching
public class CacheApplication {public static void main(String[] args) {SpringApplication.run(CacheApplication.class, args);}
}

3. 常用注解

@Cacheable
  • 用于将方法的返回结果缓存起来。
  • 方法被调用时,Spring 会先检查缓存中是否有数据,如果有则直接返回缓存结果,否则执行方法并将结果放入缓存。
  • @Cacheable 本身并不直接提供过期时间的配置。缓存的过期时间取决于具体的缓存提供者。
@Service
public class UserService {@Cacheable(value = "userCache", key = "#id")#根据id不同查找缓存,在同一个缓存空间(如 responseCache)内,可以存储多个结果,每个结果与唯一的缓存键对应(即不同的请求参数对应不同的缓存结果)。public User getUserById(Long id) {// 假设这是一个耗时的数据库查询return userRepository.findById(id).orElse(null);}
}

注:

  • @Cacheable:表示该方法的结果应该被缓存。Spring 在方法第一次被调用时会执行该方法并缓存其结果,后续对同一参数的调用将直接返回缓存中的结果。

  • value = "userCache":指定缓存的名称,value 属性对应缓存的命名空间。这里的 userCache 是缓存的名称,用于存储此方法的返回结果。userCache 可以对应一个具体的缓存实现(如 Redis、EhCache 等)中一个独立的缓存区域。

  • key = "#id":指定缓存的键。使用 Spring 表达式语言(SpEL)来定义缓存键的生成规则,#id 表示方法参数 id 的值作为缓存的键。

    • 在这里,#id 会直接使用方法参数 id 的值。例如,当 id=1 时,缓存键就是 1
    • 如果不指定 key 属性,Spring 会默认将所有方法参数的组合作为缓存键。

默认键生成机制

@Cacheable 注解没有指定 key 时,Spring Cache 会将所有方法参数的组合作为默认缓存键。具体来说,Spring Cache 使用 SimpleKeyGenerator 生成缓存键:

  1. 单一参数:如果方法有一个参数,且未指定 key,Spring 会直接使用该参数作为缓存键。
  2. 多参数:如果方法有多个参数,Spring 会将它们组合成一个 SimpleKey 对象作为缓存键。
  3. 无参数:如果方法没有参数,Spring 会使用 SimpleKey.EMPTY 作为缓存键。

如果传入的是自定义对象参数,Spring 会调用该对象的 hashCodeequals 方法来识别不同参数值的唯一性,以确保生成的缓存键唯一。对于自定义对象,如果没有重写 equalshashCode 方法,则默认使用 Object 类的实现。ObjecthashCodeequals 方法基于对象的内存地址判断两个对象是否相等,这样不同实例即使内容相同,也会被认为是不同的对象。因此,不重写 equalshashCode 可能会导致缓存命中失败,从而产生重复的缓存条目。

  • 不指定 key 且传入自定义对象:需要重写对象的 equalshashCode 方法,以确保相同内容的对象具有相同的缓存键。
  • 推荐方式:如果传入对象属性确定,可以使用 SpEL 表达式明确指定缓存键(如 key = "#user.id"),这样更简洁并避免了对 equalshashCode 的依赖。
@CachePut
  • 用于强制更新缓存内容,方法每次都会执行,将返回值放入缓存。
  • 常用于方法更新数据后,同时更新缓存中的数据。
    
    @Service
    public class UserService {@CachePut(value = "userCache", key = "#user.id")public User updateUser(User user) {// 更新数据库中的用户信息return userRepository.save(user);}
    }
    

    @CachePut 注解表示方法执行完成后将结果更新到 userCache 中,key 为 user.id

@CacheEvict
  • 用于清除缓存数据。
  • 通常用于删除或更新方法,用于从缓存中移除不再需要的数据。
    @Service
    public class UserService {@CacheEvict(value = "userCache", key = "#id")public void deleteUser(Long id) {// 删除数据库中的用户userRepository.deleteById(id);}
    }

    @CacheEvict 会将缓存 userCache 中 key 为 id 的缓存项删除,确保缓存中的数据与数据库保持一致。

@Caching
  • 用于组合多个缓存注解,适用于需要同时执行多个缓存操作的场景。

@Service
public class UserService {@Caching(put = { @CachePut(value = "userCache", key = "#user.id") },evict = { @CacheEvict(value = "userListCache", allEntries = true) })public User saveUser(User user) {// 保存用户信息return userRepository.save(user);}
}

在此示例中,@Caching 组合了 @CachePut@CacheEvict,即在缓存 userCache 中更新用户数据,同时清除 userListCache 中的所有缓存项。

4. 自定义缓存键和条件

Spring Boot Cache 允许通过 keycondition 参数自定义缓存键和条件。

  • key:自定义缓存的 key,可以使用 SpEL 表达式。默认是方法参数的组合。
  • condition:满足指定条件时才缓存,使用 SpEL 表达式。
  • unless:满足条件时不缓存,优先级高于 condition
@Cacheable(value = "userCache", key = "#user.id", condition = "#user.age > 18", unless = "#result == null")
public User getUser(User user) {return userRepository.findById(user.getId()).orElse(null);
}

 在此例中,condition 表示只有用户年龄大于 18 时才缓存,unless 表示当方法返回值为 null 时不缓存。

5.完整示例

@Service
public class UserService {@Cacheable(value = "userCache", key = "#id")public Optional<User> getUserById(Long id) {// 模拟数据库查询return userRepository.findById(id);}@CachePut(value = "userCache", key = "#user.id")public User updateUser(User user) {// 更新数据库中的用户信息return userRepository.save(user);}@CacheEvict(value = "userCache", key = "#id")public void deleteUser(Long id) {// 从数据库中删除用户信息userRepository.deleteById(id);}
}

缺点:本地缓存, 分布式场景容易产生数据不一致的情况。

2、Spring Boot 结合 Redis 实现缓存

为了解决缓存一致问题,可以引入分布式缓存Redis。

1. 引入依赖

项目中添加 Redis 的依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. 配置 Redis 连接

application.ymlapplication.properties 中配置 Redis 连接信息:

# Spring Cache 配置
spring.cache.type=redis   # 设置 Spring 缓存使用 Redis# Redis 连接配置
spring.redis.host=localhost   # Redis 服务器地址
spring.redis.port=6379        # Redis 服务器端口
spring.redis.password=yourpassword   # Redis 连接密码,如果没有则省略
# Redis 缓存过期时间(可选)
spring.cache.redis.time-to-live=60000   # 设置缓存的默认过期时间(单位为毫秒),此处为 60 秒spring.cache.redis.use-key-prefix=true    # 使用 key 前缀,果有多种业务数据存储在同一个 Redis 实例中,建议开启 key 前缀
spring.cache.redis.key-prefix=test_cache_  # 设置 Redis 缓存的 key 前缀

3. 启用缓存支持

同上

4. 使用缓存注解 

同上

这样即可使用 Redis分布式缓存替换Spring Boot 自带缓存显著提升性能,解决分布式场景下数据不一致问题。

3、引入缓存带来的问题

缓存击穿

定义

缓存击穿是指一个热点数据在缓存过期的瞬间,大量并发请求访问该数据,由于缓存刚好过期,导致请求同时涌入数据库查询。这个问题会导致数据库压力骤增,甚至崩溃。

原因
  • 热点数据在缓存失效的瞬间,大量请求同时访问该数据。
  • 高并发场景下,单一热点数据的缓存失效后数据库压力过大。
解决方案
  1. 设置热点数据永不过期(逻辑过期):对于少量非常热门的数据,可以设置缓存永不过期。然后通过后台异步线程来定时更新缓存,避免缓存失效导致的瞬间压力增大。
  2. 加锁机制:在缓存失效时,只有一个线程去数据库中查询数据并回填缓存,其他线程等待第一个线程完成后从缓存读取。可以使用分布式锁(如 Redis 分布式锁)来控制并发。
  3. 双重检查:在缓存失效时再进行二次检查。在缓存过期的情况下,多个线程查询缓存后都会去请求数据库,可以在第一次查询数据库后立即更新缓存,再次检查缓存以减少数据库的并发压力。
  4. 增加定时任务定期刷新缓存:通过 Spring 的定时任务来实现定时刷新缓存,保证热点数据在缓存即将失效时被重新加载。

缓存穿透

定义

缓存穿透是指大量请求查询缓存中不存在的数据,导致请求直接穿透缓存到数据库,给数据库带来极大压力。例如,用户查询一个不存在的用户 ID,由于缓存中没有该数据且数据库也没有结果,每次查询都绕过缓存直接访问数据库。

原因
  • 查询的 key 不存在,缓存没有数据,直接查询数据库。
  • 恶意攻击或程序错误导致频繁查询不存在的 key。
解决方案
  1. 缓存空结果:对于数据库查询结果为空的数据,将空结果也缓存一段时间(例如 5 分钟)。这样,短时间内相同的请求不会频繁查询数据库。可以通过设置较短的过期时间来避免大量无效数据长期占用缓存。(spring.cache.redis.cache-null-values=true)
  2. 布隆过滤器:在缓存之前增加一个布隆过滤器,用于判断 key 是否可能存在。布隆过滤器可以有效过滤掉不存在的数据,避免直接查询数据库。
  3. 参数校验:对用户输入的 key 进行校验,防止恶意请求。比如查询数据库前对 key 做基础检查,确保格式和范围有效。

缓存雪崩

定义

缓存雪崩是指缓存中大量数据在同一时间失效,导致大量请求同时打到数据库,造成数据库压力激增。缓存雪崩可能会导致系统出现短暂或持续的不可用。

原因
  • 大量缓存设置了相同的过期时间,在某一时刻同时失效。
  • 系统重启或崩溃,导致所有缓存失效。
解决方案
  1. 缓存过期时间加随机:避免所有缓存设置相同的过期时间,可以在过期时间上增加一个随机值,使得缓存过期时间分散,避免集中失效。
  2. 缓存预热:在系统启动时,将常用的热点数据提前加载到缓存中,避免高峰时段突然访问数据库。
  3. 分级降级策略:在缓存雪崩发生时,可以使用限流策略和降级策略,限制数据库的访问频率。同时,也可以用备用缓存来缓解瞬时压力。
  4. 多层缓存架构:使用多级缓存(如本地缓存 + 分布式缓存)或多机房缓存,实现分布式缓存冗余,避免单点失效导致大量缓存失效。

总结:

问题定义解决方案
缓存穿透请求大量不存在的 key,导致直接查询数据库缓存空结果、布隆过滤器、参数校验
缓存击穿热点数据过期,瞬时大量请求穿透缓存,打到数据库设置热点数据永不过期、加锁机制、双重检查
缓存雪崩缓存中大量数据同时失效,导致数据库请求激增设置过期时间加随机、缓存预热、分级降级策略、多层缓存

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/463427.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AVL树的插入和删除分析(图解和代码)

文章目录 1. AVL树1.1 AVL树的概念1.2 AVL树节点的定义1.3AVL树的插入1.4 AVL树的删除查找要删除的节点判断要删除节点的类型从下往上调节平衡因子真正删除节点整体代码 1.5 AVL树的性能分析 1. AVL树 1.1 AVL树的概念 二叉搜索树虽然能够缩短查找的效率,但是如果数据有序或者…

MySQL-基础汇总

MySQL-基础汇总 数据库对于任何一个从事后台开发的人说都是永远躲不掉的&#xff0c;任何系统或程序离开了数据的支持都变的毫无意义。而管理数据的工具——数据库就显得尤为重要。本章节我们的核心就是 MySQL&#xff0c;相信很多小伙伴跟我一样&#xff0c;也沉浸在增、删、…

一条sql语句是怎么执行的?

一、问题 InnoDB存储引擎&#xff0c;执行了下列语句&#xff1a; UPDATE user SET name "小明" WHERE id1002; 其中id是主键&#xff0c;这条SQL语句的执行过程是怎样的&#xff1f; 二、答案 首先客户端与MySQL连接器进行连接&#xff0c;然后分析器经过词法…

MySQL数据库迁移到DM8数据库

1. 达梦新建zsaqks库 2. 打开DM数据迁移工具 3. 新建工程 4. 迁移 - 右击 - 新建迁移 下一步 5. 选择迁移方式 6. MySQL数据源 请输入MySQL数据库信息 7. DM数据库目的 请输入达梦数据库信息 8. 迁移选项 保持对象名大小写(勾选) 9. 指定模式 指定是从数据源复制对象。 10.…

Qt 练习做一个登录界面

练习做一个登录界面 效果 UI图 UI代码 <?xml version"1.0" encoding"UTF-8"?> <ui version"4.0"><class>Dialog</class><widget class"QDialog" name"Dialog"><property name"ge…

minikube 的 Kubernetes 入门教程--(五)

本文记录 Minikube 在 Kubernetes 上安装 WordPress 和 MySQL。 这两个应用都使用 PersistentVolumes 和 PersistentVolumeClaims 保存数据。 在深入这些步骤之前&#xff0c;先分享来自kubernetes.io教程。 链接>>使用持久卷部署 WordPress 和 MySQL | Kubernetes 获…

HarmonyOS 私仓搭建

1. HarmonyOS 私仓搭建 私仓搭建文档&#xff1a;https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-ohpm-repo-quickstart-V5   发布共享包[https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-har-publish-0000001597973129-V5]…

根据问题现象、用户操作场景及日志打印去排查C++软件问题,必要时尝试去复现问题

目录 1、概述 2、通过现有信息无法定位问题时&#xff0c;则需要尝试去复现问题 3、非崩溃问题与崩溃问题的一般排查思路 3.1、非崩溃问题的排查思路 3.2、崩溃问题的排查思路 4、难以复现问题的可能原因总结 4.1、问题难以复现&#xff0c;可能和某种特殊的业务场景或操…

《JVM第3课》运行时数据区

无痛快速学习入门JVM&#xff0c;欢迎订阅本免费专栏 运行时数据区结构图如下&#xff1a; 可分为 5 个区域&#xff0c;分别是方法区、堆区、虚拟机栈、本地方法栈、程序计数器。这里大概介绍一下各个模块的作用&#xff0c;会在后面的文章展开讲。 类加载子系统会把类信息…

class 100 KMP算法原理和代码详解

1. KMP 算法介绍 1.1 暴力方法 暴力方法就是将两个字符串进行一个一个比较 这个知道就行了, 我们的重点是 KMP 算法 1.2 KMP 算法介绍 暴力方法的时间复杂度是&#xff1a;O(n * m), 使用 KMP 算法可以将时间复杂度优化到&#xff1a;O(n m). 暴力方法时间慢的原因是&…

不基于Gin手撸一个RPC服务

目标 实现一个GRPC框架&#xff0c;可以通过grpc-ui来对接口进行访问。也可以使用client来直接调用服务端服务 准备&#xff08;这边以Mac系统举例&#xff09; 安装homebrew&#xff08;如果没有安装的话&#xff09; /bin/bash -c "$(curl -fsSL https://raw.github…

大数据治理:策略、技术与挑战

随着信息技术的飞速发展&#xff0c;大数据已经成为现代企业运营和决策的重要基础。然而&#xff0c;大数据的复杂性、多样性和规模性给数据管理带来了前所未有的挑战。因此&#xff0c;大数据治理应运而生&#xff0c;成为确保数据质量、合规性、安全性和可用性的关键手段。本…

Web应用性能测试工具 - httpstat

在数字化时代&#xff0c;网站的性能直接影响用户体验和业务成功。你是否曾经在浏览网页时&#xff0c;遇到加载缓慢的困扰&#xff1f;在这个快速变化的互联网环境中&#xff0c;如何快速诊断和优化Web应用的性能呢&#xff1f;今天&#xff0c;我们将探讨一个强大的工具——h…

宝藏虚拟化学习资料大全

最近发现了关于虚拟化的宝藏资料&#xff0c;瑞斯拜&#xff01;原文链接如下&#xff1a; 500篇关于虚拟化的经典资料&#xff0c;含CPU虚拟化&#xff0c;磁盘虚拟化&#xff0c;内存虚拟化&#xff0c;IO虚拟化。 目录 &#x1fa90; 虚拟化基础 &#x1f343; 虚拟化分类&…

【源码+文档】基于SpringBoot+Vue旅游网站系统【提供源码+答辩PPT+参考文档+项目部署】

作者简介&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容&#xff1a;&#x1f31f;Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…

微服务核心——网关路由

目录 前言 一、登录存在的问题归纳 二、*微服务网关整体方案 三、认识微服务网关 四、网关鉴权实现 五、OpenFeign微服务间用户标识信息传递实现 六、微服务网关知识追问巩固 前言 本篇文章具体讲解微服务中网关的实现逻辑、用于解决什么样的问题。其中标题中标注* 涉…

移植 AWTK 到 纯血鸿蒙(HarmonyOS NEXT)系统 (0) - 序

移植 AWTK 到 纯血鸿蒙 (HarmonyOS NEXT) 系统 (0) - 序 前段时间纯血鸿蒙系统 HarmonyOS 5.0&#xff08;又称 HarmonyOS NEXT&#xff09;正式推出&#xff0c;这是继苹果 iOS 和安卓系统后&#xff0c;全球第三大移动操作系统。纯正国产操作系统登场&#xff0c;国人无不欢…

docker-compose安装rabbitmq 并开启延迟队列和管理面板插件(rabbitmq_delayed_message_exchange)

问题&#xff1a; 解决rabbitmq-plugins enable rabbitmq_delayed_message_exchange &#xff1a;plugins_not_found 我是在docker-compose环境部署的 services:rabbitmq:image: rabbitmq:4.0-managementrestart: alwayscontainer_name: rabbitmqports:- 5672:5672- 15672:156…

SpringBoot AOP介绍、核心概念、相应实现

文章目录 AOP介绍AOP的核心概念切面(Aspect)切点(Join Point)语法具体解释 增强(Advice)织入(weaving) 相应实现权限校验日志输出 AOP介绍 AOP全称Aspect Oriented Programming意为面向切面编程&#xff0c;通过预编译和运行期间通过动态代理来实现程序功能统一维护的技术。AO…

Python 数据结构对比:列表与数组的选择指南

文章目录 &#x1f4af;前言&#x1f4af;Python中的列表&#xff08;list&#xff09;和数组&#xff08;array&#xff09;的详细对比1. 数据类型的灵活性2. 性能与效率3. 功能与操作4. 使用场景5. 数据结构选择的考量6. 实际应用案例7. 结论 &#x1f4af;小结 &#x1f4af…