
事情是这样开始的
上周五晚上十一点,我手机上的告警机器人突然开始狂响。打开后台一看,数据库连接数直接飙到了两千多,平时这数字撑死也就一两百。我当时刚洗完澡,头发都没吹干就冲到电脑前,心里只有一个念头:完了,又得通宵了。
查了一圈,罪魁祸首是我们的一个活动页。那天晚上运营上线了一个秒杀预告,结果被人用脚本盯上了,不停地请求一个根本不存在的商品 ID。每个请求都绕过缓存打到数据库,数据库扛不住,就开始雪崩式地拖垮其他服务。这一晚上,我算是把缓存三兄弟——穿透、击穿、雪崩——全给亲身经历了一遍。
先说说缓存穿透
缓存穿透说白了就是:你查的数据在缓存里没有,在数据库里也没有。正常情况下,缓存没命中会去查数据库,查到了就回填缓存。可如果查的是一个不存在的 ID,数据库每次都查不到,也就永远不会回填缓存,于是每一次请求都直奔数据库。
那天晚上就是这样,攻击脚本用一堆不存在的商品 ID 轮着刷,我们的 Redis 完全派不上用场,数据库被白嫖了一整晚。解决方案其实不复杂:一个是查不到就把空值也缓存起来,设个短过期时间,比如五分钟;另一个是上布隆过滤器,把所有合法 ID 提前放进去,请求来了先过一遍过滤器,不存在的直接挡在外面,连数据库的门都摸不到。
再说缓存击穿
击穿和穿透一字之差,意思完全不一样。击穿指的是:某个热点 key 在过期的瞬间,突然有大量请求同时涌过来,大家发现缓存没了,于是一窝蜂全去打数据库。
想象一下,一个平时被十万人在线的热门商品,它的缓存刚好在某个时间点失效,那一瞬间的并发全压在数据库上,不挂才怪。解决办法有两个思路:一是加互斥锁,让第一个请求去查数据库回填缓存,其他请求等着读缓存就行;二是逻辑过期,就是缓存里存的 value 带一个过期时间字段,查询的时候发现快过期了,就异步去更新,先把旧数据返回给用户,体验上完全无感。
最后是缓存雪崩
雪崩比击穿更狠。击穿是单个热点 key 挂了,雪崩是一大片 key 同时过期,或者干脆就是 Redis 服务本身宕了,所有请求瞬间全部打到数据库。
之前我们犯过一个特别蠢的错:给一批活动数据设置缓存过期时间时,图省事全用了同一个时间点。结果凌晨零点一到,几百个 key 一起失效,数据库直接被压垮,页面白屏了快二十分钟。后来学乖了,过期时间统一加一个随机值,比如 300 秒到 600 秒之间随机,让 key 的失效时间错开。再往后,我们上了多级缓存,Redis 挂了还有本地缓存兜底,本地缓存也扛不住,才轮到数据库。
事后复盘
那次事故之后,我做了三件事:第一,给所有查询接口加了布隆过滤器;第二,热点 key 全部改成逻辑过期;第三,写了个监控脚本,专门盯着数据库连接数和缓存命中率,一旦异常就自动报警。
说实话,这些概念书上都写过,面试也背过,但真正被线上事故教育过一遍,才算真的懂了。缓存这东西,用好了是性能神器,用不好就是定时炸弹。希望看到这篇文章的你,不用像我一样非得踩一次坑才能记住。
评论 (0)
暂无评论,来写第一条吧 ✍️