首页/数据/数据库速查/空值缓存与防穿透标记实践
Redis 运维与场景124 条

空值缓存与防穿透标记实践

空值缓存是把「不存在」也变成缓存内容:实现要点=固定标记值、短 TTL、与真实值同一读取路径、更新时同步失效。

数据库速查数据更新于 2026-10-13

空值缓存的原理

缓存穿透指查询一个数据库里根本不存在的键:缓存永远不命中,每次请求都打到数据库。空值缓存把「查不到」这个结果也写进缓存,用一个不会被业务产生的标记值表示,配一个较短的 TTL。后续同样的请求在缓存层就被拦下,数据库压力随之下降。代价是读路径必须统一处理标记值,写路径必须记得清理它。

基本信息

适用平台任意 Redis 版本 + 应用侧封装
关键设计标记值、较短 TTL、统一读取路径、写后清理
TTL 建议空值比正常值短(常见 60-120 秒)
标记值选择业务绝不可能产生的字符串
配套手段参数校验、布隆过滤器、接口限流

典型使用场景

  • 按用户 ID 查询详情:大量无效 ID 来自爬虫或攻击
  • 商品编号扫描:不存在的编号被反复请求
  • 用户注册后立即查询:新建数据与空值标记的衔接
  • 对外接口防刷:用空值缓存降低穿透带来的数据库压力
  • 与限流结合:对高频无效查询做双层拦截

统一封装示例

统一封装示例
NULL = '__NULL__'          # 业务不可能产生的标记值

def cache_get(key, loader, ttl=300, null_ttl=60):
    v = redis.get(key)
    if v == NULL:
        return None          # 命中空值标记,直接返回不存在
    if v is not None:
        return v             # 命中正常缓存
    val = loader()           # 回源查询数据库
    if val is None:
        redis.set(key, NULL, ex=null_ttl)   # 短 TTL 写入空值
    else:
        redis.set(key, val, ex=ttl)
    return val

实现要点

要点做法忽略的后果
标记值选择业务不会产生的字符串真实数据被误判为不存在
TTL空值设置更短的过期时间新建数据后长时间读不到
读取路径所有读取统一走封装函数部分路径绕开标记判断导致返回错误
写路径数据创建后删除对应标记键用户注册后仍查不到自己的信息
兜底配合参数校验或布隆过滤器空值缓存写入量本身成为压力
空值缓存占用内存但成本可控:TTL 通常只有一到两分钟,只要标记值与正常值不冲突,它是一个实现简单、收益明显的防穿透手段。
对于大规模随机无效键的攻击,空值缓存会被大量写入从而占满内存:此时应先做参数合法性校验和限流,必要时用布隆过滤器在入口层拦截。

易错点

  • 标记值撞上真实数据:业务真的存了同样字符串时会被当成不存在,务必选业务不可能出现的值
  • 新建数据后不清理空值标记:注册完立刻查询仍显示不存在,用户侧体验极差
  • 空值 TTL 与正常值一样长:数据创建后要等很久才能被读到,掩盖了写路径的问题
  • 把它当成防刷的万能方案:随机键攻击下空值缓存会被写满,需要配合入口层校验与限流

数据来源与参考

  • Redis 官方文档: 使用模式
  • 缓存穿透防护实践

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