Redis 缓存穿透击穿雪崩

加了缓存后数据库压力骤降,但缓存也有三种“被击穿”的典型故障:穿透(查根本不存在的数据)、击穿(热点 key 过期瞬间被打爆)、雪崩(大量 key 同时失效压垮数据库)。三者名字像、成因不同、解法也不同,本章逐个拆解。

缓存穿透:查不存在的数据

穿透/击穿/雪崩对比 穿透指请求的 key 在缓存和数据库里都不存在,缓存永远拦不住,每次请求都打到数据库,常见于恶意攻击或非法参数。

127.0.0.1:6379> GET user:999999      # 不存在的 id,缓存 miss
# 输出:(nil)
127.0.0.1:6379> GET user:999999      # 每次都 miss,每次都查库
# 输出:(nil)

三种常用解法,可叠加使用:

  • 参数校验:id 格式、范围不合法直接拒绝,拦截大部分攻击请求。
  • 缓存空值:查不到也写入缓存,值为空占位,TTL 设短(如 60 秒),避免恶意 key 反复穿透:
127.0.0.1:6379> SET user:999999 "" EX 60 NX    # 空值也缓存 60 秒
# 输出:OK
  • 布隆过滤器:启动时把全部合法 id 装入过滤器,请求先过过滤器,判定“不存在”直接返回。Redis 可用 RedisBloom 模块,也可在应用层用 Guava 等实现:
127.0.0.1:6379> BF.ADD userFilter 10001       # 合法 id 先登记(需 RedisBloom)
# 输出:(integer) 1
127.0.0.1:6379> BF.EXISTS userFilter 999999
# 输出:(integer) 0        # 0 表示一定不存在,直接拦截

缓存击穿:热点 key 过期的瞬间

击穿针对单个热点 key:它正被高并发访问时恰好过期,一瞬间所有请求同时 miss、同时打到数据库。解决思路是“同一时刻只允许一个请求回源重建”:

  • 互斥锁:查缓存 miss 后先抢锁,抢到者查库回填,其余线程短暂等待后重查缓存:
# 流程示意:需 import redis, time 并实现 query_db(放入 main 或函数中运行)
r = redis.Redis(host="127.0.0.1", port=6379)
lock_ok = r.set("lock:hot", "1", nx=True, ex=10)   # 抢锁成功才回源
if lock_ok:
    data = query_db("hot")
    r.setex("cache:hot", 300, data)                 # 重建并回填
    r.delete("lock:hot")                            # 释放锁
else:
    time.sleep(0.1)                                 # 没抢到,稍等重试
  • 逻辑过期:不给热点 key 设物理 TTL,而在 value 里存“过期时间戳”,读取时发现逻辑过期,先返回旧值,再由后台线程异步重建,用户几乎无感知,适合不要求绝对新鲜的场景。

缓存雪崩:大量 key 同时失效

雪崩的规模大一个量级:缓存中大批 key 在同一时刻过期,或整个缓存实例宕机重启后缓存为空,所有流量瞬间涌向数据库。治理思路是“分散失效时间 + 挡住瞬时流量”:

  • 过期时间随机化:固定 TTL 上加随机偏移,避免集体过期:
# 需要 redis-py:pip install redis
import random, redis
r = redis.Redis(host="127.0.0.1", port=6379)
r.setex("news:1", 3600 + random.randint(0, 600), "标题")   # 1 小时 + 0~10 分钟随机
  • 多级缓存:Redis 前再放一层本地缓存(如 Caffeine/进程内 map),Redis 抖动时由本地缓存顶住。
  • 限流与降级:数据库前加限流,超阈值直接降级返回提示,保护数据库不死。
  • 高可用:集群 + 主从 + 持久化,避免宕机后缓存“全空”直接重启。

三者对比速查

场景特征查库根因核心解法
穿透数据根本不存在永远 miss参数校验、空值缓存、布隆过滤器
击穿单个热点 key 过期瞬间集体 miss互斥锁、逻辑过期
雪崩大批 key 同时失效集体 missTTL 随机化、多级缓存、限流降级

小结:穿透是“查不存在”,用布隆过滤器或空值缓存挡住;击穿是“热点过期瞬间”,用互斥锁或逻辑过期;雪崩是“集体失效”,靠 TTL 随机化、多级缓存与限流降级。它们都要求缓存之外必须有兜底,且数据库侧限流永远值得做。

笔记加载中…