缓存穿透本质是无效key反复查询导致db压力激增,可通过布隆过滤器(拦截预知无效key)和缓存空对象(应对未知key)组合防御,辅以格式校验、随机ttl、限流等措施分层防护。

缓存穿透的本质是查不到还反复查
缓存穿透不是 Redis 自身的问题,而是业务层没做好请求过滤:攻击者或异常流量持续查询 id=999999999 这类根本不存在的 key,Redis 查不到就打到 DB,DB 压力陡增甚至拖垮。
关键判断点:如果监控发现 redis.keyspace_hits 很低、redis.keyspace_misses 持续飙升,同时 DB 的慢查询里大量出现 SELECT ... WHERE id = ? 返回空结果,基本就是穿透了。
用布隆过滤器拦截无效 key(推荐用于写少读多、key 空间可预估的场景)
布隆过滤器(Bloom Filter)是个空间效率极高的概率型数据结构,能快速告诉你“这个 key 绝对不存在”或“可能存在”。它不存真实数据,只存哈希签名,所以内存占用小、查询 O(1)。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- Redis 6.2+ 原生支持
BF.RESERVE、BF.ADD、BF.EXISTS,但生产环境更常用客户端侧布隆过滤器(如 Java 的guava BloomFilter或redisson封装的分布式布隆),避免每次查询都走一次 Redis 网络往返 - 初始化时要把所有「合法 key」(比如商品 ID 全集、用户 UID 白名单)一次性加载进布隆过滤器;增量更新需同步调用
BF.ADD或客户端bloomFilter.put() - 查询前先过布隆:若
bloomFilter.mightContain("user:123456")返回false,直接返回空,不查 Redis 也不查 DB;若返回true,再走正常缓存逻辑 - 注意误判率:默认 3% 误判率对应约 9.6 bits/key,如果 key 总量 1 亿,布隆过滤器约占 115MB 内存;调低误判率会显著增加内存,别盲目设成 0.001%
缓存空对象(适合 key 不确定、无法预加载的场景)
对 DB 查询返回空的结果,也往 Redis 里写一个带短 TTL 的空值(比如 SET user:999999999 "null" EX 60),让后续相同请求直接命中缓存,避开 DB。
- 必须设 TTL,否则恶意构造海量不存在 key 会把 Redis 写满;60 秒是常见起点,可根据业务容忍度调整
- 空值内容别用纯字符串
"null",建议统一用 JSON 格式如{"code":404,"data":null},避免和业务真实返回"null"字符串混淆 - 要配合应用层做「空值穿透防护」:比如 MyBatis-Plus 的
lambdaQuery().eq(User::getId, id).one()返回null后,手动执行redisTemplate.opsForValue().set("user:" + id, EMPTY_JSON, 60, TimeUnit.SECONDS) - 警惕雪崩风险:如果大量空 key 同时过期,可能引发瞬时穿透,可用随机 TTL(如
60 + random(30))缓解
两种方案不是二选一,而是分层防御
布隆过滤器防的是「明目张胆的乱刷」,空对象防的是「漏网之鱼」和「新增 key 的冷启动阶段」。线上建议组合使用:
- 接入层(Nginx / API 网关)加简单正则校验,比如
id必须是 6~12 位数字,直接拦掉格式非法请求 - 应用层第一道用布隆过滤器(本地 or Redis 模块),拦截 95% 以上无效 key
- 布隆放行后,查 Redis → 查 DB → 若 DB 为空,缓存空对象并设随机 TTL
- 定期用
SCAN配合TYPE清理长期未访问的空 key,防止缓存膨胀
真正难处理的是「ID 规则被猜出且分布稀疏」的场景,比如订单号按时间戳生成,攻击者扫 20240501000001 ~ 20240501001000,这时候布隆预热成本高,空对象又容易被绕过——得结合限流(redis-cell)和业务逻辑加固,比如订单详情页强制登录态、加图形验证码。










