侧边栏壁纸
博主头像
ProSayJ 博主等级

Talk is cheap. Show me the code.

  • 累计撰写 47 篇文章
  • 累计创建 18 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

✅什么是缓存击穿、缓存穿透、缓存雪崩?

YangJian
2025-06-29 / 0 评论 / 0 点赞 / 9 阅读 / 0 字

✅ 一、缓存穿透(Cache Penetration)

📌 1. 什么是缓存穿透?

请求的数据既不在缓存中,也不在数据库中,每次请求都会穿过缓存直达数据库,导致数据库压力激增。

🧨 2. 典型场景

用户频繁请求不存在的 key,比如:

GET user:999999999
  • 如果缓存中没命中,而数据库中也没有这个用户,系统每次都查询数据库,导致缓存形同虚设

⚠️ 3. 后果

  • 数据库频繁被访问,失去缓存保护层,容易 被恶意攻击(如 ID 枚举攻击)拖垮系统。

✅ 4. 解决方案

方案

说明

缓存空值

对不存在的数据也设置一个默认的值,如 null,并设置短 TTL

布隆过滤器

请求先通过布隆过滤器判断是否有可能存在,不存在则直接拒绝请求(避免查询数据库)

参数校验

拦截非法请求,如对 ID 范围、格式等校验(限制非法访问)

缓存空值

为这些key对应的值设置为null并放到缓存中,这样再出现查询这个key 的请求的时候,直接返回null即可 。
但是还需要注意的就是需要有一个失效时间,因为如果不设置失效的话,如果哪天总部有货了,门店还是当做没货的话,就会影响销量了。

💡 示例:

// Java 空值缓存策略
User user = redis.get("user:99999");
if (user == null) {
    user = db.query("SELECT * FROM user WHERE id = 99999");
    if (user == null) {
        redis.set("user:99999", "", 5 * 60); // 缓存空值 5 分钟
    }
}


BloomFilter

很多时候,缓存穿透是因为有很多恶意流量的请求,这些请求可能随机生成很多Key来请求查询,这些肯定在缓存和数据库中都没有,那就很容易导致缓存穿透。

针对类似的情况,可以使用一个过滤器。

比如如果有一群人经常来门店问一些根本不存在的色号,比如五彩斑斓的黑,这些色号该品牌根本没生产过的话,店员就可以直接告诉顾客不存在就行了,也不需要惊动总部。

在缓存穿透防治上常用的技术是布隆过滤器(Bloom Filter)

布隆过滤器是一种比较巧妙的概率性数据结构,它可以告诉你数据一定不存在或可能存在,相比Map、Set、List等传统数据结构它占用内存少、结构更高效。

对于缓存穿透,我们可以将查询的数据条件都哈希到一个足够大的布隆过滤器中,用户发送的请求会先被布隆过滤器拦截,一定不存在的数据就直接拦截返回了,从而避免下一步对数据库的压力。

🤔 面试官心理:

考察你是否能识别缓存失效的边缘场景,以及你是否具备安全意识(如防止恶意探测请求)和缓存设计能力。


✅ 二、缓存击穿(Cache Breakdown)

📌 1. 什么是缓存击穿?

某个热点数据在某一时刻刚好失效,大量并发请求同时穿透缓存去访问数据库,导致瞬时流量打爆数据库

🧨 2. 典型场景

一个商品秒杀页面的详情缓存:

GET product:1001

这个 key 每次 TTL 到期瞬间,有上万请求同时访问,就会全部打到数据库。

⚠️ 3. 后果

  • 数据库被瞬间冲垮,系统雪崩。

  • 容易产生 “惊群效应”:多个线程同时尝试回源查询同一资源。

✅ 4. 解决方案

方案

说明

设置热点数据不过期

永不过期或手动刷新 TTL

加互斥锁(mutex)

第一个请求查询数据库,其它线程等待或返回旧值

提前预热

提前缓存热点数据,避免同时失效

随机过期时间

同类 key 设置不同 TTL,避免集中失效(平滑过期)

异步定时更新

比如某一个热点数据的过期时间是1小时,那么每59分钟,通过定时任务去更新这个热点key,并重新设置其过期时间。

互斥锁

就先处理第一个用户的咨询,其他同样请求的顾客先排队等待。一直到店员从总部那里获取到最新的库存信息后,就可以安排其他人继续购买了。

在缓存处理上,通常使用一个互斥锁来解决缓存击穿的问题。简单来说就是当Redis中根据key获得的value值为空时,先锁上,然后从数据库加载,加载完毕,释放锁。若其他线程也在请求该key时,发现获取锁失败,则先阻塞。

💡 示例:

