首页/数据/Redis 进阶速查/缓存穿透:空值缓存与布隆过滤器
数据结构与选型42 条

缓存穿透:空值缓存与布隆过滤器

查询不存在的数据会每次都打到数据库:把空结果短暂缓存(几十秒),或在入口用布隆过滤器判断键是否可能存在,拦住无效请求。

Redis 进阶速查数据更新于 2026-10-13

缓存穿透是什么

缓存穿透指查询一个根本不存在的数据:缓存必然不命中,请求每次都落到数据库上。正常业务里个别无效查询无所谓,但被恶意构造大量随机的、格式合法的 ID 时,数据库会承受远高于正常的查询压力。两种常规防线:把「查不到」这个结果也短暂缓存下来,让后续相同请求在缓存层就被挡住;或在入口放置布隆过滤器,先用低成本判断键是否可能存在,不可能存在的请求直接拒绝。

基本信息

空值缓存SET key '' EX 60,用很短的 TTL 缓存查不到的结果
布隆过滤器BF.ADD / BF.EXISTS / BF.RESERVE,需 RedisBloom 模块
误判方向只会假阳性(说不存在一定不存在),不会漏判
本地方案进程内布隆加定期重建,避免每次请求都走网络
配套手段入口限流、参数合法性校验、异常查询监控告警

典型使用场景

  • 商品详情与用户主页:ID 递增且可被枚举,容易被构造大量不存在的请求
  • 优惠券与活动页:活动链接被批量探测,空查询比例异常升高
  • 短链与邀请码:编码空间大,无效编码的查询需要在上游就被拦住
  • 内部接口防刷:对外暴露的查询接口先做参数校验与限流,再进入缓存层
  • 热点数据的负缓存:既不存在的键,也不希望每次都查库确认

两种防线写法

两种防线写法
redis-cli SET user:999999 '' EX 60          # 空值缓存:短 TTL,标识查不到
redis-cli GET user:999999                   # 返回空串,业务侧直接判为不存在
redis-cli BF.RESERVE users:bloom 0.01 1000000   # 期望误差率与预估容量
redis-cli BF.ADD users:bloom user:1001      # 写入真实存在的键
redis-cli BF.EXISTS users:bloom user:999999 # 返回 0 表示一定不存在,直接拦截
redis-cli BF.EXISTS users:bloom user:1001   # 返回 1 表示可能存在,放行到缓存层

两种方案对比

维度空值缓存布隆过滤器
实现成本低,复用现有缓存读写路径需要额外组件或模块,需维护数据同步
内存占用随无效键数量增长,可被灌满按容量与误差率预估,固定且很小
精确性精确,明确标识该键不存在有假阳性,可能放行部分无效请求
删除支持支持,正常删键即可标准布隆不支持删除,需重建或改用计数布隆
适用位置缓存写入路径请求入口,先于缓存查询
空值缓存的 TTL 要短:几秒到几十秒即可,既能挡住重复的无效请求,又能在数据真正被创建后很快恢复正常。TTL 设得过长会在「先查后建」的业务里造成假不存在,用户会看到自己刚创建的对象查不到。
布隆过滤器的维护成本在同步上:新增的键要及时 BF.ADD,删除的键无法从标准布隆中移除。数据频繁增删时通常采用定期全量重建、或按时间分片建多个布隆再轮换的方式,避免过滤器逐渐被填满导致误差率上升。

易错点

  • 空值缓存不设短 TTL 或被随机 ID 灌满,缓存内存被无效键占据,反而挤掉了真实热点数据的空间
  • 用布隆过滤器的假阳性当作故障:误判为存在时会放行到缓存层,这是设计内行为,需要用兜底查询与限流配合而不是要求它绝对精确
  • 布隆过滤器未随数据更新,新创建的合法对象被判定为不存在而直接拒绝,业务侧表现为「刚下单的商品打不开」
  • 只做缓存侧防护而不做入口校验与限流,攻击者换成其他参数组合即可绕过,多层防护必须一起上

数据来源与参考

  • Redis 官方文档:RedisBloom 模块(BF.ADD/BF.EXISTS)
  • Redis 官方文档:缓存穿透防护思路
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