缓存穿透是什么
缓存穿透指请求查询根本不存在的数据:缓存永远不命中,每次都要落到数据库,恶意构造大量不存在的 ID 时数据库会被压垮。它与击穿(单个热点键失效)和雪崩(大面积同时失效)的区别在于对象——穿透面对的是本来就不存在的数据,因此再大的缓存容量也挡不住。
基本信息
| 典型特征 | 缓存命中率低而数据库 QPS 异常升高 |
|---|---|
| 主要对策 | 空值缓存、布隆过滤器、前置参数校验与限流 |
| 空值标记 | 用不会与真实值冲突的字符串,TTL 要短 |
| 布隆过滤器 | 判断「一定不存在」可靠,判断「可能存在」有误判 |
| 常见来源 | 爬虫扫号、账号枚举、非法参数传入 |
典型使用场景
- 商品详情接口:被扫不存在的商品 ID 时长期打库
- 用户查询接口:被遍历用户 ID 时回源压力骤增
- 对外 API:第三方传入大量非法或越界参数
- 注册与登录:账号枚举攻击会查询不存在的用户名
- 缓存命中率监控:miss 数量异常升高时先怀疑穿透
- 批量任务:导入前未校验的 ID 直接进入查询链路
空值缓存示例
空值缓存示例
v = redis.get('user:%s' % uid)
if v == '__NULL__':
return None # 空值也缓存,短 TTL,避免反复回源
if v is None:
row = db.query(uid)
if row is None:
redis.set('user:%s' % uid, '__NULL__', ex=60) # 60 秒防穿透
return None
redis.set('user:%s' % uid, json.dumps(row), ex=300)
return json.loads(v)
# 前置校验放在最外面:格式、范围、长度都不合法就直接拒绝
# 命中率监控:miss 数量异常升高时先看是否有不存在的 ID 被批量请求三种缓存异常对照
| 异常 | 触发条件 | 主要对策 |
|---|---|---|
| 穿透 | 查询的数据在库中也不存在 | 空值缓存加布隆过滤器加参数校验 |
| 击穿 | 单个热点键失效瞬间并发回源 | 互斥重建或逻辑过期 |
| 雪崩 | 大批键同时失效或缓存层不可用 | TTL 抖动、多级缓存与回源限流 |
空值缓存的标记值要选不可能与真实值冲突的字符串(如 __NULL__),并统一封在数据访问层,别散落业务代码。
布隆过滤器只能给出「一定不存在」与「可能存在」两种结论,且标准实现不支持删除元素;新增数据要同步写入过滤器,重建前先评估时间与内存成本。