SpringBoot集成Redis缓存,提升接口性能的五大实战策略

SpringBoot集成Redis缓存,提升接口性能的五大实战策略

前言

在高并发、高吞吐量的互联网应用中,数据库往往是系统的瓶颈所在。Redis 作为一款高性能的内存数据结构存储系统,常被用作 缓存 层,以扛住 90% 以上的业务流量。

但简单的 set 和 get 远远不够。如何防止缓存雪崩?如何处理缓存穿透?如何保证数据一致性?本文将基于 Spring Boot 3.x ,结合 Mermaid 架构图 ,为你深度剖析集成 Redis 的五大实战策略,助你将接口性能提升 10 倍以上。

一、 缓存架构全景

在介绍具体策略前,我们需要明确 Redis 在系统中的位置。标准的缓存架构通常遵循"Cache Aside Pattern(旁路缓存模式)"。
应用层

  1. 查询 2. 读取缓存 命中
    未命中
    返回数据
  2. 写入缓存 4. 返回数据 客户端
    Spring Boot 应用
    Redis
    MySQL 数据库

二、 策略一:Spring Cache 注解化开发(基础策略)

对于简单的查询场景,Spring 提供了 spring-boot-starter-data-redis 配合 spring-boot-starter-cache,让我们像使用本地变量一样使用 Redis。

2.1 核心注解

  • @Cacheable:查询时先查缓存,没有则查 DB 并写入缓存。
  • @CachePut:执行方法体,并将结果更新到缓存(确保执行)。
  • @CacheEvict:移除缓存(用于更新或删除操作)。

2.2 实战代码

java 复制代码
@Service
public class ProductService {
    
    @Autowired
    private ProductMapper productMapper;
    // value 是命名空间,key 是缓存的唯一键(支持 SpEL)
    @Cacheable(value = "product", key = "#id")
    public Product getProductById(Long id) {
        System.out.println("查询数据库...");
        return productMapper.selectById(id);
    }
    @CachePut(value = "product", key = "#product.id")
    public Product updateProduct(Product product) {
        productMapper.updateById(product);
        return product;
    }
    @CacheEvict(value = "product", key = "#id")
    public void deleteProduct(Long id) {
        productMapper.deleteById(id);
    }
}

三、 策略二:防止缓存穿透(布隆过滤器)

问题 :攻击者大量请求一个不存在的数据 (ID 为 -1),导致请求直接穿透 Redis,打到数据库,可能导致数据库瞬间崩溃。
方案 :使用 布隆过滤器 。它是一个占空间极小的二进制向量,能高效判断一个数据"一定不存在 "或"可能存在"。

3.1 原理图解

渲染错误: Mermaid 渲染失败: Parse error on line 7: ...eq --> BF BF -->{判断是否存在?} BF -- ----------------------^ Expecting 'AMP', 'COLON', 'PIPE', 'TESTSTR', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'NODE_STRING', 'BRKT', 'MINUS', 'MULT', 'UNICODE_TEXT', got 'DIAMOND_START'

3.2 实战实现

利用 Redisson(Redis 客户端)可以轻松集成布隆过滤器。

java 复制代码
@Autowired
private RedissonClient redissonClient;
public void initBloomFilter() {
    RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter("productFilter");
    // 预计元素数量 10000,误判率 0.01
    bloomFilter.tryInit(10000, 0.01);
    // 初始化时将所有合法 ID 放入
    for (long i = 1; i <= 10000; i++) {
        bloomFilter.add(i);
    }
}
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable Long id) {
    RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter("productFilter");
    if (!bloomFilter.contains(id)) {
        throw new RuntimeException("商品 ID 不存在"); // 拦截非法请求
    }
    // ... 正常走 Redis 查询逻辑
}

四、 策略三:防止缓存雪崩(随机过期时间)

问题 :如果某一批热门商品在 同一时刻全部过期 ,成千上万的请求会瞬间击穿 Redis,像雪崩一样压垮数据库。
方案 :设置过期时间时,加上一个随机值,让过期时间分散开来。

java 复制代码
// 错误写法:统一过期 1 小时
redisTemplate.opsForValue().set(key, value, 3600, TimeUnit.SECONDS);
// 正确写法:基础时间 + 随机时间 (如 5 分钟内的随机数)
long baseExpire = 3600;
long randomExpire = ThreadLocalRandom.current().nextLong(0, 300);
redisTemplate.opsForValue().set(key, value, baseExpire + randomExpire, TimeUnit.SECONDS);

五、 策略四:解决缓存击穿(互斥锁)

问题 :某一个极度热点 的 Key(如秒杀商品 ID=1001)过期了,此时有 1 万个并发请求涌入。它们发现 Redis 没有,都会同时去查数据库,这就是缓存击穿 。
方案 :使用 Redis 分布式锁。只允许一个线程查数据库,回写缓存,其他线程等待并读缓存。

