缓存穿透是什么
缓存穿透指查询一个根本不存在的数据:缓存必然不命中,请求每次都落到数据库上。正常业务里个别无效查询无所谓,但被恶意构造大量随机的、格式合法的 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,删除的键无法从标准布隆中移除。数据频繁增删时通常采用定期全量重建、或按时间分片建多个布隆再轮换的方式,避免过滤器逐渐被填满导致误差率上升。