缓存雪崩、击穿、穿透:一次讲清三者的区别与解法
缓存出问题的时候,表现常常一模一样:数据库突然被打满,接口大面积超时。但背后的原因可能完全不同。雪崩、击穿、穿透这三个词经常被混着用,实际上它们是三种不同的故障模式,解法也不一样。本文把三者分开讲清楚。
缓存雪崩:大量缓存同一时间失效
雪崩是指同一时间段内,大量缓存同时过期失效,请求全部落到数据库上,数据库瞬间被压垮。常见的原因是缓存设置了相同的过期时间,比如批量预热时统一设了 30 分钟,到点一起失效;或者缓存服务本身宕机,所有请求直接穿透到数据库。
应对雪崩的核心思路是把失效时间打散。给缓存的过期时间加一个随机值,比如基础时间加 0 到 5 分钟的随机偏移,避免同一批缓存在同一秒过期。热点数据可以设置永不过期,由后台任务异步刷新,而不是等它过期再由请求触发加载。
另一个办法是多级缓存。本地缓存挡住第一波,分布式缓存挡第二波,数据库只承担最后的少量请求。即使分布式缓存整体失效,本地缓存还能撑一阵,给系统留出恢复时间。降级熔断也是最后一道防线:当数据库负载过高时,直接返回默认值或旧数据,先保住系统不崩。
缓存击穿:单个热点 key 过期
击穿和雪崩的区别在于规模:击穿是某一个热点 key 过期了,同一瞬间大量请求都在访问这个 key,全部打到数据库上。比如一个爆款商品的详情页缓存过期,几万个并发请求同时去查数据库,数据库扛不住。
击穿的解法是互斥锁。第一个请求发现缓存 miss 后去拿锁,拿到锁的去查数据库并回填缓存,其他请求等一小会儿再去读缓存。锁的粒度要控制在单个 key 上,不能一把全局锁锁住所有请求,否则并发一上来就全堵住了。
更稳妥的做法是热点 key 永不过期加异步刷新。后台定时任务在缓存快过期前主动刷新,请求永远读得到缓存,根本不会出现 miss。逻辑过期是另一种实现:缓存里存一份过期时间字段,数据本身不过期,读到逻辑过期的数据时由一个线程去刷新,其他线程继续返回旧数据,避免所有请求同时回源。
缓存穿透:请求查的是不存在的数据
穿透是指请求查询的数据根本不存在,缓存里没有,数据库里也没有,于是每次请求都穿过缓存打到数据库。攻击者可以故意用不存在的 ID 轮询,数据库被大量无效查询拖垮。正常业务里也有这种情况,比如用户输入了错误的订单号、爬虫遍历不存在的商品 ID。
最简单的防线是空值缓存。数据库查不到时,也往缓存里写一个空值并设置较短的过期时间,下次同样的请求直接命中空值,不再查库。要注意空值缓存的过期时间不能太长,否则数据刚写入时,请求还会拿到旧的空值。
布隆过滤器是更彻底的办法。在请求到达缓存之前,先用布隆过滤器判断这个 key 是否可能存在,不存在就直接返回,不查缓存也不查数据库。布隆过滤器有一定的误判率,但它只会把不存在的误判为存在,不会把存在的误判为不存在,所以是安全的。维护成本在于要把所有合法 key 同步进过滤器,新增数据时要及时更新。
参数校验也别忽视。很多穿透请求其实一眼就能看出来不合法:ID 格式不对、范围明显越界,这类请求在网关层直接拦截,根本不用进到缓存和数据库。
三者怎么区分,怎么选解法
从现象上区分:雪崩是大量不同 key 同时失效,数据库压力来自全量请求;击穿是单个热点 key 失效,压力集中在一条查询上;穿透是请求的数据本身不存在,缓存永远命中不了。
解法上也可以对照着记:雪崩靠打散过期时间和多级缓存,击穿靠互斥锁和逻辑过期,穿透靠空值缓存和布隆过滤器。实际项目里三种问题可能同时出现,防护手段也要叠加使用。
最后一点经验:缓存问题大多不是缓存本身的问题,而是过期策略和回源策略没设计好。上线前把热点 key 的过期时间、空值处理、并发回源这三件事想清楚,能避免绝大多数线上事故。
- 点赞
- 收藏
- 关注作者
评论(0)