Redis 缓存穿透、击穿与雪崩:原理与解决方案
用 Redis 做缓存几乎是后端项目的标配,但缓存用不好,反而会在高并发时把数据库拖垮。缓存穿透、击穿、雪崩是最常被问到、也最容易踩坑的三个问题,这篇文章把它们捋清楚。
1. 缓存穿透:查询一个根本不存在的数据
现象:请求查询一个数据库里也不存在的 key,缓存里自然也没有,每次请求都要打到数据库上。如果有人恶意用不存在的 id 疯狂请求,数据库很容易被打垮。
解决方案:
- 缓存空值:查询结果为空时,也把这个空结果缓存起来(设置一个较短的过期时间),避免同一个不存在的 key 反复穿透到数据库。
value = redis_client.get(key)
if value is None:
value = query_from_db(key)
if value is None:
redis_client.setex(key, 60, '') # 缓存空值,60 秒过期
else:
redis_client.setex(key, 3600, value)
- 布隆过滤器:提前把所有合法的 key 放进布隆过滤器,请求先经过过滤器判断,不在里面的 key 直接拒绝,连缓存都不用查。
2. 缓存击穿:一个热点 key 突然过期
现象:某个访问量极高的 key(比如首页热门文章)缓存过期的瞬间,大量并发请求同时穿透到数据库,短时间内造成数据库压力骤增。
解决方案:
- 加互斥锁:缓存失效时只让一个请求去查数据库并重建缓存,其他请求等待或读旧值。
lock_key = f'lock:{key}'
if redis_client.set(lock_key, '1', nx=True, ex=10):
try:
value = query_from_db(key)
redis_client.setex(key, 3600, value)
finally:
redis_client.delete(lock_key)
else:
time.sleep(0.05)
value = redis_client.get(key) # 重试读取
- 热点数据不过期:对特别热门的 key,直接不设置过期时间,靠后台任务异步更新。
3. 缓存雪崩:大量 key 集中在同一时刻过期
现象:如果给一批 key 设置了相同的过期时间,到了那个时间点,缓存集体失效,请求瞬间全部涌向数据库。
解决方案:
- 过期时间加随机值:避免大量 key 在同一时刻集中失效。
import random
expire_time = 3600 + random.randint(0, 300) # 基础 1 小时 + 0~5 分钟随机抖动
redis_client.setex(key, expire_time, value)
- 多级缓存:本地缓存(如内存缓存)+ Redis 缓存,即使 Redis 整体不可用,也能兜底一部分请求。
- 限流 + 降级:数据库压力过大时,通过限流保护数据库,必要时返回降级数据(比如旧数据或默认值)。
小结
| 问题 | 触发场景 | 核心思路 |
|---|---|---|
| 缓存穿透 | 查询不存在的数据 | 缓存空值 / 布隆过滤器 |
| 缓存击穿 | 单个热点 key 过期 | 互斥锁 / 热点不过期 |
| 缓存雪崩 | 大量 key 同时过期 | 过期时间加随机 / 多级缓存 |
这三个问题看着相似,但触发条件和解法都不一样,面试也经常放在一起问。理解清楚各自的场景,才能在实际项目里对症下药。
评论区