// Java 互斥锁方案
User user = redis.get("user:1001");
if (user == null) {
    if (tryLock("lock:user:1001")) {
        try {
            user = db.query(...);
            redis.set("user:1001", user, 60 * 5);
        } finally {
            unlock("lock:user:1001");
        }
    } else {
        Thread.sleep(50); // 休眠后重试,防止抢锁打爆数据库
        return getUser();
    }
}

🤔 面试官心理:

面试官想知道你是否有并发控制的意识,是否能在高并发条件下避免缓存短暂失效带来的打击,是否考虑锁的粒度与死锁问题


✅ 三、缓存雪崩(Cache Avalanche)

📌 1. 什么是缓存雪崩?

大量缓存同时过期或 Redis 故障宕机,导致大量请求全部落到数据库,造成系统整体崩溃。

🧨 2. 典型场景

你批量缓存了一批 key,都设置了 10 分钟 TTL,结果某一时刻同时过期,数据库同时被打爆。

⚠️ 3. 后果

  • Redis 没起作用,数据库负载飙升。

  • 整个系统可能进入雪崩式崩溃

✅ 4. 解决方案

方案

说明

设置不同过期时间

给 key 添加随机 TTL,避免同时过期(如 60s ~ 120s)

数据预热

系统重启后预加载核心缓存数据

降级策略

Redis 故障时直接返回默认值或错误提示,保护数据库不被击穿

多级缓存

本地缓存 + Redis 结合,减少对 Redis 的依赖

💡 示例:

int baseExpire = 300; // 5分钟
int randomExtra = new Random().nextInt(60); // 随机附加秒数
redis.set("key", value, baseExpire + randomExtra);

不同的过期时间

为了避免缓存雪崩,门店可以考虑给不同的色号的口红预留不同的库存,并且采用不同的频率咨询总部库存情况,更新到门店中。这样就可以避免突然同一个时间点所有色号都售罄。

为了避免大量的缓存在同一时间过期,可以把不同的key过期时间设置成不同的, 并且通过定时刷新的方式更新过期时间。


集群

为了避免门店出问题导致大量顾客直接打电话到总部,可以考虑开更多的门店,将用户分流到多个店铺中。
类似的,在缓存雪崩问题防治上面,一个比较典型的技术就是采用集群方式部署,使用集群可以避免服务单点故障。

🤔 面试官心理:

面试官在评估你是否能从全局架构角度考虑问题:当缓存系统整体挂了,你的方案能否保证服务可用、是否具备降级思维和容灾意识


🧠 总结对比表

名称

触发条件

主要影响

解决手段关键点

缓存穿透

请求数据不存在

每次都打到数据库

布隆过滤器 / 缓存空值

缓存击穿

某热点 key 失效瞬间

瞬间并发全部打到数据库

加锁 / 不过期 / 提前刷新

缓存雪崩

大量 key 同时失效 / Redis 故障

大规模流量打到数据库,系统崩溃

随机过期 / 降级 / 多级缓存


简明对比:缓存击穿 vs 缓存雪崩

类型

缓存击穿(Cache Breakdown)

缓存雪崩(Cache Avalanche)

定义

单个热点 Key 失效,瞬时大量请求打到数据库

大量缓存同时失效 或 Redis 故障,所有请求打爆数据库

触发条件

某个高并发访问的 key 失效

大量 key 同时设置了相同 TTL、或 Redis 整体不可用

影响范围

影响单点数据(热点 key)

影响大范围或全局数据访问

本质

缓存保护失效,数据库被突袭

缓存系统整体保护失效,系统雪崩

解决思路

加锁、预热、永不过期、互斥访问等

随机 TTL、多级缓存、限流降级、熔断保护等


解决方案精要对比

场景

主要策略

缓存击穿

🔹 热点 key 不设置过期时间🔹 加互斥锁防止并发🔹 异步更新缓存🔹 提前预热热点 key

缓存雪崩

🔸 TTL 设置随机值错峰过期🔸 加入本地缓存兜底🔸 启动时缓存预热🔸 服务限流+降级🔸 多副本容灾部署

✅ 高级总结句式

我对缓存穿透、击穿、雪崩这三种 Redis 缓存风险有深入理解,并能针对不同场景设计防御方案,如布隆过滤器、互斥锁、降级熔断、多级缓存与预热策略等,具备在高并发分布式环境中保障系统稳定性和可用性的能力。

缓存击穿和缓存雪崩都属于缓存失效带来的高并发风险,但前者是局部热点 key 的突发失效,后者是大面积 key 同时过期或缓存宕机造成全局影响。

我一般会通过加锁、永不过期或预热热点 key来防止缓存击穿,使用过期时间加随机、降级限流、多级缓存、本地兜底等手段来防止缓存雪崩。同时我也会定期检查 Redis key 失效分布,避免集中设置统一 TTL,确保缓存系统在高并发场景下稳定可靠。

0

评论区