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

news2024/11/5 18:55:08

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.coloradmin.cn/o/2231631.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024年,Rust开发语言,现在怎么样了?

Rust开发语言有着一些其他语言明显的优势&#xff0c;但也充满着争议&#xff0c;难上手、学习陡峭等。 Rust 是由 Mozilla 主导开发的通用、编译型编程语言&#xff0c;2010年首次公开。 在 Stack Overflow 的年度开发者调查报告中&#xff0c;Rust 连续多年被评为“最受喜爱…

Golang | Leetcode Golang题解之第528题按权重随机选择

题目&#xff1a; 题解&#xff1a; type Solution struct {pre []int }func Constructor(w []int) Solution {for i : 1; i < len(w); i {w[i] w[i-1]}return Solution{w} }func (s *Solution) PickIndex() int {x : rand.Intn(s.pre[len(s.pre)-1]) 1return sort.Searc…

微服务day02

教学文档&#xff1a; 黑马教学文档 Docker Docker的安装 镜像和容器 命令解读 常见命令 案例 查看DockerHub&#xff0c;拉取Nginx镜像&#xff0c;创建并运行容器 搜索Nginx镜像&#xff1a;在 www.hub.docker.com 网站进行查询 拉取镜像&#xff1a; docker pull ngin…

认证鉴权框架之—sa-token

一、概述 Satoken 是一个 Java 实现的权限认证框架&#xff0c;它主要用于 Web 应用程序的权限控制。Satoken 提供了丰富的功能来简化权限管理的过程&#xff0c;使得开发者可以更加专注于业务逻辑的开发。 二、逻辑流程 1、登录认证 &#xff08;1&#xff09;、创建token …

python爬虫实现自动获取论文GB 7714引用

在写中文论文、本硕博毕业设计的时候要求非常严格的引用格式——GB 7714引用。对于普通学生来说都是在google scholar上获取&#xff0c;一个一个输入点击很麻烦&#xff0c;就想使用python完成这个自动化流程&#xff0c;实现批量的倒入论文标题&#xff0c;导出引用。 正常引…

pycharm中python控制台出现CommandNotFoundError: No command ‘conda run‘.

1、错误现象 pycharm中打开python控制台出现CommandNotFoundError: No command conda run.的错误。 2、背景 conda是4.6版本&#xff0c;在Anaconda Prompt可以正常运行虚拟环境。 3、解决方法 更新conda版本&#xff0c;基本命令&#xff0c;会自动更新到最新版本。 con…

masm汇编字符输入小写转大写演示

从键盘读取一个字符变成大写换行并输出 assume cs:codecode segmentstart:mov ah,1int 21hmov bl,alsub bl,20hmov dl,10mov ah,2int 21hmov dl,blmov ah,2int 21hmov ah,4chint 21hcode ends end start 效果演示&#xff1a;

VisualStudio远程编译调试linux_c++程序(二)

前章讲述了gdb相关&#xff0c;这章主要讲述用VisualStudio调试编译linux_c程序 1&#xff1a;环境 win10 VisualStudio 2022 Community ubuntu22.04 2:安装 1>vs安装时&#xff0c;勾选 使用c进行linux 和嵌入式开发 (这里以vs2022为例) OR VS安装好了&#xff0c; 选择工…

【002】基于SpringBoot+thymeleaf实现的蓝天幼儿园管理系统

基于SpringBootthymeleaf实现的蓝天幼儿园管理系统 文章目录 系统说明技术选型成果展示账号地址及其他说明源码获取 系统说明 基于SpringBootthymeleaf实现的蓝天幼儿园管理系统是为幼儿园提供的一套管理平台&#xff0c;可以提高幼儿园信息管理的准确性&#xff0c;系统将信息…

