讲解缓存穿透、缓存击穿、缓存雪崩

📌 什么是缓存穿透?

缓存穿透 是指请求的数据既不在缓存中,也不在数据库中 ,导致请求每次都打到数据库,从而绕过缓存,造成数据库压力过大,甚至可能被恶意攻击。

🧨 常见场景:

  • 用户请求一个不存在的id
  • 恶意构造大量随机key攻击接口(缓存永远不命中

🔍 缓存穿透的危害

  • 缓存失效,数据库成了前线,系统抗压能力下降
  • 可被攻击者利用,导致数据库宕机
  • 缓存失效不一定是Bug,但会影响系统性能和稳定性

✅ 缓存穿透的解决方案

1. 缓存空对象(推荐)

思路:

当数据库查询结果为空时,也将空结果写入缓存(比如缓存一个null或特殊标记),并设置短 TTL。

java 复制代码
// 伪代码示例
String value = redis.get(key);
if (value == null) {
    value = db.query(id);
    if (value == null) {
        redis.set(key, "", 5分钟); // 缓存空值
    } else {
        redis.set(key, value, 正常TTL);
    }
}

优点:

  • 简单易实现
  • 避免反复查询数据库

缺点:

  • 可能缓存大量无用key,浪费空间(可用短TTL规避)

2. 布隆过滤器(推荐)

思路:

在缓存之前,使用布隆过滤器记录所有合法请求ID集合(如商品ID、用户ID等)。非法请求直接拒绝,不查缓存和数据库。

text 复制代码
请求流程:
请求 -> 布隆过滤器判断是否可能存在 -> 是 → 查缓存 → 查DB
                                      → 否 → 拦截请求,直接返回空或错误提示

优点:

  • 空间占用极小,效率极高
  • 拦截恶意请求非常有效

缺点:

  • 极小概率误判存在
  • 布隆过滤器需手动维护更新(如新增商品ID时要添加进过滤器)

3. 请求参数校验(防御第一步)

思路:

  • 在接口层增加参数校验,如 ID 是否为正整数,是否符合业务规则。

示例:

java 复制代码
if (id <= 0) return error("非法请求");

优点:

  • 处理非法请求最直接的方法
  • 降低后续压力

4. 加强安全保护

适用于遭受恶意攻击的情况:

  • 添加验证码机制(限制恶意访问)
  • 接口限流(如:每个 IP 每秒请求数)
  • 黑名单机制(封掉频繁攻击源)

🧠 总结对比

方案 优点 缺点 适用场景
缓存空对象 简单、实用 占用空间、要注意TTL 数据库查无时自动兜底
布隆过滤器 高效拦截、节省资源 有误判、维护复杂 商品ID、用户ID等有限集合验证
参数校验 快速、低成本 只防止明显非法请求 前端传参初步过滤
接口限流/验证码 防止恶意攻击 用户体验稍差 高并发或敏感接口

📍 二、缓存雪崩(Cache Avalanche)

1️⃣ 问题定义

大批缓存Key同时过期或Redis宕机,导致大量请求绕过缓存,直接打数据库,系统雪崩式瘫痪。

2️⃣ 常见触发原因

  • 所有缓存key的 TTL 相同或过于集中
  • Redis实例突然宕机或重启
  • 系统突发高并发流量

3️⃣ 解决方案

✅ 方案一:设置 随机TTL

缓存过期时间加一个随机值,避免集中失效

java 复制代码
int baseTtl = 30 * 60; // 30分钟
int random = new Random().nextInt(10 * 60); // 额外0~10分钟
redis.set(key, value, baseTtl + random);

✅ 方案二:缓存预热 + 缓存重建

  • 服务启动时,提前将重要数据加载进缓存
  • 定时刷新热点缓存,避免过期同时发生

✅ 方案三:限流 + 降级机制

  • 请求高峰期,使用限流器(如:令牌桶)限速访问数据库
  • 当缓存不可用时,返回默认值/服务降级响应,保障系统可用性

✅ 方案四:Redis高可用部署

  • 使用主从集群、哨兵机制、持久化(RDB+AOF)保障Redis稳定性
  • 防止整个缓存系统宕机引发大面积雪崩

📍 三、缓存击穿(Cache Breakdown)

1️⃣ 问题定义

缓存中的某个"热点Key"刚好过期,此时有大量并发请求访问这个Key,由于缓存失效,请求直接打到数据库,导致数据库瞬时压力过大。

2️⃣ 常见场景

  • 商品详情页、活动信息等热门数据设置了相同 TTL
  • 过期瞬间,数千上万请求同时到来,击穿缓存层

3️⃣ 解决方案

✅ 方案一:互斥锁(加锁防击穿)

这里使用Redis中的setnx指令实现互斥锁,只有当值不存在时才能进行set操作

第一个请求线程访问数据库,其它线程等待,避免并发打DB

java 复制代码
if (缓存不存在) {
    获取分布式锁
    if (获取成功) {
        查询数据库
        写入缓存
        释放锁
    } else {
        睡一会儿再重试
    }
}

适合场景: 数据更新频率低、读取频率高的热点数据
工具推荐: Redisson、分布式锁实现


✅ 方案二:逻辑过期(伪装未过期)

给缓存设置一个逻辑过期时间,一旦逻辑过期,则后台异步刷新数据,但仍然返回旧数据,防止缓存空缺。

json 复制代码
{
  "data": {商品数据},
  "expireTime": "2025-07-30 17:00:00"
}
  • 读取缓存时先判断是否逻辑过期
  • 如果过期,异步线程刷新缓存
  • 仍然返回旧数据,避免数据库被压垮

优点: 无需加锁,不阻塞用户请求
缺点: 数据有一点点旧,不适合强一致性需求


✅ 方案三:热点数据预热

热门数据在缓存过期前手动刷新或重新写入,避免其真正过期。

  • 启动时将热门key预先加载进缓存
  • 定时任务刷新缓存

📌 总结口诀记忆法

  • 穿透:查无数据 → 加布隆,缓存空
  • 击穿:热点失效 → 加锁或逻辑过期
  • 雪崩:集体失效 → TTL分散,限流兜底

🧠 三种常见缓存问题对比速览

名称 定义简述 触发条件 危害 常用解决方案
缓存穿透 查无数据 请求的数据 缓存和数据库都没有 请求直击数据库(高并发压垮数据库) 布隆过滤器、缓存空对象、参数校验
缓存击穿 热点失效 单个热点key刚好失效,此时大量请求涌入 同一时间大量请求打到数据库 互斥锁、逻辑过期、热点预热
缓存雪崩 大量失效 大量缓存key同一时间失效(如设置相同TTL) 缓存大面积失效 → DB崩溃 过期时间随机化、分批加载、限流降级
相关推荐
追逐时光者3 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_3 小时前
敏捷开发流程-精简版
前端·后端
苏打水com3 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
间彧4 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧4 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧4 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧4 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧5 小时前
Spring Cloud Gateway详解与应用实战
后端
EnCi Zheng6 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端
烙印6016 小时前
Spring容器的心脏:深度解析refresh()方法(上)
java·后端·spring