🎯 缓存经典问题
穿透 · 击穿 · 雪崩 · 双写一致性 · 缓存预热与降级
1. 什么是缓存穿透?怎么解决?
穿透:查询不存在的 key——缓存没有、数据库也没有。恶意攻击者用不存在的 ID 疯狂请求,每次都打到数据库。
解决:
- 缓存空值:查不到也写缓存(value=null,TTL 短,如 60s)。简单有效,但浪费空间且短窗口内 DB 仍被打
- 布隆过滤器(推荐):请求先过布隆(所有可能存在的 key 预置其中),不在则直接返回。误判率可控(1%),不存在的 key 100% 被拦
- 参数校验:非法 ID(≤0、超范围)直接拒绝
布隆过滤器原理
// 一个 bit 数组 + k 个哈希函数
// 插入:key 经 k 个哈希函数,把对应 k 位全部置 1
// 查询:k 位只要有一位是 0 → 一定不存在(不漏报)
// k 位全是 1 → 可能存在(有误报,因位被其他 key 置 1)
// Redis 实现:
// - 手动:SETBIT/GETBIT + 多哈希(简单但麻烦)
// - Redisson:RBloomFilter,开箱即用
// 注意:布隆不支持删除(位可能被多个 key 共享)→ 数据删除后误报率上升
🎯 面试要点
- 穿透 = 查不存在的数据;防护重点是不存在的 key 不能打到 DB
- 布隆过滤器适合"全量 key 集合稳定"的场景(如手机号是否注册过);集合频繁变化时维护成本高
2. 缓存击穿和缓存雪崩的区别?
- 击穿:单个热点 key 过期瞬间,大量并发请求同时打到数据库(同一时刻)。点状问题
- 雪崩:大量 key 同时过期(或 Redis 挂了),请求洪峰直接压垮数据库。面状问题
击穿解决:
- 互斥锁:缓存过期后只让一个线程去查库回填,其他线程等锁后读新缓存(SETNX 或 JVM 锁)
- 逻辑过期:缓存永不过期(TTL 不设),value 里带过期时间字段;读时发现逻辑过期 → 异步线程更新缓存,先返回旧值(牺牲一致性换可用性)
- 热点 key 的 TTL 错开 + 预热
雪崩解决:
- 过期时间加随机值(基础 TTL + random(0~300s)),错开过期时间
- 多级缓存(本地缓存 Caffeine + Redis),Redis 挂了本地扛
- Redis 集群高可用(哨兵/Cluster),避免单点挂
- 限流降级:DB 扛不住时降级返回默认值/旧数据
🎯 面试要点
- 击穿是"一个 key 的瞬间",雪崩是"一批 key 或整个缓存层"——先讲区别再讲方案,得分点
- 互斥锁回填代码要防止死锁:加锁 → 二次检查缓存 → 查库 → 回填 → finally 释放
- 生产经验:热 key 加逻辑过期 + 异步刷新是主流(对一致性要求不高的场景)
3. 数据库与缓存如何保证一致性?
问题根源:缓存更新和 DB 更新不是原子的,乱序执行就会不一致。先分析四个方案:
- 先更新 DB,再更新缓存 → 并发下两个线程交叉写,缓存可能存旧值 ❌
- 先删缓存,再更新 DB → 更新期间有请求读到 DB 旧值回填缓存 ❌(旧值又进缓存)
- 先更新 DB,再删缓存 → 读请求在"删之前"读到旧值,但删后下次读就是新的 ✅ 主流方案
业界主流:Cache Aside(旁路缓存)——先更新数据库,再删除缓存:
- 读:缓存有 → 返回;没有 → 查库回填,TTL 兜底
- 写:先更新 DB,成功后删除缓存(下次读再回填)
仍可能不一致的窗口与补救:
- 删缓存失败 → 重试(消息队列/定时对账)
- 极端并发:A 读缓存未命中 → A 先查库拿到旧值 → B 更新数据库 → B 删除缓存 → A 才把旧值回填进缓存 → 旧值滞留一个 TTL。缓解:延迟双删(更新库并删缓存后,在事务提交之后再延迟 500ms~1s 删一次)或 key 加版本号
- 强一致要求 → 别用缓存,或读改写都走 DB(缓存仅加速)
🎯 面试要点
- 答"先更新 DB 再删缓存" + 原因(并发窗口最小、删缓存天然幂等)+ 兜底(重试/对账/延迟双删)就是完整答案
- 为什么不更新缓存而删除:缓存更新要算值且并发写覆盖难控,删除最省事且下次读自然回填
- 最终一致性方案:binlog 订阅(canal)→ 异步删缓存,业务代码零侵入
4. 缓存预热和缓存降级?
- 预热:系统上线/大促前,把热点数据提前写入缓存(后台任务扫库回填 + 手动触发),避免启动瞬间击穿。配合本地缓存 + 热 key 识别(LRU 统计)
- 降级:缓存不可用时(Redis 故障/超时),策略性返回兜底数据:默认值、旧快照、空数据——保系统可用不拖垮 DB。分:读降级(限流+熔断)和写降级(同步写转异步 MQ)
🎯 面试要点
- 降级的核心是"优雅的降级而非崩溃":超时设置(如 100ms)比无限等待重要
- 大促预案:预热 + 多级缓存 + 限流 + 熔断 + 降级,一套组合拳