1. 什么是缓存穿透?怎么解决?

穿透:查询不存在的 key——缓存没有、数据库也没有。恶意攻击者用不存在的 ID 疯狂请求,每次都打到数据库。

解决:

  1. 缓存空值:查不到也写缓存(value=null,TTL 短,如 60s)。简单有效,但浪费空间且短窗口内 DB 仍被打
  2. 布隆过滤器(推荐):请求先过布隆(所有可能存在的 key 预置其中),不在则直接返回。误判率可控(1%),不存在的 key 100% 被拦
  3. 参数校验:非法 ID(≤0、超范围)直接拒绝
布隆过滤器原理
// 一个 bit 数组 + k 个哈希函数
// 插入:key 经 k 个哈希函数,把对应 k 位全部置 1
// 查询:k 位只要有一位是 0 → 一定不存在(不漏报)
//         k 位全是 1 → 可能存在(有误报,因位被其他 key 置 1)

// Redis 实现:
// - 手动:SETBIT/GETBIT + 多哈希(简单但麻烦)
// - Redisson:RBloomFilter,开箱即用
// 注意:布隆不支持删除(位可能被多个 key 共享)→ 数据删除后误报率上升

🎯 面试要点

  • 穿透 = 查不存在的数据;防护重点是不存在的 key 不能打到 DB
  • 布隆过滤器适合"全量 key 集合稳定"的场景(如手机号是否注册过);集合频繁变化时维护成本高

2. 缓存击穿和缓存雪崩的区别?

击穿解决:

  1. 互斥锁:缓存过期后只让一个线程去查库回填,其他线程等锁后读新缓存(SETNX 或 JVM 锁)
  2. 逻辑过期:缓存永不过期(TTL 不设),value 里带过期时间字段;读时发现逻辑过期 → 异步线程更新缓存,先返回旧值(牺牲一致性换可用性)
  3. 热点 key 的 TTL 错开 + 预热

雪崩解决:

  1. 过期时间加随机值(基础 TTL + random(0~300s)),错开过期时间
  2. 多级缓存(本地缓存 Caffeine + Redis),Redis 挂了本地扛
  3. Redis 集群高可用(哨兵/Cluster),避免单点挂
  4. 限流降级:DB 扛不住时降级返回默认值/旧数据

🎯 面试要点

  • 击穿是"一个 key 的瞬间",雪崩是"一批 key 或整个缓存层"——先讲区别再讲方案,得分点
  • 互斥锁回填代码要防止死锁:加锁 → 二次检查缓存 → 查库 → 回填 → finally 释放
  • 生产经验:热 key 加逻辑过期 + 异步刷新是主流(对一致性要求不高的场景)

3. 数据库与缓存如何保证一致性?

问题根源:缓存更新和 DB 更新不是原子的,乱序执行就会不一致。先分析四个方案:

业界主流:Cache Aside(旁路缓存)——先更新数据库,再删除缓存:

  1. 读:缓存有 → 返回;没有 → 查库回填,TTL 兜底
  2. 写:先更新 DB,成功后删除缓存(下次读再回填)

仍可能不一致的窗口与补救:

🎯 面试要点

  • 答"先更新 DB 再删缓存" + 原因(并发窗口最小、删缓存天然幂等)+ 兜底(重试/对账/延迟双删)就是完整答案
  • 为什么不更新缓存而删除:缓存更新要算值且并发写覆盖难控,删除最省事且下次读自然回填
  • 最终一致性方案:binlog 订阅(canal)→ 异步删缓存,业务代码零侵入

4. 缓存预热和缓存降级?

🎯 面试要点

  • 降级的核心是"优雅的降级而非崩溃":超时设置(如 100ms)比无限等待重要
  • 大促预案:预热 + 多级缓存 + 限流 + 熔断 + 降级,一套组合拳

🎤 常见面试追问

  1. 穿透、击穿、雪崩怎么区分?(必背)——穿透:查不存在的 key(攻击,缓存和库都没有);击穿:单个热点 key 过期瞬间;雪崩:大量 key 同时过期或 Redis 挂了。穿透是"没有",击穿是"一个热 key",雪崩是"一批/整个缓存层"。
  2. 为什么"先更新 DB 再删缓存"是主流?——并发窗口最小:删缓存是幂等的、下次读自然回填;而"先删缓存再更新 DB"中间会有请求把旧值写回缓存。
  3. 缓存不一致怎么兜底?——删缓存失败重试(MQ)、延迟双删、binlog 订阅(canal)异步删、定期对账。强一致就别用缓存。
  4. 布隆过滤器为什么能防穿透?——所有"可能存在"的 key 预置到布隆:不存在的 key 100% 被拦(某位为 0),存在的可能有误判但不会漏。缺点:不支持删除。
  5. 互斥锁回填要注意什么?——加锁后要二次检查缓存(可能其他线程已回填)、finally 释放锁、设置等待超时——否则会死锁或锁等待拖垮。

📖 名词解释(本页术语)

术语 大白话解释
缓存穿透查询不存在的 key:缓存没有、数据库也没有,每次请求都打到数据库。解决:缓存空值、布隆过滤器、参数校验。
缓存击穿单个热点 key 过期瞬间,大量请求同时打库。解决:互斥锁、逻辑过期(异步刷新)。
缓存雪崩大量 key 同时过期(或 Redis 挂),请求洪峰打垮数据库。解决:TTL 随机化、多级缓存、集群高可用。
布隆过滤器bit 数组 + 多个哈希函数:判断"一定不存在"(某位为 0)或"可能存在"。防穿透利器,不支持删除。
Cache Aside(旁路缓存)主流缓存模式:读先查缓存,没有查库回填;写先更新库再删缓存。TTL 兜底。
延迟双删更新库后删缓存,睡一会再删一次——兜住"删缓存前有请求回填旧值"的极端窗口。
逻辑过期缓存不设 TTL,value 里带过期时间:读时发现逻辑过期先返回旧值、异步线程刷新——防击穿方案。
缓存预热 / 降级预热:上线前把热点数据提前塞进缓存;降级:缓存不可用时返回兜底数据保可用。
⚠️ 本页面由 AI 生成,内容仅供参考,请以官方文档和实际源码为准。