Redis 命中率 98% 掉到 23%:穿透、击穿、雪崩是三种病,别用同一副药
参考来源:阿里云开发者社区《命中率98%跌至23%,17条告警齐发:Redis缓存三大故障复盘》,另见 技术站转载。
事故:618 促销凌晨,17 条告警齐发
- Redis 命中率从 98% 断崖式跌到 23%
- MySQL 连接池打满
- 网关开始返 502
- 每单支付慢 4 秒
根因不复杂:一批过期 key 的 TTL 被设成了同一秒,到点全部失效,几千个请求同一时刻打到数据库。这是典型的缓存雪崩。
很多团队出问题后第一反应是「加缓存」,但穿透、击穿、雪崩的成因和防御手段完全不同,用同一副药只会耽误排查。
三种病先分清
| 类型 | 触发条件 | 表现 | DB 压力 | 防御 | 排查特征 |
|---|---|---|---|---|---|
| 缓存穿透 | 查不存在的 key | 永远不会命中 | 持续高(无效查询) | 布隆过滤器 + 空值缓存 | 请求分散,无热点 key |
| 缓存击穿 | 单个热点 key 过期瞬间 | 命中瞬间归零再恢复 | 瞬时打满 | 分布式锁 + 逻辑过期 | 某个 key 请求量异常集中 |
| 缓存雪崩 | 大量 key 同时过期 | 命中率断崖下降 | 瞬间洪峰 | TTL 随机化 + 多级缓存 + 降级 | 大量 key 同时 miss |
穿透
布隆过滤器:二进制位数组 + 多个哈希函数。查询时只要有一位是 0,就一定不存在;说存在的小概率不存在(误判可控,但不漏判)。
空值缓存:把 null 也缓存,TTL 设 60–120 秒。太长有新数据仍返回旧 null,太短防不住高频攻击。
踩坑记录:误判率一开始设 0.001,内存占了几百兆;改成 0.01(1%,缓存场景完全够)后内存降了一个数量级。业内推荐 0.1%–5%,只有安全敏感场景才追求更低。
落地用 RedisBloom 模块:
docker run -d --name redis-bloom -p 6379:6379 redislabs/redismod:latest
手动挂载:
git clone https://github.com/RedisBloom/RedisBloom.git && cd RedisBloom && make
docker run -d --name redis-bloom -v /opt/redis/modules:/modules redis:7-alpine --loadmodule /modules/redisbloom.so
验证模块是否加载(应看到 bf):
docker exec -it redis-bloom redis-cli MODULE LIST
BF.RESERVE user_ids_filter 0.01 1000000
BF.ADD user_ids_filter "user_123"
BF.EXISTS user_ids_filter "user_123" # 1
注意两件事:过滤器初始化后大小不可改;数据删除频繁的场景可以换布谷鸟过滤器,它支持删除。
击穿
互斥锁(Mutex):适合强一致场景。别手写 SETNX,线程异常退出锁不释放,其他线程会一直等。生产用 Redisson,有看门狗机制,线程异常退出会自动释放。
逻辑过期:适合响应时间要求 200ms 以内、不能等待的场景。value 里自带过期时间字段,Redis 的 TTL 设很长;查询时判断逻辑过期时间,过期就让后台线程异步重建,其他线程先返回旧值。
雪崩
- TTL 随机化:基础 TTL + 随机偏移,偏移量取基础值的 5%–10%,例如 3600s 加 300s 随机。偏移别超 10%,否则会拉长数据一致性窗口。
- 多级缓存:本地缓存 + Redis。
- 资源隔离与熔断降级:Sentinel / Resilience4j。
- 持久化:Redis 宕机是更致命的雪崩诱因,配置 AOF + RDB,防止重启后缓存为空再引发一次雪崩。
- 限流:单接口 QPS 超阈值直接返回 429。
排查与监控
雪崩场景下别用 KEYS 排查。生产环境执行 KEYS 会让 Redis 阻塞十几秒,用 SCAN 代替。
监控上,命中率低于 80% 就要开始排查穿透;数据库连接池要设最大连接数上限。
回头看这次事故,17 条告警、502、支付慢 4 秒,源头只是一批 TTL 写成了同一个值。