凌晨两点,运维老张被报警电话炸醒。数据库连接池耗尽,页面全白。他盯着监控大屏上那条垂直飙升的曲线,骂了句脏话——又是缓存集体罢工。

这不是电影桥段。Redis缓存的三大坑,每个搞技术的都迟早撞上。今天咱们不念八股文,直接拆招。

打开网易新闻 查看精彩图片

穿透:用不存在的数据砸穿防线

黑产脚本在凌晨三点启动,遍历手机号段13800000000到13899999999。这些用户根本不存在,缓存里当然找不到。请求像漏勺里的水,全部灌进数据库。

10秒内,DB连接数从80飙到2000+。服务超时,用户骂娘,老张背锅。

这就是缓存穿透——查询一个注定不存在的东西,缓存形同虚设,数据库硬扛所有伤害。

原文给过一个真实案例:未对注册手机号做空值缓存,黑产脚本直接导致服务全面超时。没有假设,没有"可能",就是发生过的事。

防御需要两道闸门。

第一道叫布隆过滤器(Bloom Filter)。它是个概率型数据结构,告诉你"这个key绝对不存在"或者"可能存在"。把用户ID全集灌进去,查询前先过一遍筛子。布隆过滤器说没有的,直接返回None,数据库连眼皮都不用抬。

第二道叫空值缓存。万一布隆过滤器漏了网,数据库查完确认真的没有,把这个"没有"也记下来——缓存一个字符串"NULL",过期时间设短一点,比如5分钟。下次同样的无效查询再来,Redis直接打发走,数据库继续睡它的觉。

原文的Python示例很直白:先查布隆过滤器,再查Redis,都没有才碰数据库。查完数据库不管有没有结果,都写回缓存——有数据缓存1小时,没数据缓存5分钟空值。

核心就八个字:存在性前置校验,空值短期缓存。

击穿:万人抢一扇门的悲剧

双十一零点,首页推荐位"hot_item"的缓存刚好过期。这一毫秒,1000个线程同时发现缓存没了,齐刷刷冲向数据库要数据。

谁重建?大家一起重建。数据库CPU瞬间拉满,缓存层反而成了瓶颈。

这叫缓存击穿——单个热点key过期,高并发请求穿透缓存直达数据库,争抢重建导致雪崩前兆。

原文的复现场景写得很细:GET hot_item返回nil的刹那,1000个线程同时执行SET。没有协调机制,就是灾难。

解法是分布式锁。Redis的SETNX命令(SET if Not eXists)天然适合干这个——谁抢到锁谁去查数据库重建缓存,其他人等着或者拿旧数据凑合。

但锁不能随便加。原文提了四个关键点:锁粒度最小化(只锁这个key,别锁整个系统)、自动过期(防止死锁)、二次检查(拿到锁后再确认一遍缓存真的没了)、异常必释放(finally里删锁,别漏了)。

代码逻辑很清晰:先读缓存,没有就尝试加锁。加锁成功?再读一次缓存(防止别人已经重建好了),还是没有才查数据库、写缓存、删锁。加锁失败?睡10毫秒重试,或者直接返回降级数据。

这把锁不是为了慢,是为了让重建动作串行化。一个人干活,其他人排队,总比一千人挤破门强。

雪崩:缓存的集体冬眠

最阴的是雪崩。运营同学图省事,所有商品详情页的缓存都设成3600秒过期。凌晨2点整,几万个key同时失效。

流量洪峰同步涌向数据库。这不是击穿,这是核爆。

原文给过一个危险操作:KEYS item:*——生产环境绝对禁用,会阻塞整个Redis实例。更安全的做法是SCAN配合TTL采样,慢慢排查。

根治方案是两招组合拳。

第一招叫随机抖动(Jitter)。基础TTL还是3600秒,但加减一个随机值,比如正负10%。这样key的过期时间被摊开到3240秒到3960秒之间,不会集体猝死。

第二招叫逻辑永不过期。核心数据不设过期时间,靠后台任务每30分钟主动刷新。缓存永远有数据,只是偶尔旧一点,总比没有强。

原文的口诀很实在:不设固定过期时间,只设"基础TTL+随机抖动";核心数据宁可冗余更新,也不集体断供。

三招拆完,剩下的是肌肉记忆

穿透、击穿、雪崩,本质是缓存与数据一致性之间的三道裂缝。守住它们,原文总结得很清楚:

存在性校验——布隆过滤器加空值缓存,让"不存在"止步于缓存层。

热点保护——原子锁加双重检查,把重建动作串行化。

过期分散——随机TTL加异步刷新,避免缓存集体休眠。

这三招不是理论,是运维老张用凌晨两点的电话换来的。你在项目中用过哪种防护组合?redis.conf里有没有藏着什么野路子?