缓存穿透基础
那是一个普通的周二下午。
突然,监控系统发出警报:
- 警告:外部 API 调用失败率飙升到 80%
- 警告:API 响应时间超过 5 秒
- 警告:错误率上升到 30%
我冲回电脑前,查看日志,发现了异常:
2025-03-15 14:23:15 - GET /weather?city=invalid_city_1 → 404
2025-03-15 14:23:15 - GET /weather?city=invalid_city_2 → 404
2025-03-15 14:23:16 - GET /weather?city=fake_city_999 → 404
...
有人在恶意攻击我的 API!
缓存穿透攻击流程
恶意攻击者
构造大量不存在 key缓存层(Redis)
invalid_001不存在invalid_002不存在invalid_003不存在外部 API
重复查询穿透至上游服务GET /api/weather?city='invalid_001'200msGET /api/weather?city='invalid_002'195msGET /api/weather?city='invalid_003'210ms缓存穿透是指:
- 查询一个不存在的数据
- 缓存层没有(因为数据不存在)
- 请求直接穿透到外部 API
- 如果大量这样的请求同时到来,外部 API 可能被压垮
关键特征:
- 查询的数据在缓存和外部 API 中都不存在
- 每个请求都会访问外部 API
- 恶意攻击者可以利用这个漏洞
练习 1
缓存穿透的根本原因是什么?它与缓存击穿、缓存雪崩有什么区别?
解题思路:
先定位根本原因:查的数据在缓存和外部 API 里都不存在,所以永远缓存不住、每次都回源。再用「数据是否存在、key 数量、并发特征」与击穿、雪崩对照。
详细步骤:
最终答案:
缓存穿透的根本原因:查询的数据在缓存和外部 API 中都不存在。
具体分析:
- 请求的 key 在缓存中不存在
- 请求的 key 在外部 API 中也不存在(返回 404)
- 因此无法将结果写入缓存
- 每次请求都会调用外部 API
恶意利用: 攻击者可以构造大量不存在的 key,使所有请求都调用外部 API,导致 API 限流或服务不可用。
与缓存击穿、缓存雪崩的区别:
| 问题 | 根本原因 | 数据是否存在 | 典型场景 |
|---|---|---|---|
| 缓存穿透 | 查询不存在的数据 | 不存在 | 恶意攻击、参数错误 |
| 缓存击穿 | 热点数据过期 | 存在 | 热点新闻、明星数据 |
| 缓存雪崩 | 大量数据同时过期 | 存在 | 批量导入、定时任务 |
练习 2
假设你的 API 遭受了缓存穿透攻击,每秒有 1000 个请求查询不存在的数据,外部 API 的限流是每秒 100 次。请分析:
- 如果不防护,会发生什么?
- 如果使用缓存空对象方案(空值缓存 60 秒),能减少多少 API 调用?
- 如果使用布隆过滤器(误判率 0.1%),能减少多少 API 调用?
解题思路:
这题要算的是量:先算不防护时回源请求数与 API 限流阈值的差距,再分别代入空值缓存和布隆过滤器,得出各自的调用削减比例。
详细步骤:
最终答案:
1. 不防护的情况:
- 每秒 1000 个请求都调用外部 API
- API 限流是每秒 100 次
- 结果:900 个请求失败(90% 失败率)
- 系统表现:大量错误,用户体验极差
2. 使用缓存空对象方案:
- 第 1 秒:1000 个请求都调用 API(100 成功 + 900 被限流)
- 第 2-60 秒:所有请求从缓存返回,0 次 API 调用
- 第 61 秒:如果攻击持续,重复上述循环
API 调用减少:
- 无防护:1000 请求/秒 × 60 秒 = 60,000 次
- 有防护:1000 请求(第 1 秒)+ 0(第 2-60 秒)= 1,000 次
- 减少:59,000 次(约 98.3%)
3. 使用布隆过滤器:
- 误判率 0.1%,意味着 0.1% 的不存在数据会被误判为可能存在
- 每秒 1000 个请求,布隆过滤器拦截 999 个
- 只有 1 个请求(0.1%)会穿透到 API
API 调用减少:
- 无防护:1000 请求/秒 × 60 秒 = 60,000 次
- 有布隆过滤器:1 请求/秒 × 60 秒 = 60 次
- 减少:59,940 次(约 99.9%)
结论:
- 缓存空对象:减少约 98% 的 API 调用,简单有效
- 布隆过滤器:减少约 99.9% 的 API 调用,防护更强
练习 3
在什么情况下应该选择缓存空对象方案?在什么情况下应该选择布隆过滤器方案?
解题思路:
两种方案的取舍要落到几个可量化的条件上:非法 key 规模、内存预算、能否接受误判、实现与维护成本,以及数据一致性的要求。
详细步骤:
最终答案:
选择缓存空对象方案的情况:
✅ 非法 key 数量较少(< 1 万) ✅ 希望实现简单,快速上线 ✅ 需要 100% 准确,不能有误判 ✅ 内存资源充足 ✅ 数据一致性要求高(不能有误判)
典型场景:
- 小型 API 服务
- 内部系统
- 参数格式固定的场景
- 可以接受短暂的数据不一致
选择布隆过滤器方案的情况:
✅ 非法 key 数量巨大(> 10 万) ✅ 内存资源紧张 ✅ 可以接受小幅误判(0.1% 左右) ✅ 有明确的合法数据集合 ✅ 需要极高的拦截率
典型场景:
- 大型互联网应用
- 面向公网的 API
- 用户 ID、商品 ID 等海量 key
- 对性能要求极高的场景
组合使用的情况:
✅ 防护要求极高的场景 ✅ 不能容忍单点失效 ✅ 资源充足,追求极致性能
组合架构:
请求 → 布隆过滤器(第一层)→ 缓存空对象(第二层)→ 外部 API
拦截 99.9% 拦截剩余 0.09%发现内容有问题?
告诉我们哪里需要改进。
