老张揉着眼睛爬起来,手机屏幕上DBA的未接来电已经摞了七个。后台监控群里,数据库连接数曲线像坐了火箭——10秒从200飙到2000+,服务全面超时。黑产脚本正在遍历138****0001到138****9999,每秒上万次查询砸向根本不存在的用户ID。这是缓存穿透,Redis缓存三连击的第一拳。
三拳全中什么场面?双十一零点你抢券页面卡死,背后可能是穿透、击穿、雪崩轮番上阵。本文用一张图拆透这三道裂缝,附可直接落地的Python代码。
一图锁定:三道裂缝长什么样
想象缓存是一堵墙,墙后是你的数据库:
穿透——有人专门砸墙不存在的部位,墙说"这儿没门",但砸的人太多,墙后的DB先崩。
击穿——热门展位突然撤了(缓存过期),上千人同时挤过去重建,DB被踩垮。
雪崩——整面墙的砖块同时到期,流量洪峰无差别灌入DB。
三者的共同点是:缓存没拦住,数据库硬扛。区别只在"没拦住"的原因不同。
穿透:让"不存在"止步于缓存层
攻击者用脚本狂刷user:999999999这种幽灵ID,缓存永远miss,请求直达数据库。每秒1万次EXISTS返回0,DB瞬间变筛子。
防御双保险:
第一重:布隆过滤器(Bloom Filter)做存在性前置校验。这是个概率型数据结构,告诉你"绝对不存在"或"可能存在"。把全量用户ID哈希进去,查询时先过一遍,绝对不存在的key直接拦截,零成本。
第二重:空值短期缓存。布隆过滤器说"可能存在"但实际查DB没有?把这个"没有"也缓存5分钟,下次同样请求直接返回NULL,不再穿透。
代码逻辑:
查缓存前→布隆过滤器拦截→缓存命中直接返回→未命中查DB→DB有数据缓存1小时/无数据缓存5分钟NULL。
核心就两点:存在性前置校验 + 空值短期缓存,把无效流量挡在数据库门外。
击穿:热点key过期时的并发踩踏
首页推荐位hot_item缓存到期那一毫秒,1000个线程同时发现GET返回nil,同时执行SET。谁先写?谁来查库?没协调就是灾难——数据库CPU飙升,缓存重建被重复执行N次。
解法:分布式锁串行化重建。
Redis的SETNX(SET if Not eXists)是原子操作,配合自动过期时间防死锁。抢到锁的线程去查库重建,没抢到的短暂等待后重试,或返回降级数据。
关键细节:
锁粒度必须最小化,只锁单个key;锁必须设自动过期,防止持有锁的线程崩溃导致死锁;拿到锁后二次检查缓存,防止重复重建;finally块必须释放锁,异常也不能漏。
锁不是性能杀手,未协调的并发才是。
雪崩:别让缓存集体"下班"
凌晨2点,运营批量导入商品设置了统一TTL=3600秒。整点一到,数万key同时失效,流量洪峰同步涌向数据库——这是缓存雪崩。
根治口诀:不设固定过期时间,只设"基础TTL+随机抖动";核心数据宁可冗余更新,也不集体断供。
具体做法:
普通数据:基础TTL ± 10%随机抖动。3600秒的基础过期时间,实际落在3240~3960秒之间,分散失效窗口。
核心数据:逻辑永不过期 + 异步刷新。缓存不设置过期时间,后台任务每30分钟主动更新,旧数据直到新数据写入前一直有效。
生产环境禁用KEYS命令扫描,用SCAN+TTL采样更安全。
从事故到防线:一个真实复盘
那次凌晨三点的穿透事故后,团队补了三件事:
布隆过滤器接入用户查询链路,百万级容量、千分之一误判率,内存占用不到2MB;空值缓存5分钟,黑产脚本的遍历攻击从2000+连接数压到个位数;监控告警从"数据库CPU>80%"前置到"缓存命中率骤降+大量NULL查询"。
监控不是锦上添花,是故障前的最后一道哨兵。
穿透、击穿、雪崩,本质是缓存与数据一致性之间的三道裂缝。守住它们,只需三招:存在性校验让"不存在"止步于缓存层;热点保护把重建动作串行化;过期分散避免缓存集体休眠。
缓存不是银弹,是需要精心设计的防护体系。你在项目中用过哪种防护组合?redis.conf里有没有藏着自研的黑魔法?
热门跟贴