【AIGC】从CoT到BoT:AGI推理能力提升24%的技术变革如何驱动ChatGPT未来发展

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;迈向AGI的新跨越&#x1f4af;BoT与CoT的技术对比技术原理差异推理性能提升应用范围和通用性从错误中学习的能力总结 &#x1f4af;BoT的工作流程和机制初始化过程生成推…

无人机拦截捕获/直接摧毁算法详解!

一、无人机拦截捕获算法 网捕技术 原理&#xff1a;抛撒特殊设计的网具&#xff0c;捕获并固定无人机。 特点&#xff1a; 适用于小型无人机。 对无人机的损害较小&#xff0c;基本不影响其后续使用。 捕获成功率较高&#xff0c;且成本相对较低。 应用实例&#xff1a;…

解码层跑几次取决于输出词汇多少;10个单词,在解码层跑几次transformer

目录 解码层跑几次取决于输出词汇多少 10个单词,在解码层跑几次transformer 解码层跑几次取决于输出词汇多少 10个单词,在解码层跑几次transformer 取决于具体任务和输出要求 在自然语言处理任务中,Transformer 架构的解码器(Decoder)运行次数与你想要生成的输出长度有关…

算法【Java】—— 记忆化搜索

记忆化搜索 在前面我们已经学习了递归回溯等知识&#xff0c;什么是记忆化搜索&#xff0c;其实就是带着备忘录的递归&#xff0c;我们知道在递归过程中如果如果出现大量的重复的相同的子问题的时候&#xff0c;我们可能进行了多次递归&#xff0c;但是这些递归其实可以只用进…

优化EDM邮件营销,送达率与用户体验双赢

EDM邮件营销需选对平台&#xff0c;优化邮件列表&#xff0c;确保内容优质&#xff0c;进行邮件测试&#xff0c;关注用户反馈调整频率&#xff0c;以保高送达率&#xff0c;提升营销效果。 1. 了解电子邮件送达率的重要性 在开始优化邮件送达率之前&#xff0c;首先需要理解电…

Metasploit渗透测试之在云服务器中使用MSF

概述 随着云计算的发展&#xff0c;对基于云的应用程序、服务和基础设施的测试也在不断增加。在对云部署进行渗透测试时&#xff0c;最大的问题之一是共享所有权。过去&#xff0c;在进行渗透测试时&#xff0c;企业会拥有网络上的所有组件&#xff0c;我们可以对它们进行全部…

Qt桌面应用开发 第一天

目录 1.默认代码解析 1.1main.h解析 1.2myWidget.h解析 1.3FirstProject.pro解析&#xff08;FirstProject为创建的Qt项目名&#xff09; 2.命名规范与快捷键 3.按钮控件及窗口设置 3.1按钮控件QPushButton类 3.2窗口常用设计 4.Qt中的对象树 5.Qt中的坐标系 Qt是一个…

简记Vue3(三)—— ref、props、生命周期、hooks

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

Mybatis查询数据库,返回List集合,集合元素也是List。

#有时间需求会要求&#xff1a;查询全校的学生数据&#xff0c;且学生数据按班级划分。那么就需要List<List<user>>类型的数据。 SQL语句 SELECT JSON_ARRAYAGG(JSON_OBJECT(name , name ,BJMC, BJMC ,BJBH,BJBH)) as dev_user FROM dev_user WHERE project_id …

Freertos学习日志(1)-基础知识

目录 1.什么是Freertos&#xff1f; 2.为什么要学习RTOS&#xff1f; 3.Freertos多任务处理的原理 1.什么是Freertos&#xff1f; RTOS&#xff0c;即&#xff08;Real Time Operating System 实时操作系统&#xff09;&#xff0c;是一种体积小巧、确定性强的计算机操作系统…

批量提取当前文件夹内的文件名

在需要提取的文件夹内新建一个txt文件&#xff0c;输入&#xff1a; dir ./b>name.txt 然后将该txt文件的扩展名改为.bat 如图 双击即可提取当前文件夹文件名&#xff0c;并保存到name.txt内