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

Java + Spring Boot + Redis技术栈,在实际使用缓存时遇到 缓存击穿、缓存穿透、缓存雪崩

针对 Java + Spring Boot + Redis 技术栈,在实际使用缓存时遇到 缓存击穿、缓存穿透、缓存雪崩 是非常常见的,下面我会结合这个技术组合,给出 具体原因、问题场景、以及对应的解决方案和代码/配置示例,帮助你在实际项目中更好地应对这些问题。


一、缓存击穿(Cache Breakdown)—— 热点 key 突然失效,大量请求直达数据库

🎯 场景举例

比如“热门商品详情”(如 iPhone 15 商品ID=1001)被缓存,且缓存 key product:1001 设置了过期时间,假设是 30 分钟。当这个 key 恰好在某个高峰期过期,而又有大量用户同时访问该商品,就会导致 大量请求穿透到数据库,造成 DB 压力剧增。

✅ 解决方案(Spring Boot + Redis)

方案1:使用 互斥锁(分布式锁),只允许一个线程重建缓存

使用 Redis 的 SETNX(或 Redisson 的分布式锁)来实现:

依赖(如果使用 Redisson,推荐):
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.x.x</version>
</dependency>
示例代码(伪代码逻辑,简化版):
@Autowired
private RedisTemplate<String, Object> redisTemplate;@Autowired
private RedissonClient redissonClient; // 使用 Redisson 分布式锁public Product getProductById(Long id) {String cacheKey = "product:" + id;// 1. 先查缓存Product product = (Product) redisTemplate.opsForValue().get(cacheKey);if (product != null) {return product;}// 2. 缓存没有,尝试获取锁RLock lock = redissonClient.getLock("lock:product:" + id);try {// 尝试加锁,最多等10秒,锁持有15秒后自动释放if (lock.tryLock(10, 15, TimeUnit.SECONDS)) {try {// 双重检查,可能其他线程已经重建好缓存product = (Product) redisTemplate.opsForValue().get(cacheKey);if (product != null) {return product;}// 3. 查数据库product = productRepository.findById(id).orElse(null);if (product != null) {// 4. 写入缓存,设置过期时间,比如30分钟redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);} else {// 可选:缓存空对象,防止穿透redisTemplate.opsForValue().set(cacheKey, new NullProduct(), 5, TimeUnit.MINUTES);}return product;} finally {lock.unlock();}} else {// 没抢到锁,稍后重试或者返回默认/错误信息Thread.sleep(100);return getProductById(id); // 简单重试,生产环境建议限流或返回兜底数据}} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("获取锁失败", e);}
}

⚠️ 注意:生产环境中应做好重试机制、超时控制与降级策略,不要轻易重试或递归调用。

方案2:逻辑过期 + 后台刷新(适合读多写少)

不设置真正的过期时间,而是在 value 里保存一个过期时间字段,定时检查并异步刷新缓存。


二、缓存穿透(Cache Penetration)—— 查询不存在的数据,绕过缓存

🎯 场景举例

用户请求了一个 不存在的商品 ID(比如 999999999),这个 ID 在数据库中根本不存在,所以每次查询:

  • 缓存中没有
  • 数据库中也没有
    → 导致每次都打到数据库,如果有人恶意发起大量此类请求,DB 就会扛不住。

✅ 解决方案

方案1:缓存空对象(Null Object Pattern)

当查询数据库发现数据不存在时,仍然将一个特殊的标记(如 null 或自定义的 NullProduct 对象)存入 Redis,并设置较短的过期时间,例如 3~5 分钟,避免频繁查库。

代码片段(接上面):
if (product == null) {// 缓存空对象,防止穿透redisTemplate.opsForValue().set(cacheKey, new NullProduct(), 5, TimeUnit.MINUTES);return null;
}

其中 NullProduct 是你自定义的一个类,代表“空结果”,可用于前端展示或逻辑判断。

方案2:使用 布隆过滤器(Bloom Filter)

在查询数据库之前,先用布隆过滤器判断该 key(如商品 ID)是否 可能存在,如果布隆过滤器判断“一定不存在”,则直接返回,无需查缓存和数据库。

如何集成布隆过滤器?

可以使用 Google 的 Guava BloomFilter(适合单机)或 RedisBloom(适合分布式,需引入 Redis 模块)。

简易示例(Guava,适合简单场景):
// 初始化时将所有合法商品ID加入布隆过滤器
BloomFilter<Long> bloomFilter = BloomFilter.create(Funnels.longFunnel(), 1000000,  // 预期元素数量0.01      // 误判率
);// 添加合法ID
bloomFilter.put(1001L);
bloomFilter.put(1002L);// 查询时先判断
if (!bloomFilter.mightContain(id)) {return null; // 一定不存在
}
// 再查缓存、查DB

⚠️ 生产环境中若数据量大、分布式部署,建议使用 RedisBloom 模块(需 Redis 支持)或自建布隆过滤器服务。


