缓存与数据库一致性实战

共 18 题
📑 题目列表 18 题
#
★★★

1. Cache-Aside 下先更新数据库再删缓存为什么是主流方案?延迟双删和订阅 binlog(Canal)方案分别解决什么残留问题?

请说明 Cache-Aside 模式下先更新数据库再删缓存为什么是主流方案,以及延迟双删和订阅 binlog(Canal)方案分别解决什么残留问题?

  • Cache-Aside 的读写流程
  • 先更新 DB 再删缓存的优势
  • 延迟双删与 Canal 的残留问题

Cache-Aside 中,读先查缓存,未命中则查库回填;写时先更新数据库,再删除缓存。主流方案是"先更新 DB 再删缓存"而非"先删缓存再更新 DB",因为先删缓存再更新 DB 的窗口期,并发读会读到旧数据库值并回填旧缓存,造成脏数据。而先更新 DB 再删缓存,即使删缓存失败,也只是短暂读到旧缓存,DB 已是最新。残留问题:删除缓存失败或并发读写交错仍可能产生不一致,延迟双删(先删缓存、更新 DB、延迟后再删一次)解决"更新后立即有读回填旧值"的残留;订阅 binlog(Canal)通过监听 DB binlog 异步删除缓存,解决删除失败重试与缓存一致性问题。

"先 DB 后删缓存"把不一致窗口缩到最小,且删缓存幂等(删除失败可重试)。延迟双删与 Canal 是进一步消除"删除失败/竞态"导致的残留脏数据,Canal 提供了可靠异步删除的兜底。

#
★★★

2. 缓存与数据库不一致的所有产生时序(并发读写交错)如何系统枚举与规避?

请系统枚举缓存与数据库不一致的所有产生时序(并发读写交错),并说明如何规避?

  • 并发读写交错的时序
  • 不一致的产生机制
  • 规避手段

缓存与 DB 不一致的主要时序:① 读请求先查缓存未命中,准备回填 DB 旧值,期间写请求更新 DB 并删缓存,读请求随后把旧值写回缓存(读旧写新);② 写请求先删缓存,期间读请求查库回填旧值,写请求再更新 DB,缓存残留旧值;③ 写操作先更新 DB 后删缓存,但删缓存失败,缓存残留旧值;④ 并发两个写请求,后写者的 DB 更新与缓存删除交错。规避手段:先更新 DB 再删缓存 + 删除失败重试(延迟双删、Canal/binlog);为回填缓存加锁/版本号,避免旧值覆盖新值;引入版本号(缓存存版本,DB 版本更新后比对)防止脏读回填。

不一致的本质是"缓存写入与 DB 更新/删除的时序竞态"。核心规避是"先 DB 后删缓存 + 删除可靠 + 回填防旧",用版本号或互斥锁消除"读旧写新"。

#
★★★

3. 缓存穿透、缓存击穿、缓存雪崩三大问题的根因、检测方法与工程解决方案?

请说明缓存穿透、缓存击穿、缓存雪崩三大问题的根因、检测方法与工程解决方案?

  • 三大问题的根因
  • 各自的检测方法
  • 工程解决方案

缓存穿透:查询不存在的数据,缓存与 DB 都无,请求直接打到 DB,甚至被恶意利用。解决:布隆过滤器拦截、空值缓存(null 也缓存短 TTL)、参数校验。缓存击穿:单个热点 key 过期瞬间,大量并发请求同时回源 DB。解决:互斥锁(只允许一个请求回源)、逻辑过期(热点不设物理 TTL)、热点 key 预热。缓存雪崩:大量 key 同时过期或缓存集群故障,DB 被瞬间打爆。解决:过期时间随机化、多级缓存、限流降级、集群高可用。检测方法:穿透看 DB 命中率异常、击穿看热点 key 并发回源、雪崩看 DB 连接/负载突增。

三者的共同点是"大量请求回源 DB",区别在"数据不存在 / 单 key 过期 / 批量 key 过期"。解决方案分别针对"拦截空查询 / 串行化回源 / 错峰与降级"。

#
★★★

4. Cache Aside 的缓存删除失败、并发读写竞态(读旧写新)如何用延迟双删/版本号解决?

