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

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

前言

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

但简单的 setget 远远不够。如何防止缓存雪崩?如何处理缓存穿透?如何保证数据一致性?本文将基于 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 接口将具备生产级的"抗揍"能力!
相关推荐
打工的小王7 分钟前
Spring Boot(三)Spring Boot整合SpringMVC
java·spring boot·后端
毕设源码-赖学姐9 分钟前
【开题答辩全过程】以 高校体育场馆管理系统为例,包含答辩的问题和答案
java·spring boot
vx_Biye_Design11 分钟前
【关注可免费领取源码】房屋出租系统的设计与实现--毕设附源码40805
java·spring boot·spring·spring cloud·servlet·eclipse·课程设计
翱翔-蓝天40 分钟前
为什么“看起来很规范”的后端项目反而臃肿且性能下降
spring boot
摇滚侠1 小时前
阿里云安装的 Redis 在什么位置,如何找到 Redis 的安装位置
redis·阿里云·云计算
啦啦啦_99991 小时前
Redis-2-queryFormat()方法
数据库·redis·缓存
80530单词突击赢2 小时前
JavaWeb进阶:SpringBoot核心与Bean管理
java·spring boot·后端
long3163 小时前
Aho-Corasick 模式搜索算法
java·数据结构·spring boot·后端·算法·排序算法
独断万古他化3 小时前
【SSM开发实战:博客系统】(三)核心业务功能开发与安全加密实现
spring boot·spring·mybatis·博客系统·加密
rannn_1113 小时前
【苍穹外卖|Day4】套餐页面开发(新增套餐、分页查询、删除套餐、修改套餐、起售停售)
java·spring boot·后端·学习