缓存空对象

问题引入

在了解了什么是缓存穿透之后,我们需要找到一种方法来防护这种攻击。

核心问题

  • 查询不存在的数据时,无法将结果写入缓存
  • 每次请求都会调用外部 API
  • 恶意攻击者可以构造大量不存在的 key

解决思路:即使数据不存在,也把”空结果”缓存起来

这就是缓存空对象方案的核心思想。

什么是缓存空对象?

缓存空对象是指:当查询的数据不存在时,在缓存中存储一个特殊的标记,表示”该数据不存在”。

核心原理

正常情况:
- 数据存在 → 缓存真实数据
- 数据不存在 → 无法缓存 → 每次都查询外部 API

缓存空对象方案:
- 数据存在 → 缓存真实数据
- 数据不存在 → 缓存空标记 → 避免重复查询

与其他方案对比

方案内存占用判断准确率适用场景实现难度
缓存空对象100%少量非法 key简单
布隆过滤器极低99%+海量数据、key 空间大中等
数据库预检查100%合法数据集合小简单

工作原理

用户请求
Redis 缓存
invalid_001 __NULL__
beijing_weather {"temp": 25}
数据库
受到保护

效果验证

实施缓存空对象方案后,系统指标明显改善:

指标攻击前被攻击时防护后
外部 API 调用失败率1%80%2%
API 响应时间200ms5000ms250ms
错误率0.1%30%0.5%

当前技术架构

练习

练习 1

缓存空对象方案中,为什么空值的过期时间要设置得比较短?

参考答案 (2 个标签)
缓存空对象 过期时间

答案

空值过期时间设置较短(如 60 秒)的原因:

  1. 数据一致性:如果数据在空值缓存期间被创建,用户会一直看到”不存在”的结果

  2. 内存效率:空值也占用内存,过长的过期时间会浪费存储空间

  3. 安全性平衡

    • 60 秒足以抵挡大部分攻击
    • 攻击者等待 60 秒后再次攻击,成本已经很高
    • 如果需要更强防护,可以结合布隆过滤器

推荐设置

  • 空值缓存:30-120 秒
  • 正常数据:根据业务设置(如 3600 秒)

练习 2

缓存空对象方案和布隆过滤器方案有什么区别?分别适用于什么场景?

参考答案 (3 个标签)
缓存空对象 布隆过滤器 方案对比

答案

维度缓存空对象布隆过滤器
内存占用中(每个空值约 90 字节)极低(10 万条约 100KB)
准确率100%99%+(存在误判)
实现难度简单中等
维护成本中(需要维护合法数据集合)
适用场景少量非法 key海量数据、key 空间大

场景选择

使用缓存空对象

  • 非法 key 数量较少(< 1 万)
  • 希望实现简单
  • 需要 100% 准确
  • 内存资源充足

使用布隆过滤器

  • 非法 key 数量巨大(> 10 万)
  • 内存资源紧张
  • 可以接受小幅误判
  • 有明确的合法数据集合

组合使用

  • 第一层:布隆过滤器拦截大部分非法请求
  • 第二层:缓存空对象处理误判和漏网之鱼

练习 3

请设计一个带有缓存空对象防护的用户查询流程,要求:

  1. 复用统一的缓存防护流程
  2. 空值缓存 60 秒,正常数据缓存 1 小时
  3. 包含错误处理
参考答案 (2 个标签)
缓存空对象 方案设计

参考答案

设计流程
练习 3
  1. 步骤 1:读取、刷新或失效缓存数据
  2. 步骤 2:写入缓存值、空值标记或热点保护状态
  3. 步骤 3:记录命中率、回源次数和异常 key 分布
  4. 步骤 4:记录命中率、回源次数和异常 key 分布
关注点:命中率、回源压力、TTL 和异常 key 处理。

关键点

  1. 复用统一的缓存防护流程,流程复用性强
  2. 空值使用特殊标记 NULL_MARKER
  3. 空值缓存时间短(60 秒)
  4. 正常数据缓存时间长(3600 秒)
  5. 完善的错误处理(Redis 连接失败、JSON 解析错误)
  6. 参数校验在查询入口完成
  7. 支持复杂数据类型(自动序列化)