请说明 Cache Aside 中缓存删除失败与并发读写竞态(读旧写新)如何用延迟双删或版本号解决?

  • 删除失败的处理
  • 延迟双删的原理
  • 版本号防脏读回填

删除失败:先更新 DB 再删缓存,若删除失败,缓存残留旧值。解决:删除失败重试(消息队列/重试表)、延迟双删(更新 DB 后先删一次,延迟一段时间再删一次,防止更新后窗口内读回填旧值)。并发读写竞态(读旧写新):读请求在更新 DB 与删缓存之间查库回填旧值。解决:版本号方案——缓存中存版本号,DB 更新时递增版本,回填缓存时校验 DB 版本,只有版本一致才允许回填,或用互斥锁保证回填与更新串行。延迟双删与版本号可组合:延迟双删处理删除窗口,版本号从根上防旧值覆盖。

延迟双删应对"删除失败/删除窗口"的残留,版本号应对"读旧写新"的本质竞态。两者的共同目标是让缓存最终与实际 DB 值一致。

#
★★★

5. 缓存一致性在生产中的验收方法(故障注入、并发压测与监控指标)

请说明缓存一致性在生产中的验收方法,包括故障注入、并发压测与监控指标?

  • 故障注入验证
  • 并发压测场景
  • 一致性监控指标

缓存一致性验收方法:故障注入(模拟删除缓存失败、DB 故障、缓存过期,验证是否有脏数据与非预期行为);并发压测(构造读写并发场景,验证读旧写新、删除竞态下的一致性,检查是否有脏数据残留);监控指标(缓存命中率、DB 回源率、缓存与 DB 的比对抽样、删除失败率、异常日志)。工程上可做"数据对账":定期抽样比对缓存与 DB 值,发现不一致。验收标准是"最终一致 SLA 内无脏数据、无不可容忍的延迟"。

一致性验收需要"主动制造故障 + 并发压测 + 持续监控"三层验证,用故障注入暴露弱点点、用并发压测验证竞态、用对账与监控保证长期一致。

#
★★★

6. 缓存穿透/击穿/雪崩的应对,布隆过滤器、互斥锁与过期随机化?

请说明缓存穿透、击穿、雪崩的应对手段,即布隆过滤器、互斥锁与过期随机化?

  • 布隆过滤器解决穿透
  • 互斥锁解决击穿
  • 过期随机化解决雪崩

布隆过滤器用于缓存穿透:在缓存前加布隆过滤器,判断 key 是否存在,不存在则直接返回,拦截大量不存在的查询打到 DB。互斥锁用于缓存击穿:热点 key 过期时只允许一个请求回源 DB 并重建缓存,其他请求等待缓存重建完成,避免并发打爆 DB。过期随机化用于缓存雪崩:给 key 的 TTL 加随机值,避免大量 key 同时过期导致 DB 瞬间压力。三者分别针对穿透、击穿、雪崩,可组合使用(如布隆过滤器 + 互斥锁 + 随机过期)。

三个方案对应三个问题的根因:布隆过滤器拦截"无效查询",互斥锁串行化"热点回源",随机化错峰"批量过期"。它们共同降低 DB 的瞬时压力。

#
★★

7. 读写穿透(Read/Write-Through)与写回(Write-Behind)模式在工程中少见的原因?

请说明读写穿透(Read/Write-Through)与写回(Write-Behind)模式在工程中少见的原因?

  • Read/Write-Through 与 Write-Behind 的原理
  • 与 Cache-Aside 的对比
  • 少见原因

Read/Write-Through:应用把读写委托给缓存,缓存负责同步 DB,缓存是主入口,DB 对应用透明。Write-Behind:写操作先写缓存,异步批量写回 DB,显著提升写吞吐。少见原因:Write-Through 把所有读写压到缓存,缓存与 DB 的双写耦合、缓存故障时应用不可用;Write-Behind 引入异步写回的丢失风险与最终一致性问题,难以保证数据安全与一致性。Cache-Aside 更灵活、可控、故障隔离好,是主流。Write-Through/Write-Behind 需要缓存中间件原生支持(如 Redis 不太适合),实现复杂,故工程上少用。

