八股文解析
Redis 缓存穿透/击穿/雪崩有什么区别?怎么防?
一句话结论
缓存穿透查不存在的数据打穿DB,缓存击穿热点key过期瞬间打爆DB,缓存雪崩大量key同时失效导致DB整体崩溃,三者本质是缓存未命中后的DB保护问题。
面试标准答法
1. 缓存穿透(Cache Penetration)
定义:查询一个根本不存在的数据,缓存和数据库都没有,每次请求都直接打到数据库。
关键机制:
- 请求流程:
Client → Redis(未命中) → MySQL(无记录) → 返回null - 恶意攻击场景:攻击者构造大量不存在的ID(如负数、UUID、超范围ID)持续请求
- 危害:DB 承受大量无效查询,连接池耗尽,服务雪崩
核心解决方案:
| 方案 | 原理 | 代价 |
|---|---|---|
| 缓存空值(Null Cache) | 将null结果也缓存,TTL设短(30-60s) | 额外内存,需防大量key堆积 |
| 布隆过滤器(Bloom Filter) | 请求前先判断key是否可能存在,不存在直接拒绝 | 有误判率(false positive),需维护全量数据 |
| 参数校验 | 拦截非法参数(如id<=0) | 只防低级攻击,防不住合法格式的无效key |
关键参数:布隆过滤器误判率默认设置 0.01,位数组大小 m = -n*ln(p)/(ln2)^2,哈希函数个数 k = m/n * ln2。
2. 缓存击穿(Cache Breakdown / Hotspot Key Expiration)
定义:单个热点key 在缓存过期的瞬间,大量并发请求同时穿透到数据库。
关键机制:
- 触发条件:热点key(如秒杀商品、微博热搜)过期 + 高并发访问
- 时间窗口:key过期后到新值回填前的极短时间窗口内,所有请求直击DB
- 与穿透区别:穿透是查不存在的数据,击穿是查存在但缓存刚好过期的数据
核心解决方案:
| 方案 | 原理 | 适用场景 |
|---|---|---|
| 互斥锁(Mutex Lock) | 缓存未命中时,只放一个线程去查DB,其他线程等待/自旋 | 并发量极大,一致性要求高 |
| 逻辑过期(Logical Expiration) | 缓存中存 value + expireTime,线程发现逻辑过期后异步刷新 | 允许短暂脏读,追求极致性能 |
| 热点key永不过期 + 后台定时刷新 | 物理上不设TTL,由Job定期更新 | 数据变更频率可控 |
互斥锁实现要点:使用 SETNX(SET if Not eXists)加锁,设置合理超时时间(如200ms),防止死锁;等待线程需设置最大等待时间,避免无限阻塞。
3. 缓存雪崩(Cache Avalanche)
定义:大量key同时失效(或Redis集群宕机),导致所有请求全部打到数据库,DB瞬间被压垮。
关键机制:
- 两种触发场景:① 大量key设置了相同的过期时间(如零点统一过期) ② Redis集群故障(主从全挂)
- 雪崩是击穿的大规模版本:击穿是单点失效,雪崩是批量失效
核心解决方案:
| 方案 | 原理 | 缺点 |
|---|---|---|
| 过期时间加随机值 | TTL = 基础值 + random(0, 300s) | 简单有效,防同时失效 |
| 多级缓存(L1+L2) | 本地缓存(Caffeine/Guava)+ Redis,本地缓存抗住第一波 | 需维护一致性 |
| Redis高可用 | 主从 + 哨兵(Sentinel)/ 集群(Cluster),自动故障转移 | 增加运维复杂度 |
| 服务降级 + 限流 | Hystrix/Sentinel 熔断降级,返回兜底数据 | 业务需接受降级响应 |
对比总表
| 维度 | 穿透 | 击穿 | 雪崩 |
|---|---|---|---|
| 核心问题 | 数据不存在 | 单个热点key过期 | 大量key同时过期/Redis宕机 |
| 并发特征 | 持续低频/恶意高频 | 瞬时超高并发 | 瞬时全量请求 |
| 数据状态 | 缓存和DB都没有 | DB有,缓存没有 | DB有,缓存批量没有 |
| 首要解决方案 | 布隆过滤器/空值缓存 | 互斥锁/逻辑过期 | TTL随机化/高可用 |
| 影响范围 | 单key无效查询 | 单key热点 | 全局/大面积 |
| 检测方式 | 缓存命中率骤降 | 单个key QPS突增 | 整体缓存命中率暴跌 |
常见追问
| 追问 | 回答要点 |
|---|---|
| 布隆过滤器支持删除吗? | 标准布隆过滤器不支持删除(每个bit可能被多个元素共享)。如需删除用计数布隆过滤器(Counting Bloom Filter),每个槽位用计数器代替bit。 |
| 互斥锁方案中,其他线程等待多久? | 推荐自旋 + 超时:自旋间隔50ms,最大等待500ms(DB查询平均耗时)。超时后返回默认值或降级数据,避免线程堆积。 |
| 缓存空值的TTL设多少合适? | 30-60s。太短防不住穿透,太长会导致数据更新后长时间不可见。可配合异步更新:发现key被写入后主动删除空值缓存。 |
| Redis集群宕机怎么办? | ① 依赖多级缓存(本地缓存兜底) ② 开启持久化(RDB/AOF)快速恢复 ③ 触发熔断降级,直接返回静态数据或错误码,保护DB。 |
面试回答模板(30秒版)
延伸准备(加分项)
- 缓存一致性深挖:穿透/击穿/雪崩的防护方案与缓存一致性(Cache Aside Pattern、Write Behind)的冲突点。例如逻辑过期方案会牺牲一致性,如何用版本号或binlog订阅(Canal)补偿。
- 热点key的自动发现:生产环境如何自动识别热点key?基于Redis的hotkey分析(
redis-cli --hotkeys)、JD本地统计或代理层(Codis/Twemproxy)流量分析。知道这个说明你有实战经验。
- 布隆过滤器的工程落地:数据量100亿级别时,布隆过滤器初始化需要扫描全量数据,如何用分段构建 + 渐进式加载避免启动阻塞?误判率如何动态调整?能讲清楚这些细节会明显加分。
想系统备战大厂大模型/Agent 开发?NiceOffer 提供 SDE+LLM 双轨 1v1 陪跑,合同保底 40w 年薪,文末扫码咨询。