缓存穿透、击穿和雪崩到底有什么区别
三者的本质区别只有一句话:缓存穿透是「查一个压根不存在的数据」,缓存击穿是「一个热点 key 恰好在高并发瞬间过期」,缓存雪崩是「大批 key 同时失效或缓存服务整体挂掉」。共同点是请求都穿过了缓存打到数据库,但触发条件、影响规模和应对手段完全不同,混用方案等于白做。
缓存穿透是什么?和击穿、雪崩差在哪
结论:穿透的根因是「数据不存在」,缓存里没有、数据库里也没有,所以每次请求都会穿过缓存直击数据库。
典型场景是恶意攻击:用 id=-1、随机 UUID 这类不存在的参数高频请求接口,缓存永远不命中。因为缓存的设计是「查到了才写回」,不存在的数据天然不会被缓存,攻击者用 1 个 key 就能持续压垮数据库。
识别方法很直接:看数据库的慢查询日志,如果大量 WHERE id = ? 返回 0 行,就是穿透。
三种机制的区别可以这样记:
| 维度 | 穿透 | 击穿 | 雪崩 |
|---|---|---|---|
| 涉及 key 数量 | 1 个不存在的 key | 1 个存在的热点 key | 成千上万个 key |
| 触发条件 | 数据本身不存在 | 热点 key 到期瞬间 | 批量同时过期或缓存宕机 |
| 数据库压力 | 持续、稳定 | 瞬时尖峰 | 整体性洪峰 |
| 数据是否真实存在 | 否 | 是 | 是 |
缓存穿透怎么解决?
结论:核心思路是「让不存在的 key 也进缓存」,或「在缓存之前就把非法请求拦掉」。
第一招是缓存空值:查数据库返回 null 时,也往 Redis 写一个空标记,设置 60 秒左右的短过期时间(如 SET key "" EX 60)。这样同一个不存在的 id 在 60 秒内只会打一次数据库。注意过期时间不能设太长,否则数据后来真实写入时会出现脏读。
第二招是布隆过滤器:把所有可能存在的 key 预先哈希到位数组里,请求先过过滤器,判断「一定不存在」就直接返回。误判率 p 设为 0.01 时,每个元素大约需要 9.6 bit 空间(m/n = -ln p / (ln2)²)。它只会误判「存在」(假阳性),不会漏判真 key,代价是不支持删除元素,需要定期重建或用 Counting Bloom Filter。
第三招是入口校验:id <= 0、参数长度超限的直接在网关层拒绝,成本最低,但只能挡住规则明显的攻击。
缓存击穿怎么解决?
结论:击穿只发生在热点 key 上,解决手段是「让同一时刻只有一个线程回源查库」。
最常用的是互斥锁:回源前先 SET lock:key 1 NX EX 10,抢到锁的线程查数据库并回写缓存,其他线程等待 50-200ms 后重试,重试 3 次仍未命中就返回降级数据。线程数从 1000 降到 1,数据库压力直接消失。
第二种是逻辑过期:热点 key 在 Redis 里永不设 TTL,value 内部存一个 expireTime 字段。读到过期的逻辑时间戳时,异步起一个线程去更新缓存,当前请求直接返回旧数据。这样任何请求都不会阻塞,代价是短时间内会返回旧值。
第三种是预热:大促开始前用脚本把 Top 1000 热点数据的缓存提前写好,避开自然过期时间点。
缓存雪崩怎么解决?
结论:雪崩的应对是多层防护,单一手段挡不住。
过期时间打散是第一层:给基础 TTL 加上随机扰动,例如基础 30 分钟,实际写成 1800 + random(0, 600) 秒,避免同一批 key 在同一秒集体失效。
多级缓存是第二层:本地缓存(Caffeine、Guava Cache)扛住 80% 以上的读请求,Redis 作为第二级。本地缓存 TTL 可设 10 秒,即使 Redis 整体宕机,绝大部分请求也不会落到数据库。
高可用是第三层:Redis 走主从加哨兵,或直接用 Cluster 分片,单节点故障时 10 秒内完成故障转移。
兜底是第四层:数据库前加 Sentinel 或 Hystrix 做熔断限流,QPS 超过阈值直接返回兜底页,宁可部分用户降级,也不让数据库被压死。
一句话总结该用哪套方案
穿透靠「缓存空值 + 布隆过滤器」,击穿靠「互斥锁 + 逻辑过期」,雪崩靠「TTL 加随机 + 多级缓存 + 高可用 + 熔断」。三者可以叠用:线上系统同时开布隆过滤器挡穿透、对 Top 热点 key 加互斥锁防击穿、TTL 统一加 0-600 秒随机值防雪崩,这三步做完,缓存层对数据库的保护才算完整。
原文链接:https://www.gj0.com/thread-171.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。