Cache-Aside 把缓存作为"加速层"而非"主存储",故障隔离与灵活性更好;Write-Through/Write-Behind 把缓存当主存储,引入耦合、丢失与一致性问题,故不主流。

#
★★

8. 多级缓存(本地缓存 Caffeine + 分布式缓存 Redis)的一致性挑战,本地缓存如何感知远端失效?

请说明多级缓存(本地缓存 Caffeine + 分布式缓存 Redis)的一致性挑战,以及本地缓存如何感知远端失效?

  • 多级缓存的层级结构
  • 本地缓存与远端的一致性问题
  • 失效感知机制(Pub/Sub、版本、TTL)

多级缓存把本地缓存(Caffeine)作为 L1、Redis 作为 L2,读先查 L1 再 L2 再 DB。一致性挑战:本地缓存分散在各应用实例,更新发生在远端 Redis 时,各实例的本地缓存无法自动感知,可能长期读到旧值。失效感知机制:Redis Pub/Sub 广播失效消息(写时删除缓存并发布通知,各实例订阅后清除本地缓存);版本号/失效版本比对;本地缓存设置较短 TTL(依赖 TTL 兜底);或用消息队列广播失效。这些机制需权衡实时性与复杂度。

本地缓存的一致性难点在于"分散的实例如何收到失效通知"。Pub/Sub 广播 + 短 TTL 兜底是常见组合,既保证相对实时又防止通知丢失后长期不一致。

#
★★

9. 缓存预热(Warm-up)与缓存降级策略,系统重启或缓存集群故障时如何保护数据库?

请说明缓存预热(Warm-up)与缓存降级策略,以及系统重启或缓存集群故障时如何保护数据库?

  • 缓存预热的意义与方法
  • 缓存降级策略
  • 缓存故障时的数据库保护

缓存预热(Warm-up)是在系统启动或大促前,把热点数据提前加载进缓存,避免冷启动时全部请求打到 DB(穿透)。方法:启动时加载热点 key、定时刷新、基于历史访问统计预热。缓存降级:缓存集群故障时,关闭缓存链路,直接读 DB(或读降级数据源),并启动限流保护 DB。系统重启或缓存故障时,通过限流、熔断、兜底数据(本地缓存/静态数据)保护 DB,避免缓存不可用导致 DB 被瞬间打爆。

预热解决"缓存空"的冷启动穿透,降级应对"缓存坏"的故障。两者都为了"保护 DB",配合限流与兜底数据实现弹性。

#
★★

10. 热点 Key 的识别与治理,如何防止单个热点 Key 导致 Redis 单节点过载?

请说明热点 Key 的识别方法,以及如何防止单个热点 Key 导致 Redis 单节点过载?

  • 热点 Key 识别(--hotkeys、客户端统计)
  • 热点 Key 治理(本地缓存、复制分片、限流)
  • 防止单节点过载

热点 Key 识别:redis-cli --hotkeys(依赖 LFU 命中统计)、客户端统计 key 访问频率、慢查询/监控。治理:本地缓存(Caffeine)把热点数据缓存在应用进程,减少对 Redis 的访问;热点 key 复制分片(把同一热点 key 复制成多个副本分散到不同节点,读时随机取一个);热点 key 逻辑过期/预热;限流与降级。通过这些手段把热点 key 的流量从"单个 Redis 节点"分散到"多节点 + 本地缓存",避免单节点过载。

热点 Key 的治理本质是"把集中流量分散":本地缓存分流进程内读、副本分片分散节点读、限流保护。防止单节点过载是热点治理的核心目标。

#
★★

11. 缓存与 DB 的一致性在强一致业务(库存/余额)下的取舍,何时必须同步失效?

请说明缓存与 DB 的一致性在强一致业务(库存/余额)下的取舍,以及何时必须同步失效?

  • 强一致业务对缓存的要求
  • 缓存失效的时机
  • 同步失效 vs 异步

