4.3 缓存三大问题:穿透、击穿、雪崩
只要用了"先查缓存,没有再查数据库"的模式,就绕不开这三个问题。 它们是 Redis 面试的顶级高频题,也是真实线上事故的常见原因。名字很像,本章帮你彻底分清。
先记住一张对比表
| 问题 | 一句话描述 | 关键词 |
|---|---|---|
| 缓存穿透 | 查询根本不存在的数据,缓存永远没有,每次都打到数据库 | 数据不存在 |
| 缓存击穿 | 某个热点 key 过期的瞬间,海量请求同时打到数据库 | 单个热 key 过期 |
| 缓存雪崩 | 大量 key 同时过期(或 Redis 整个挂了),请求全部涌向数据库 | 大面积失效 |
记忆技巧:穿透 = 穿过去了(数据两边都没有);击穿 = 击破一个点(一个热 key);雪崩 = 一大片塌了。
1. 缓存穿透(Cache Penetration)
问题
正常流程:查缓存 → 没有 → 查数据库 → 写回缓存。
但如果查的是数据库里也不存在的数据(比如 id = -1 或随机瞎编的 id),数据库查不到,也就没有东西写回缓存。于是每次请求都完整穿过缓存打到数据库。攻击者用脚本狂查不存在的 id,就能轻松打垮数据库。
解法一:缓存空值(简单常用)
数据库查不到时,也往 Redis 存一个特殊标记,并设较短的过期时间:
def get_product(pid):
key = f"product:{pid}"
cached = r.get(key)
if cached is not None:
return None if cached == "NULL" else json.loads(cached)
product = db.query(pid)
if product:
r.set(key, json.dumps(product), ex=3600)
else:
r.set(key, "NULL", ex=60) # 空值也缓存!短过期
return product
要点:空值过期时间要短(比如 60 秒),防止占内存、也防止真实数据新增后长时间读不到。
解法二:布隆过滤器(进阶)
布隆过滤器(Bloom Filter)是一个超省内存的结构,能快速判断"某元素一定不存在或可能存在"。启动时把全部合法 id 灌进去,请求先过它:判定不存在的直接拒绝,根本不碰缓存和数据库。
Redis 官方 RedisBloom 模块提供支持(bf.add / bf.exists)。新手记住概念即可。
附加手段
参数校验(id 格式不对直接拒绝)、接口限流、封禁恶意 IP。
2. 缓存击穿(Hotspot Invalid)
问题
某个超级热点 key(如首页 Banner、顶流明星的微博)每秒被访问 1 万次。它的缓存过期的那一瞬间:1 万个请求同时发现"缓存没有",同时去查数据库、同时写回缓存——数据库瞬间被打出尖峰,可能直接跪掉。
解法一:互斥锁(保证只有一个人去查库)
发现缓存失效时,先抢一把锁(就用上一章的 SET NX),抢到的那一个去查数据库并回填缓存,其他人等几十毫秒后重试读缓存:
def get_hot_data(key):
cached = r.get(key)
if cached:
return cached
# 缓存失效,抢锁重建
if r.set(f"lock:{key}", "1", nx=True, ex=10): # 只有一个请求能成功
try:
data = db.query(...)
r.set(key, data, ex=3600)
return data
finally:
r.delete(f"lock:{key}")
else:
time.sleep(0.05) # 没抢到锁:稍等后重读缓存
return get_hot_data(key)
解法二:热点数据不过期(逻辑过期)
对极热的 key 干脆不设 Redis 过期时间,把"过期时间"作为字段写在 value 里。读到"逻辑上已过期"的数据时,返回旧数据 + 异步触发一个线程去更新。用户永远能拿到数据(可能稍旧),数据库毫无压力。
选型:一般热点用互斥锁(简单、强一致);极端热点、能容忍短暂旧数据用逻辑过期。
3. 缓存雪崩(Cache Avalanche)
问题
两种起因:
- 大量 key 同一时刻过期:比如凌晨预热缓存时统一设了 3600 秒过期,第二天凌晨同一秒全体失效,请求洪水般涌向数据库
- Redis 整个挂了:所有请求 100% 打到数据库
解法
针对"同时过期":过期时间加随机数,把失效时间打散:
import random
r.set(key, value, ex=3600 + random.randint(0, 600)) # 3600~4200 秒随机
针对"Redis 挂了":
- 高可用架构:主从 + 哨兵,或集群(进阶篇会讲),Redis 挂一台自动切换
- 服务降级/熔断:数据库压力超阈值时,非核心请求直接返回默认值/错误页,保住数据库不死
- 多级缓存:本地缓存(如 Caffeine)+ Redis,Redis 挂了还有本地缓存兜底
4. 总结(面试背诵版)
| 问题 | 原因 | 主要解法 |
|---|---|---|
| 穿透 | 查不存在的数据 | ① 缓存空值(短过期)② 布隆过滤器 ③ 参数校验 |
| 击穿 | 热点 key 过期瞬间高并发 | ① 互斥锁重建 ② 逻辑过期/不过期 |
| 雪崩 | 大量 key 同时过期 / Redis 宕机 | ① 过期时间加随机 ② 高可用架构 ③ 降级熔断 + 多级缓存 |
回答面试题的套路:先说是什么(一句话)→ 为什么会发生 → 给 2~3 个解法 → 能说出解法的取舍更加分。
实战篇完结!接下来了解 Redis 如何做到高可用 👉 5.1 主从复制