缓存穿透本质是请求未在应用入口拦截导致高频查无数据,需分层防护:网关校验+限流、布隆过滤器(id可枚举场景)、空值缓存加随机ttl兜底。

做不到“零穿透”,但能压到接近业务可忽略的量级——关键不在堆技术,而在分层拦截和请求合法性前置。
缓存穿透的本质不是Redis问题,而是请求没被拦在应用入口
所谓“零穿透”,常被误解为让Redis自己扛住所有非法key。实际上,Redis对不存在的key只能返回nil,它不负责判断“这个key该不该存在”。真正该拦截的位置是:API网关、Spring Cloud Gateway、Nginx或Controller层的参数校验。
- 恶意ID(如
user:-1、product:999999999999)应在反序列化后立刻被@Min(1)或正则校验拒绝,不进缓存逻辑 - 高频扫描类请求(如爬虫批量试key)应通过IP+路径限流(如Sentinel QPS=5),直接
429返回 - 布隆过滤器(BloomFilter)只适合“全量已知ID空间”的场景(如用户表主键范围确定),若ID来自外部不可控输入(如第三方OAuth ID),布隆过滤器反而成瓶颈和误判源
Redisson.getLock() 不是用来防穿透的,而是防击穿的
很多人把RLock套在get(key)外层,以为能挡穿透——这是典型误用。当key根本不存在时,get()直接返回null,锁根本不会触发;只有key存在但过期/未命中时,锁才起作用。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 穿透场景:请求
user:1008611,数据库无此记录 →redisTemplate.opsForValue().get("user:1008611")返回null→ 直接查DB → 锁没机会加 - 击穿场景:热点key
product:123同时过期 → 多个线程都走到if (user == null)分支 → 此时RLock才生效,只放行一个线程查库 - 正确用法:锁应加在“查库 + 写缓存”这一整段逻辑外,且必须配合
tryLock(3, 2, TimeUnit.SECONDS)避免死等
空值缓存必须带随机偏移,否则会引发雪崩式续命
缓存空对象看似简单,但set(key, "", 60, TimeUnit.SECONDS)这种写法在高并发下极易导致“空值雪崩”:大量空key在同一秒过期,瞬间涌向DB。
- 必须加随机偏移:比如基础TTL=60s,再叠加±10s扰动,实际过期时间落在50~70s之间
- 空值内容不能是空字符串,而应是明确标识(如
"NULL_V1"),便于后续灰度剔除或监控统计 - 需配套清理机制:用
redis-cli --scan --pattern "user:*" | grep NULL_V1定期抽样检查,防止空值堆积占用内存
内存网格(IMDG)如Redisson本身不解决穿透,但能降低拦截成本
Redisson的RMapCache或RLocalCachedMap本质是本地缓存+远程兜底,它对穿透的贡献在于:把部分“本该打到Redis的无效请求”,提前在JVM内拦截掉。
- 例如配置
localCacheOptions.entryExpireAfterWrite(10, TimeUnit.SECONDS),即使Redis里没key,本地Map也会记住“user:999999查过且为空”,10秒内不再发请求 - 但注意:
RLocalCachedMap的本地缓存默认不共享,多实例部署时各节点本地缓存独立,无法全局拦截——此时仍需布隆过滤器或网关层统一校验 - 真正提升的是“重复无效请求”的响应速度,从RT 2ms(网络+Redis)降到0.05ms(纯内存),但首次穿透依然发生
穿透防控最硬的那道墙,永远在离用户最近的地方:网关做合法性校验、限流、WAF规则;中间件层做布隆过滤(仅限ID可枚举场景);应用层做空值+随机TTL兜底。把Redis当成最后一道缓冲,而不是第一道防线。










