缓存穿透的本质是查不到且高频请求击穿数据库;可通过set+expire缓存空值(如empty:item:9999999)或redisbloom布隆过滤器拦截,二者均需写db时同步更新标识。

缓存穿透的本质是查不到 + 高频请求击穿数据库
Redis 本身没有内置“拦截器”概念,所谓“在 Redis 端实现拦截”实际是指:利用 Redis 的数据结构和原子操作,在请求到达后端数据库前,就识别并阻断那些注定查不到的恶意/异常 key。关键不是让 Redis 主动过滤,而是让业务层借助 Redis 的能力快速判别——比如用 BloomFilter(需外部集成)、SET 记录空值、或用 EXPIRE 控制空结果缓存时效。
用 SET + EXPIRE 缓存空值是最直接可行的方案
当查询一个不存在的 key(如商品 ID item:9999999),DB 返回 null,此时不丢弃结果,而是往 Redis 写入一个占位符,比如 empty:item:9999999,并设置较短过期时间(如 2 分钟):
SET empty:item:9999999 1 EX 120
后续请求先检查这个空值标记,命中则直接返回空,不再查 DB。注意几个实操要点:
- 空值 key 要有统一前缀(如
empty:),避免和业务 key 冲突 - 过期时间不宜过长(防止真实数据上架后长期不可见),也不宜过短(增加 Redis 压力)
- 写空值必须用原子命令组合,推荐
SET key value EX seconds,避免SET+EXPIRE分离导致竞态 - 如果业务 key 本身含特殊字符(如冒号、空格),空值 key 也要做同样编码,否则匹配失效
用 BF.EXISTS + BF.ADD 需要 RedisBloom 模块支持
布隆过滤器适合海量 key 场景(如千万级用户 ID),但 Redis 默认不带,得额外加载 RedisBloom 模块。启用后,所有写入 DB 的合法 key 都要同步调用 BF.ADD 注册;读请求先走 BF.EXISTS 判断是否存在,BF.EXISTS 返回 0 就代表“一定不存在”,可立即拒绝。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
容易踩的坑:
- 模块未安装时执行
BF.EXISTS会报错ERR unknown command,必须提前检测兼容性 - 布隆过滤器有误判率(false positive),但不会漏判(false negative);所以它只能用于“快速拒绝”,不能替代 DB 查询
- 扩容时
BF.RESERVE的capacity和error_rate必须预估准确,改了就得重建,无法动态伸缩
不要依赖 KEYS 或 SCAN 做实时拦截
有人想用 SCAN 扫描所有 key 判断是否“已知”,这在生产环境是危险操作:
-
KEYS *会阻塞 Redis 单线程,QPS 稍高就雪崩 -
SCAN虽不阻塞,但全量扫描耗 CPU,且结果非实时(期间 key 可能被删或新增) - 无法区分“真不存在”和“刚过期还没来得及清理”的空状态
- 没有任何原子性保证,不能作为拦截依据
真正可靠的拦截,永远基于写时注册 + 读时查表,而不是读时反向推导。
空值缓存和布隆过滤器不是二选一,高频固定集合(如黑名单)用 BF,低频动态场景(如新上架商品)用 SET + EXPIRE;两者都绕不开“写 DB 时同步更新缓存标识”这个动作——漏掉这步,再好的结构也形同虚设。










