缓存穿透本质是非法或已删ID高频请求导致查无结果并持续压库;需用布隆过滤器预检、空值缓存设短过期、统一key规则及跨服务事件同步来防控。

缓存穿透的本质是查不到 + 高频打穿
缓存穿透不是 Redis 本身的问题,而是业务查询逻辑和缓存策略没对齐:用户用非法 ID(比如 -1、"abc")或已删除的 ID 频繁请求,缓存里没有,DB 里也没有,每次都要穿透到数据库,压垮后端。
关键判断:如果 GET 返回 null,但 SELECT FROM db WHERE id = ? 也返回空,且这类请求占比突然升高,基本就是穿透开始了。
常见错误现象:RedisCacheManager 没配 null 值缓存、接口没做参数校验、DB 查询没加索引导致慢查被反复重试。
- 必须区分「缓存未命中」和「数据确实不存在」——前者要查 DB,后者查完就得记下来
-
SET空值时,务必设较短过期时间(如60秒),别用永不过期 - 空值不能直接存
null,得序列化成明确标识,比如"NULL"或{"exists":false}
用布隆过滤器预检非法 key(Java + RedisTemplate 场景)
布隆过滤器不是万能,但它适合在请求进缓存前快速拦截 99% 的无效 key,尤其适用于 ID 规则固定、总量可预估的场景(如订单号、用户 ID)。
注意:它有误判率,但只“误判存在”,不会“误判不存在”——所以它只能用来挡掉明显非法的请求,不能替代 DB 查询。
容易踩的坑:BloomFilter 实例没共享、没持久化、扩容后失效;或者把动态生成的模糊查询字段(如 name LIKE "%xxx%")也塞进去,完全不适用。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 初始化时用
RedisBloom(RedisBloom 模块)或客户端侧Guava BloomFilter+ 定期 dump 到SET,避免重启丢失 - key 进布隆前必须归一化:统一转小写、去空格、截断超长值,否则
"U123 "和"u123"被当成两个 key - 不要在每次请求里 new 一个
BloomFilter,要用单例或 Spring Bean 管理
空值缓存必须带过期时间,且避免大 Value 占满内存
存空值最简单,但最容易出事。很多人用 set(key, null, 60, TimeUnit.SECONDS),结果发现 Redis 里真存了个 null 字符串,后续反序列化失败;或者所有空值都设 24 小时过期,最后缓存里堆了几百万个 "NULL" 占着内存。
真实影响:内存碎片上升、INFO memory 显示 used_memory_dataset_perc 持续 >95%,触发频繁淘汰,连带正常缓存也被踢。
- 空值建议统一用轻量标记,比如字符串
"MISS",而不是对象序列化结果 - 过期时间按业务容忍度设:用户中心可用
30-60秒,商品详情可设5-10秒,别一刀切 - 配合
SCAN定期清理过期空值(如凌晨跑脚本删*:MISS),防止冷 key 滞留
微服务间空值状态不同步会导致二次穿透
一个服务查 DB 发现不存在,写了 "MISS" 到 Redis;另一个服务读缓存时却因为本地缓存(Caffeine)、或 Redis 连接池路由到从节点还没同步,又去查 DB ——这就是跨服务穿透。
这不是 Redis 配置问题,是架构层缺失一致性约定。
- 所有服务必须使用同一套缓存 key 生成规则,包括前缀、分隔符、大小写,比如统一用
"user:detail:" + userId - 空值写入后,建议发一条
cache.miss.event到消息队列,其他服务监听后主动清本地缓存(如有) - 避免在网关层做空值缓存,网关不知道业务语义,容易把
404全部当穿透处理
真正难的不是加布隆过滤器或存 "MISS",而是怎么让每个服务对“这个 key 是否该查 DB”达成一致判断。线上一旦出现穿透,第一反应不该是加缓存,而是翻日志看这些请求的原始参数长什么样——90% 的穿透,源头都在上游调用方传了脏数据。

















