导航菜单

缓存穿透基础

一个真实的故事

那是一个普通的周二下午。

突然,监控系统发出警报:

⚠️ 警告:外部 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' 200ms
GET /api/weather?city='invalid_002' 195ms
GET /api/weather?city='invalid_003' 210ms
限流状态:
已触发
外部 API 触发限流!

缓存穿透是指:

  • 查询一个不存在的数据
  • 缓存层没有(因为数据不存在)
  • 请求直接穿透到外部 API
  • 如果大量这样的请求同时到来,外部 API 可能被压垮

关键特征

  • 查询的数据在缓存和外部 API 中都不存在
  • 每个请求都会访问外部 API
  • 恶意攻击者可以利用这个漏洞

攻击原理

正常请求 vs 穿透攻击

正常请求流程

用户请求 → 查缓存(命中)→ 直接返回

    查缓存(未命中)→ 调用外部 API → 写入缓存 → 返回

穿透攻击流程

恶意请求 1 → 查缓存(无)→ 调用外部 API(404)→ 返回空
恶意请求 2 → 查缓存(无)→ 调用外部 API(404)→ 返回空
恶意请求 3 → 查缓存(无)→ 调用外部 API(404)→ 返回空
...

问题所在

  • 因为数据不存在,所以无法写入缓存
  • 每个请求都直接调用外部 API
  • 攻击者可以构造大量不存在的 key
  • 外部 API 有调用频率限制,容易被触发

真实攻击案例

# 攻击者构造的恶意请求
GET /api/weather?city=invalid_001
GET /api/weather?city=invalid_002
GET /api/weather?city=invalid_003
...
GET /api/weather?city=invalid_99999

# 每秒 1000 个这样的请求
# 每个请求都调用外部 API
# 很快触发了 API 限流

当前技术架构

每个请求都直接调用外部 API,响应慢
客户端
用户请求 200 个开发者
应用服务
应用服务器 处理请求
缓存层 / 外部服务
外部天气 API 响应时间 2 秒
引入 Redis 缓存,大幅降低响应时间
客户端
用户请求 高并发访问
应用服务
应用服务器 处理请求
缓存层 / 外部服务
Redis 缓存 1 小时过期
外部天气 API 响应时间 2 秒
增加空值缓存,防止缓存穿透
客户端
用户请求 包含恶意请求
应用服务
应用服务器 参数校验
缓存层 / 外部服务
Redis 缓存 包含空值缓存
外部天气 API 有调用限制
使用布隆过滤器提前过滤无效请求
客户端
用户请求 包含恶意请求
应用服务
应用服务器 布隆过滤器校验
缓存层 / 外部服务
Redis 缓存 包含空值缓存
外部天气 API 有调用限制
使用互斥锁防止缓存击穿
客户端
用户请求 高并发访问
应用服务
应用服务器 互斥锁控制
缓存层 / 外部服务
Redis 缓存 热点 key 防护
外部天气 API 有调用限制
熔断器 + 降级策略应对缓存雪崩
客户端
用户请求 高并发访问
应用服务
应用服务器 熔断器 + 降级
缓存层 / 外部服务
Redis Sentinel 主从高可用
外部天气 API 有调用限制
多地域部署的高可用 API 平台
客户端
用户请求 10 万用户
应用服务
应用服务器集群 26 台,3 地域
缓存层 / 外部服务
Redis 集群 6 主 6 从
MySQL 主从 1 主 5 从
消息队列 RabbitMQ 3 台

练习

练习 1

缓存穿透的根本原因是什么?它与缓存击穿、缓存雪崩有什么区别?

参考答案 (2 个标签)
缓存穿透 基础概念

答案

缓存穿透的根本原因查询的数据在缓存和外部 API 中都不存在

具体分析:

  1. 请求的 key 在缓存中不存在
  2. 请求的 key 在外部 API 中也不存在(返回 404)
  3. 因此无法将结果写入缓存
  4. 每次请求都会调用外部 API

恶意利用: 攻击者可以构造大量不存在的 key,使所有请求都调用外部 API,导致 API 限流或服务不可用。

与缓存击穿、缓存雪崩的区别

问题根本原因数据是否存在典型场景
缓存穿透查询不存在的数据不存在恶意攻击、参数错误
缓存击穿热点数据过期存在热点新闻、明星数据
缓存雪崩大量数据同时过期存在批量导入、定时任务

练习 2

假设你的 API 遭受了缓存穿透攻击,每秒有 1000 个请求查询不存在的数据,外部 API 的限流是每秒 100 次。请分析:

  1. 如果不防护,会发生什么?
  2. 如果使用缓存空对象方案(空值缓存 60 秒),能减少多少 API 调用?
  3. 如果使用布隆过滤器(误判率 0.1%),能减少多少 API 调用?
参考答案 (2 个标签)
缓存穿透 效果分析

答案

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

在什么情况下应该选择缓存空对象方案?在什么情况下应该选择布隆过滤器方案?

参考答案 (2 个标签)
缓存穿透 方案选择

答案

选择缓存空对象方案的情况

✅ 非法 key 数量较少(< 1 万) ✅ 希望实现简单,快速上线 ✅ 需要 100% 准确,不能有误判 ✅ 内存资源充足 ✅ 数据一致性要求高(不能有误判)

典型场景

  • 小型 API 服务
  • 内部系统
  • 参数格式固定的场景
  • 可以接受短暂的数据不一致

选择布隆过滤器方案的情况

✅ 非法 key 数量巨大(> 10 万) ✅ 内存资源紧张 ✅ 可以接受小幅误判(0.1% 左右) ✅ 有明确的合法数据集合 ✅ 需要极高的拦截率

典型场景

  • 大型互联网应用
  • 面向公网的 API
  • 用户 ID、商品 ID 等海量 key
  • 对性能要求极高的场景

组合使用的情况

✅ 防护要求极高的场景 ✅ 不能容忍单点失效 ✅ 资源充足,追求极致性能

组合架构

请求 → 布隆过滤器(第一层)→ 缓存空对象(第二层)→ 外部 API
       拦截 99.9%           拦截剩余 0.09%

搜索