极高 困难
缓存与数据库一致性#
一句话答案#
Cache Aside 模式:读时先查缓存未命中查 DB 写缓存,写时先更新 DB 再删除缓存,删而非更新避免并发覆盖。更可靠的方案用 Canal + MQ 异步删缓存。
核心要点
Cache Aside Pattern(旁路缓存)#
读流程: 先读缓存 → miss → 读 DB → 写缓存 写流程: 先更新 DB → 再删除缓存
为什么删而不更新缓存?
- 更新可能被并发覆盖(A 更新缓存后 B 立刻覆盖,但 B 的 DB 数据比 A 旧)
- 懒加载——不一定有人来读,白算了
- 缓存值可能是多表聚合结果,更新成本高
”先删缓存还是先更 DB”的争论#
| 方案 | 不一致场景 | 概率 |
|---|---|---|
| 先删缓存→更 DB | 线程 A 删缓存,线程 B 读 miss 回填旧值,A 再更 DB → 缓存是旧值 | 高(读远多于写) |
| 先更 DB→再删缓存 | 线程 A 读 DB 旧值(缓存刚好失效),线程 B 更 DB 并删缓存,A 再回填旧值 → 缓存是旧值 | 极低(需要读比写慢) |
结论: “先更 DB→再删缓存”更安全,不一致窗口更短。
延迟双删#
1. 删除缓存
2. 更新 DB
3. 延迟 N 毫秒后再次删除缓存(覆盖可能的旧值回填)plaintext延迟时间怎么定?
N = 主从同步延迟 + 一次读请求耗时 + 冗余(如 100ms)
通常 300ms ~ 1splaintext问题:
- 延迟时间难以精确计算
- 第二次删除失败怎么办?→ 需要重试机制
- 增加了系统复杂度
Canal + MQ 方案(推荐)#
应用更新 DB → MySQL binlog 变更
↓
Canal 监听 binlog
↓
发送删除消息到 MQ(RocketMQ / Kafka)
↓
消费者消费消息 → 删除 Redis 缓存
↓
删除失败 → MQ 重试(最多 N 次)
↓
重试耗尽 → 告警 + 人工处理plaintext为什么比延迟双删更可靠?
| 维度 | 延迟双删 | Canal + MQ |
|---|---|---|
| 可靠性 | 依赖延迟时间估算 | 基于 binlog,事件驱动 |
| 重试 | 需自己实现 | MQ 原生支持重试 + 死信队列 |
| 侵入性 | 业务代码耦合删除逻辑 | 业务无感,独立组件监听 |
| 延迟 | 固定延迟不精确 | binlog 近实时(毫秒级) |
| 复杂度 | 低 | 高(需部署 Canal + MQ) |
Canal 挂了怎么办?
- Canal 支持 HA(主备切换)
- binlog 有位点记录,重启后从断点继续
- 兜底:定时全量对账任务(每小时比对 DB 和缓存热点数据)
MQ 消费失败怎么办?
- 重试 3-5 次(指数退避)
- 重试耗尽进死信队列
- 死信队列触发告警 → 人工处理
- 缓存设 TTL 作为最终兜底(即使删除失败,TTL 到期后自动过期)
最终一致 vs 强一致的业务权衡#
| 场景 | 一致性要求 | 方案 |
|---|---|---|
| 商品详情页 | 最终一致(秒级延迟可接受) | Cache Aside + TTL |
| 库存数量 | 强一致 | 不走缓存,直接查 DB / Redis 做主存储 |
| 用户信息 | 最终一致 | Canal + MQ 异步删除 |
| 排行榜 | 最终一致(分钟级可接受) | 定时刷新 |
核心原则: 大部分业务最终一致就够了,为强一致付出的成本(性能、复杂度)往往不值得。
面试回答(2分钟版)
缓存一致性我用 Cache Aside 模式,写时先更新 DB 再删除缓存——先更 DB 再删缓存比先删缓存更安全,因为”读 DB 比写 DB 慢”的概率极低。删除失败的问题,简单场景用延迟双删(延迟 = 主从同步时间 + 读耗时 + 冗余,通常几百毫秒),但时间难精确且需自己实现重试。更可靠的方案是 Canal + MQ:Canal 监听 MySQL binlog,变更事件发到 MQ,消费者负责删除缓存。失败了 MQ 自动重试,重试耗尽进死信队列告警。业务代码完全无感,缓存删除逻辑解耦。最终兜底是给缓存设 TTL——即使所有机制都失败了,TTL 到期后数据也会刷新。不过大部分业务最终一致就够了,库存这种需要强一致的就不走缓存。
追问与易错
追问方向:
- “延迟双删的延迟时间怎么定?”→ 主从延迟 + 读耗时 + 冗余,通常 300ms-1s
- “Canal 是什么?原理?”→ 伪装为 MySQL 从库,读取 binlog 解析出数据变更事件
- “这套方案能保证强一致吗?”→ 不能,只保证最终一致。强一致需要分布式锁或不用缓存
- “缓存和 DB 都更新失败怎么办?”→ DB 更新失败直接回滚不删缓存;缓存删除失败 MQ 重试
易错点:
- ❌ “Cache Aside 保证强一致”——只保证最终一致
- ❌ “延迟双删万能”——时间难定、删除可能失败、增加延迟
- ❌ 不设 TTL 兜底——TTL 是最后一道防线