在库存、余额等强一致业务下,缓存必须与 DB 保持一致,否则会出现超卖、余额错误。取舍:这类业务通常不把缓存作为"最终一致"的加速层,而是要求"同步失效"——更新 DB 后立即删除缓存(或同步更新缓存),并保证删除成功(重试、事务内删除)。何时必须同步失效:当缓存值参与业务正确性判断(如扣减库存、余额校验)时,必须先更新 DB 再同步删缓存,且保证读不会读到旧值(可用版本号/锁)。若无法保证同步一致,宁可绕过缓存直接用 DB。

强一致业务下缓存的角色从"加速"转为"敏感数据",一致性优先于性能。同步失效(DB 更新后立即删缓存且保证成功)是正确做法,必要时绕开缓存。

#
★★

12. 多级缓存(本地+分布式)的一致性与失效广播(Redis Pub/Sub)如何设计?

请说明多级缓存(本地 + 分布式)的一致性与失效广播(Redis Pub/Sub)如何设计?

  • 多级缓存失效链路
  • Redis Pub/Sub 广播失效
  • 一致性设计要点

多级缓存设计:更新 DB 后删除 Redis 缓存,并通过 Redis Pub/Sub 广播"key 失效"消息,各应用实例订阅该消息,收到后清除本地缓存(Caffeine)。这样本地缓存能感知远端失效。设计要点:发布消息在删除 Redis 之后(或同时),保证订阅者收到的失效是权威的;订阅者清除本地缓存与重建要幂等;Pub/Sub 不保证消息不丢失(断线期间可能漏收),需配合本地缓存短 TTL 兜底;用版本号比对可避免旧值回填。整体是"Pub/Sub 实时失效 + TTL 兜底 + 版本号防旧"。

Pub/Sub 解决"本地缓存实时感知失效",但 Pub/Sub 本身不持久,需 TTL 兜底。多级缓存一致性 = 广播失效 + 短 TTL 兜底 + 版本号防旧。

#
★★

13. 缓存击穿与热点 Key,逻辑过期与分布式锁?

请说明缓存击穿与热点 Key 的解决手段,即逻辑过期与分布式锁?

  • 逻辑过期方案
  • 分布式锁方案(互斥锁)
  • 两者对比

缓存击穿解决:互斥锁方案——热点 key 过期时,只允许一个请求用分布式锁回源 DB 重建缓存,其他请求等待缓存重建完成;逻辑过期方案——缓存中存"逻辑过期时间"字段,读时判断逻辑过期,过期则返回旧值并触发异步重建(更新缓存),不阻塞读。互斥锁保证只有一人回源但可能阻塞等待;逻辑过期读不阻塞、返回旧值,但短暂读到旧数据。热点 Key 可结合两者:逻辑过期返回旧值 + 异步重建 + 分布式锁控制重建。两者都能避免并发回源打爆 DB。

互斥锁"串行回源"、逻辑过期"异步重建",前者以等待换一致、后者以旧值换高吞吐。热点 key 场景常组合使用。

#
★★

14. Caffeine 的 refreshAfterWrite 与 expireAfterWrite 对缓存一致性的影响

请说明 Caffeine 的 refreshAfterWrite 与 expireAfterWrite 对缓存一致性的影响?

  • refreshAfterWrite:异步刷新
  • expireAfterWrite:到期删除
  • 对一致性的影响

expireAfterWrite:写入后经过指定时间过期,到期后 key 被移除,下次访问需重新加载,保证缓存不会无限期陈旧,但到期瞬间可能引发并发穿透(多个请求同时回源)。refreshAfterWrite:写入后经过指定时间触发异步刷新(后台重新加载新值),期间访问仍返回旧值,不会阻塞,但刷新是异步的,可能短暂读到旧值。一致性影响:expireAfterWrite 是"硬过期",保证最终一致但可能并发穿透;refreshAfterWrite 是"软刷新",读不阻塞但可能读到旧值。两者常组合:refreshAfterWrite 保证高频访问的 key 自动更新,expireAfterWrite 作为最大陈旧兜底。

expireAfterWrite 强调"过期淘汰"、refreshAfterWrite 强调"后台刷新"。refresh 读不阻塞但可能旧,expire 强制过期但可能穿透,组合使用平衡新鲜度与体验。

#
★★

