缓存穿透是指查询根本不存在的数据,导致请求既不命中Redis也不命中数据库,持续直击数据库造成压力剧增甚至宕机;典型场景包括恶意构造负数ID、随机字符串等,防御需优先在接口层校验参数,并结合布隆过滤器与空值缓存。

缓存穿透:查的是根本不存在的数据
缓存穿透的本质是「无效查询压垮数据库」——请求的 key 既不在 Redis 中,也不在数据库中,但被高频发起(比如恶意构造的负数 id、随机字符串、已删除数据的残留 ID)。这类请求绕过缓存直击 DB,命中率趋近于 0,DB QPS 突增,CPU 很快拉满。
典型现象:redisTemplate.opsForValue().get("user:999999999") 返回 null,紧接着 userDao.selectById(999999999) 查库也返回空,但请求还在持续打进来。
- 优先在接口层拦截明显非法值,如
id 、非数字字段含特殊字符,直接 <code>return或抛IllegalArgumentException - 对确实需要透传的查询,缓存空结果时务必设较短过期时间(如
30秒),避免占满内存;不要用永久缓存null - 高风险场景(如开放 API)必须引入
BloomFilter,部署在应用接入层或网关前,用 O(1) 时间判断key是否「可能存」,拦截 99% 以上无效请求
缓存击穿:查的是热点但刚好过期的数据
缓存击穿聚焦于「单个热点 key 失效瞬间的并发冲击」。比如秒杀商品详情页的 item:10086,缓存 TTL 到期后,上百个并发请求同时发现缓存 miss,全部涌向数据库查同一行记录,造成瞬时压力峰值。
关键区别在于:数据库里有这条数据,只是缓存断档了;而穿透是数据库里压根儿没这条数据。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 不要依赖「自然过期」,改用「逻辑过期 + 后台刷新」:缓存值内嵌一个
expireTime字段,读取时先比对,过期则异步触发更新,当前请求仍返回旧值 - 必须加锁时,用
SET key value NX PX 30000原子操作实现分布式锁,NX保证唯一性,PX防死锁;锁住后只允许一个线程查库写缓存,其余等待或重试 - 避免给所有热点 key 统一设置固定 TTL,至少加
Math.random() * 120秒扰动,分散失效时间点
布隆过滤器和互斥锁不是万能解药
BloomFilter 有误判率(false positive),但不会漏判(false negative);它能挡住 99.9% 的穿透请求,但无法识别「刚删掉又新增」的合法 key,所以不能替代业务层空值缓存逻辑。
SETNX 类锁方案在极端高并发下仍有竞争窗口——两个线程几乎同时执行 SETNX,可能都返回成功(取决于 Redis 单线程执行顺序),导致重复加载。生产环境建议用 Redission 的 RLock 或 Lua 脚本封装原子操作。
真正容易被忽略的点是:缓存策略必须和业务生命周期对齐。比如用户注销后,要同步清理其关联的多个 key(头像、订单、权限),否则残留 key 会持续引发穿透;而商品下架后,对应的击穿防护就该降级甚至关闭。

