5.1 互斥锁流程图

Database Redis 请求 2...N 请求 1 Database Redis 请求 2...N 请求 1 只有 R1 可以查库 查询 Key 1 缓存未命中 2 尝试 SET NX (加锁) 3 锁获取成功 4 查询 Key 5 缓存未命中 6 尝试 SET NX (加锁) 7 锁获取失败 8 查询数据 9 返回结果 10 写入缓存 11 释放锁 12 稍后重试查询 13 缓存命中 (R1 写入的) 14

5.2 实战代码

使用 SET key value NX EX seconds 原性加锁。

java 复制代码
public String getProductWithLock(String key) {
    // 1. 查缓存
    String value = (String) redisTemplate.opsForValue().get(key);
    if (StringUtils.isNotBlank(value)) return value;
    // 2. 获取锁
    String lockKey = "lock:" + key;
    Boolean isLocked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
    if (Boolean.TRUE.equals(isLocked)) {
        try {
            // Double Check: 防止在获取锁期间,其他线程已经写入缓存
            value = (String) redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(value)) return value;
            // 3. 查库
            value = db.query(); 
            // 4. 写缓存
            redisTemplate.opsForValue().set(key, value, 3600, TimeUnit.SECONDS);
            return value;
        } finally {
            // 5. 释放锁 (需确保只释放自己的锁,这里简化为直接删除)
            redisTemplate.delete(lockKey);
        }
    } else {
        // 获取锁失败,休眠 100ms 重试,或者返回默认值
        return getProductWithLock(key); 
    }
}

六、 策略五:缓存一致性(双写策略)

问题 :修改了数据库数据,缓存如果不及时更新,会导致"脏读"。
方案 :通常采用 延时双删 策略,或者订阅 Binlog (推荐方案,如 Canal)。这里介绍最基础的 Cache Aside Pattern 更新逻辑:先更新 DB,再删除缓存。
为什么先更 DB?

  • 如果先删缓存:线程 A 删了 -> 线程 B 读 DB 并写缓存(旧值) -> 线程 A 更新 DB。此时库新,缓存旧,不一致。
  • 如果先更 DB:线程 A 更新 DB(失败回滚) -> A 删缓存(成功)。如果 A 更新 DB 成功,B 删缓存失败?概率很低,且可以通过重试解决。

6.1 一致性流程图

失败
成功
成功
失败
更新商品请求
更新数据库
事务回滚, 结束
删除缓存
操作成功
发送失败消息到 MQ
消费者重试删除


七、 总结

集成 Redis 并不是简单的 set/get,而是一场与数据一致性、并发安全博弈的过程。

  1. 基础层 :熟练使用 Spring Cache。
  2. 防御层 :利用 布隆过滤器 拦截恶意穿透,利用 随机过期时间 防止雪崩。
  3. 高并发层 :利用 分布式锁 解决击穿问题。
  4. 数据层 :先更 DB,再删 Cache,并配合 MQ 重试保证最终一致性。
    掌握这五大策略,你的 Spring Boot 接口将具备生产级的"抗揍"能力!
相关推荐
顽疲3 小时前
java springboot vue 养老系统源码详解:请假 care_leave 销假与床位停费日扣实践
java·vue.js·spring boot
Java_AI工程师4 小时前
Spring4 / SpringBoot1.x 老项目想接入 AI?不用升级框架!3 套无痛落地方案
java·人工智能·spring boot
VimorphTech4 小时前
Java 文件缓存实战:如何避免读到未下载完成的视频?
java·spring boot
十年Java程序媛6 小时前
JVM 内存区域与对象内存布局|new 对象底层流程,对象头 MarkWord 详解
java·jvm·spring boot
linyb极客之路6 小时前
Spring Boot 2升3太痛苦?用OpenRewrite批量搞定,少走90%弯路!
spring boot
171320330计算机毕设编程6 小时前
计算机毕业设计 基于springboot+vue的在线观鸟视频播放网站 LW PPT
java·大数据·vue.js·spring boot·算法·课程设计
Pniubi7 小时前
力扣146.LRU缓存机制
算法·leetcode·缓存
欣栀♡9 小时前
Spring 循环依赖不止三级缓存,Boot 2.6 起这个开关默认已经关了
java·spring·缓存
xiaoqiMikko9 小时前
Redis 实战(二):宕机会丢多少数据 —— kill -9 与真断电下 RDB / AOF 逐个实测(6.2.4)
redis·后端
星辰云-11 小时前
# 基于Spring Boot的德州特产扒鸡商城系统(毕设|源码|论文|演示视频)
spring boot·后端·课程设计