防止缓存穿透和雪崩需分层设防:穿透靠空值缓存+布隆过滤器+接口校验拦截无效请求;雪崩靠随机过期+互斥锁+双key容灾打散失效节奏并控制重建并发;辅以本地缓存、限流与监控形成闭环。

防止缓存穿透和雪崩不是靠单一手段,而是分层设防:穿透重在“拦截无效请求”,雪崩重在“打散失效节奏+控制重建并发”。两者逻辑不同,需分别应对。
用空值缓存+布隆过滤器堵住穿透
缓存穿透本质是查一个数据库里根本不存在的 key,比如恶意传入负数 ID、随机字符串,每次请求都绕过缓存直击 DB。核心对策是:不让它走到 DB 这一步。
- 空值缓存:数据库查无结果时,仍写入 Redis,值设为空字符串或 null,TTL 控制在 30–180 秒。后续相同请求直接命中缓存,不查库。
- 布隆过滤器前置校验:把所有合法存在的 key(如用户 ID、商品 SKU)预加载进 RedisBloom 或自研简易布隆结构;请求来时先过 filter,返回“一定不存在”就直接拒绝,不查缓存也不查库。
- 接口层基础校验:对 ID 做类型/范围检查(如非负整数)、对字符串做长度与格式限制(如 UUID 格式),从源头过滤明显非法请求。
用随机过期+互斥锁化解雪崩
缓存雪崩是大量 key 在同一秒集体过期,瞬间流量压垮数据库。关键不是“不让它过期”,而是“不让它们一起过期”,并“不让多个请求同时重建”。
-
设置带偏移的过期时间:写缓存时不设固定 TTL,改用
$ttl = 3600 + rand(60, 600)。哪怕批量生成,每个 key 失效时间也自然分散开。 -
用互斥锁控制重建入口:缓存失效后,第一个请求加锁(如 Redis 的
SETNX lock:user:123 1 EX 10),成功者查库并回填缓存;其余请求短暂等待(如 usleep(50000))后重试读缓存,避免并发击穿。 - 双 key 容灾机制:对核心数据写两个缓存 key——主 key(TTL=3600)、备用 key(TTL=14400)。读主 key 失败时立即读备用 key,并异步触发主 key 重建,确保总有可用缓存兜底。
加一层本地缓存降低远程压力
Redis 是网络调用,本身也有延迟和失败风险。在 PHP 进程内存中加一级 APCu 或 ArrayCache,作为第一道屏障,能显著减少无效 Redis 请求。
立即学习“PHP免费学习笔记(深入)”;
- 先查 APCu,命中则直接返回;未命中再查 Redis;Redis 也未命中才查库。
- APCu 缓存空值同样有效,且不占 Redis 内存,适合高频无效 key 的快速拦截。
- 注意 APCu 是进程级,需配合 opcache 和 FPM worker 管理策略,避免缓存不一致。
配合限流与监控形成闭环
技术防护之外,必须有运营感知能力。没有监控的防御是盲防,没有限流的防御是软防。
- 对单位时间内同一 IP 的空结果返回次数做统计(如 Redis 计数器
incr invalid_req:192.168.1.100),超阈值(如 100 次/分钟)自动封禁或返回 429。 - 用 Prometheus + Grafana 监控 Redis 的
keyspace_misses、DB 的慢查询数、缓存平均命中率,设置雪崩前兆告警(如命中率 1 分钟内跌超 40%)。 - 预热脚本定时运行:每天低峰期加载 TOP 200 热点数据进缓存,人为拉长“冷启动窗口”,避免凌晨缓存清空后突发流量冲击。



















