缓存穿透是恶意查询不存在key导致请求直达数据库并压垮DB的问题;需用布隆过滤器预判绝对不存在的key,并对空值缓存设置随机过期时间以避免雪崩。

缓存穿透是什么,为什么它会直接打垮数据库
缓存穿透不是性能问题,是攻击面问题:当大量请求查询 null 或根本不存在的 key(比如恶意构造的 user:id:999999999),Redis 查不到就穿透到 DB,DB 瞬间被压垮。关键在于——这些 key 从不写入缓存,所以每次都是“空查 + DB 负载”。
典型现象:Redis get 返回 null,但日志里 DB 的 SELECT 却高频执行;监控看到 DB QPS 暴涨,而 Redis hit rate 接近 100%(因为全是 miss,没得 hit)。
用布隆过滤器预判“key 绝对不存在”
布隆过滤器(Bloom Filter)是空间换时间的方案,适合做“存在性快速否定”。它不能 100% 确认存在,但能 100% 确认不存在——这正是防御穿透的核心需求。
- Spring Boot 中推荐用
RedisBloom模块(需 Redis 服务启用redisbloom模块)或客户端侧用guava+ 本地布隆过滤器(适合 key 总量可控、更新不频繁的场景) - 初始化时把所有合法 key(如用户 ID、商品 SKU)批量塞进布隆过滤器;新增 key 时同步更新(注意分布式一致性)
- 查询前先调
bloom.exists("user:id:123"),返回false就直接返回空,不查 Redis 也不查 DB - 注意误判率:默认 3% 误判率会导致 3% 的真实 key 被当成不存在,建议设为
0.01,代价是内存翻倍
空值缓存(Cache Null)必须带随机过期时间
对查不到的 key,写入一个占位值(如 "null" 或空 JSON "{}")并缓存,避免重复穿透。但若所有空值统一设固定 TTL(比如 5 分钟),会引发“缓存雪崩式重击”——大量空值同时过期,瞬间涌向 DB。
- 写空值时用随机 TTL:比如基础 2 分钟,再加
Random.nextInt(60)秒,范围控制在 120–180 秒之间 - 空值内容建议用轻量标识,如
"__NULL__",避免和业务真实数据混淆 - 必须配合布隆过滤器使用:否则布隆漏掉的非法 key 会反复写空值,浪费 Redis 内存
- 注意序列化:若用
StringRedisTemplate,确保null值被序列化为字符串而非抛异常
@Cacheable 不能直接防穿透,要重写逻辑
Spring 的 @Cacheable 默认只缓存方法返回非 null 结果,遇到 DB 返回 null 就不会写缓存——这恰恰放行了穿透请求。
- 别依赖
@Cacheable(key = "#id", unless = "#result == null"),它不解决空值不缓存的问题 - 正确做法是手写 service 方法:先查 Redis → 若为
"__NULL__"直接 return;若为null,再查 DB;DB 也为空则写"__NULL__"+ 随机 TTL - 如果坚持用注解,可自定义
CacheResolver或继承RedisCacheManager,重写getMissingCache和写入逻辑,但复杂度陡增 - 切记:所有空值写入必须走
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS),别用boundValueOps,后者不支持随机 timeout
真正难的不是代码怎么写,而是布隆过滤器的初始加载时机、key 集合的完整性保障、以及空值与布隆双层校验的时序一致性——这些点一旦出错,防护就形同虚设。