三、缓存雪崩(Cache Avalanche)—— 大量缓存同时失效 / Redis 宕机

🎯 场景举例

假如你给所有商品缓存都设置了 过期时间为 30 分钟,并且这些 key 的过期时间基本一致,那么在 某个 30 分钟的整点时刻,大量缓存同时失效,导致大量请求直接打到数据库,造成服务崩溃。

或者,Redis 宕机或网络抖动,也会导致缓存完全不可用,所有请求直连 DB。

✅ 解决方案

方案1:设置 随机过期时间,避免同时失效

不要所有 key 都设置相同的过期时间,比如:

// 原始:30分钟
// 改为:30分钟 + 随机0~10分钟
int expireTime = 30 + new Random().nextInt(10);
redisTemplate.opsForValue().set(cacheKey, product, expireTime, TimeUnit.MINUTES);

这样可以有效让缓存 错开失效时间,避免同时雪崩。

方案2:多级缓存策略

  • 一级缓存:本地缓存(如 Caffeine、Guava Cache)
  • 二级缓存:Redis

即使 Redis 出现问题,本地缓存依然能挡住一部分流量。

示例(Caffeine 作为本地缓存):
<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId>
</dependency>
LoadingCache<Long, Product> localCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).maximumSize(10_000).build(this::loadProductFromRedisOrDB);

方案3:Redis 高可用

  • 使用 Redis 哨兵(Sentinel)集群(Cluster) 模式,提高缓存可用性,防止单点故障。
  • 如果 Redis 宕机,应有 降级策略(比如返回旧数据、默认值、错误提示等)。

方案4:熔断降级(如 Sentinel、Hystrix)

当数据库压力过大或 Redis 不可用时,可以快速失败或返回兜底内容,避免服务雪崩。


四、其他增强措施(推荐)

✅ 缓存预热

在系统启动时或低峰期,提前将热点数据加载到缓存中,避免冷启动时大量请求直接访问数据库。

✅ 监控与报警

  • 使用 Spring Boot Actuator + Prometheus + Grafana 监控:
    • 缓存命中率
    • Redis 响应时间、QPS
    • 数据库负载
  • 设置报警规则,如缓存未命中率过高、Redis 连接失败等。

✅ 接口限流

对于高频访问接口,可使用 Sentinel、RateLimiter、Redis + Lua 实现接口限流,防止恶意刷接口。


总结(Java + Spring Boot + Redis 应对三大问题)

问题原因简述推荐解决方案(Spring Boot + Redis)
缓存击穿热点 key 突然失效,大量请求直达 DB互斥锁(Redisson)、逻辑过期、后台刷新、双重检查锁
缓存穿透查询不存在的数据,绕过缓存直连 DB缓存空对象(Null Object)、布隆过滤器(Guava / RedisBloom)、参数校验
缓存雪崩大量 key 同时失效 / Redis 宕机设置随机过期时间、多级缓存(Caffeine + Redis)、Redis 高可用、熔断降级、缓存预热
http://www.dtcms.com/a/568915.html

相关文章:

  • Elasticsearch安装使用
  • 太原网站建设斯飞网络服务器wordpress
  • 知识图谱与黑盒大语言模型:生物医学研究的新突破
  • 不小心在idea中点了add 到版本控制 怎么样恢复?
  • 建网站空间的详细说明金华市有网站建设最低价
  • 服务器bmc功能
  • Linux Watchdog机制深度分析与实践指南
  • 在amazon linux 2023上面源码手动安装tesseract5.5.1
  • Linux---序列化与反序列化
  • 1.6.课设实验-数据结构-栈、队列-银行叫号系统2.0
  • 在amazon linux 2023上面通过Fedora 36软件仓库源安装tesseract5
  • seo网站做推广公司公司网站哪里好
  • 动态资源加载:不用Selenium,如何高效抓取Ajax和SPA网站?
  • 7.【NXP 号令者RT1052】开发——实战-串口通信
  • CANoe学习(二)使用CANdb++制作dbc
  • Node.js Buffer:深入理解与高效使用
  • Prompt Gen Desktop 管理和迭代你的 Prompt!
  • Prompt Composition with LangChain’s PipelinePromptTemplate
  • 【HarmonyOS NEXT】常见的性能优化
  • [Ethernet in CANoe]2--如何在CANoe中去仿真CP版本的SOME/IP通信
  • 优先队列(堆)
  • 基于 TCP 线程池服务器封装 HTTP 服务器:从协议解析到适配落地
  • xargs
  • 据库事务是数据库管理系统 ACID 四大特性
  • 宜昌市住房和城乡建设局网站wordpress后台慢
  • SSM基于HTML5的流浪动物领养平台yww0b(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 网站栏目分类网站开发市场
  • word转Pdf,在window正常,放在linux服务器上就转出来中文是空白
  • 攻防世界-Misc-pdf
  • 开启RN之旅——前端基础