15. 缓存版本化(Versioned Cache)与最终一致性 SLA,业务可接受多长时间的缓存不一致?如何度量?

请说明缓存版本化(Versioned Cache)与最终一致性 SLA,以及如何度量缓存不一致时长?

  • 版本化缓存机制
  • 最终一致性 SLA 定义
  • 不一致时长的度量

缓存版本化(Versioned Cache):缓存中存版本号,DB 更新时递增版本,读缓存时比对版本,不一致则回源 DB 重建缓存,防止读旧值。最终一致性 SLA:定义"一个写操作在缓存中可见的最大延迟"(如 5 秒内可见),即缓存与 DB 的滞后上限。度量方法:对账(定期抽样比对缓存与 DB 值,统计不一致存在的时间)、监控写后读延迟(写 DB 后多久缓存反映新值)、日志记录版本更新与缓存刷新时间差。业务可接受的不一致时长取决于业务(强一致业务要求秒级内,展示类可放宽)。

版本化解决"读旧值"的根因,最终一致性 SLA 把一致性要求量化成可度量指标。度量核心是"写后可见延迟"与"对账检出率"。

#
★★

16. 缓存击穿修复方案(互斥锁/逻辑过期)与缓存雪崩的随机过期策略如何组合?

请说明缓存击穿修复方案(互斥锁/逻辑过期)与缓存雪崩的随机过期策略如何组合使用?

  • 击穿与雪崩方案的作用
  • 组合策略
  • 组合的实施要点

缓存击穿与雪崩可组合:针对热点 key 用互斥锁或逻辑过期(击穿)、针对所有 key 用随机过期(雪崩),两者结合:热点 key 用逻辑过期 + 互斥锁异步重建,普通 key 用随机 TTL 错峰过期。组合要点:热点 key 不设物理 TTL(或长 TTL)避免过期,用逻辑过期控制重建;普通 key 用随机 TTL 避免同时过期;缓存重建用互斥锁/异步单飞避免并发回源。整体保证"热点不并发回源 + 批量不错峰过期"。

击穿是"单 key 过期并发",雪崩是"批量 key 过期",组合方案分别治理:热点用锁/逻辑过期串行重建,普通用随机 TTL 错峰。二者互补覆盖不同场景。

#
★★

17. 缓存与数据库的双写一致性,消息队列最终一致?

请说明缓存与数据库的双写一致性如何通过消息队列实现最终一致?

  • 双写与最终一致
  • 消息队列解耦与重试
  • 删除缓存失败的处理

双写一致性通过消息队列实现最终一致:更新 DB 时把"删除缓存/刷新缓存"事件写入消息队列,由消费者异步执行缓存更新,失败则重试,保证最终一致。这样把"DB 更新与缓存更新"解耦,DB 更新成功即提交,缓存更新由 MQ 异步可靠执行(重试、幂等)。相较同步删缓存,MQ 方案能处理删除失败、临时故障,实现最终一致。需注意消息幂等(重复消费不产生脏数据)与顺序(同 key 消息有序)。适合能容忍最终一致的业务。

MQ 方案的核心是"可靠异步 + 重试 + 幂等",把缓存更新从 DB 事务中解耦,解决删除失败问题,最终一致。适合对一致性要求不高的业务。

#

18. 缓存 Key 的设计规范(前缀、版本、TTL 分层)

请说明缓存 Key 的设计规范,包括前缀、版本与 TTL 分层?

  • key 前缀规范
  • 版本号设计
  • TTL 分层设计

缓存 Key 设计规范:统一前缀(如 cache:user:{id},区分业务、便于管理、避免冲突);版本号(如 cache:user:v2:{id},数据结构不兼容时用版本隔离);TTL 分层(不同类型/热度数据设置不同 TTL:热点数据长 TTL 或逻辑过期、普通数据适中 TTL、易变数据短 TTL,并结合随机偏移避免雪崩)。key 命名要可读、可追溯、避免超长,考虑 hash tag 保证集群下同槽。整体规范让缓存可管理、可排查、可分区。

key 规范是缓存工程的基础:前缀区分业务、版本应对结构变更、TTL 分层平衡一致性与命中率。好的规范降低运维与排查成本。