缓存一致性:从面试“翻车”到系统设计高手
🎭 面试场景还原
候选人:“缓存一致性?这个我研究过,应该是先删缓存,再更新数据库”
面试官:“如果删缓存成功,但更新数据库失败了呢?”
候选人:“呃...那换成先更新数据库,再删缓存?”
面试官:“如果数据库更新成功,但删缓存失败了呢?”
候选人:“这...”(开始冒汗)
面试官:“我们聊聊缓存一致性的几种方案吧...”
📊 4种核心方案对比
方案一:先更新DB,后删缓存(Cache-Aside)
┌─────────┐ 1.更新DB ┌─────────┐ 2.删除缓存 ┌─────────┐
│ 客户端 │ ────────────> │ 数据库 │ ────────────> │ 缓存 │
└─────────┘ └─────────┘ └─────────┘
优点:
- 简单直接,业界常用
- 保证数据最终一致性
问题:
- 删缓存失败 → 缓存里是旧数据
- 并发读可能导致脏数据:
线程A: 更新DB (value=2) 线程B: 读缓存(没命中) → 读DB(读到旧值1) → 写缓存(1) 线程A: 删除缓存 → 结果:缓存=1 (脏数据)
方案二:先删缓存,后更新DB
┌─────────┐ 1.删除缓存 ┌─────────┐ 2.更新DB ┌─────────┐
│ 客户端 │ ────────────> │ 缓存 │ ────────────> │ 数据库 │
└─────────┘ └─────────┘ └─────────┘
优点:
- 确保后续读请求一定从DB读最新数据
问题:
- 更新DB失败 → 缓存已删,下次读会穿透到DB
- 并发问题更严重:
线程A: 删除缓存 线程B: 读缓存(没命中) → 读DB(旧值) → 写缓存(旧值) 线程A: 更新DB(新值) → 结果:缓存=旧值,DB=新值
方案三:延时双删
┌─────────┐ 1.删除缓存 2.更新DB 3.等待 4.再删缓存
│ 客户端 │ ────────────> ──────────> [延时] ──────────>
└─────────┘
执行步骤:
- 先删除缓存
- 更新数据库
- 等待一段时间(比如500ms)
- 再次删除缓存
为什么等待?
- 给并发读请求时间写完旧的脏数据
- 等待时间 = 一次读耗时 + 一次写耗时
优点:
- 显著降低脏数据概率
缺点:
- 延迟高,性能影响
- 等待时间不好确定
- 第二次删除可能失败
方案四:监听binlog(推荐方案)
┌─────────┐ 写DB ┌─────────┐ binlog ┌─────────┐ 删缓存
│ 客户端 │ ────────> │ MySQL │ ──────────> │ Canal │ ────────>
└─────────┘ └─────────┘ └─────────┘
异步消息队列 ──────────> 删除缓存
工作原理:
- 正常更新数据库
- 数据库的binlog(变更日志)被监听
- 通过消息队列异步删除缓存
优点:
- 解耦业务和缓存操作
- 保证最终一致性
- 可重试机制
缺点:
- 架构复杂
- 有一定延迟
🏆 面试最佳回答模板
“缓存一致性没有银弹,需要根据业务场景选择”
第一步:明确需求
- 需要强一致性还是最终一致性?
- 读多写少还是写多读少?
- 数据敏感性如何?
第二步:分层回答
基础方案(中小系统)
“对于读写比不高、一致性要求不强的场景,我推荐 先更新数据库,再删除缓存,配合以下几点保证:
- 重试机制:删除失败时重试3次
- 设置缓存过期时间:兜底方案,比如30分钟
- 记录操作日志:用于问题排查和修复”
进阶方案(大型系统)
“对于高并发、一致性要求高的场景,我建议:
- 监听binlog + 消息队列 作为主方案
- 延时双删 作为补充
- 多级缓存策略:本地缓存 + Redis,设置不同过期时间
- 补偿任务:定时扫描数据库和缓存差异,修复不一致”
第三步:展示设计思维
“我还会考虑:
- 降级方案:缓存大面积失效时如何保护DB
- 监控告警:缓存不一致率超过阈值时告警
- 灰度发布:新策略先在小流量验证”
🎯 不同业务场景建议
场景一:用户个人信息(弱一致性可接受)
- 方案:先更新DB,后删缓存 + 缓存过期时间(30分钟)
- 理由:用户自己修改,看到旧信息一会儿影响不大
场景二:商品库存(强一致性要求)
- 方案:监听binlog + 同步更新缓存
- 理由:超卖会直接造成经济损失,必须强一致
场景三:文章阅读量(最终一致性可接受)
- 方案:先更新DB,异步更新缓存
- 理由:阅读量有少量误差可以接受,性能优先
场景四:秒杀库存(超高并发)
- 方案:
- Redis原子操作扣减库存
- 异步同步到数据库
- 库存变化时通过消息队列更新缓存
- 理由:性能压倒一切,允许短暂不一致
💡 面试加分项
1. 主动提出边缘情况
“除了刚才讨论的,还有一些特殊情况需要考虑:
- 分布式锁的使用:防止并发更新
- 缓存穿透/雪崩的防护:空值缓存、热点Key分散
- 多级缓存同步:本地缓存如何更新”
2. 展示实战经验
“我在上个项目中遇到过缓存不一致问题,我们的解决方案是:
- 先采用延时双删,但发现延迟太高
- 改为binlog方案,不一致率从0.1%降到0.001%
- 增加了缓存版本号,支持灰度回滚”
3. 提出未来优化
“如果进一步优化,我会考虑:
- 引入缓存数据中心(CDC)统一管理
- 使用一致性哈希减少缓存抖动
- 实现缓存预热和热点探测”
📝 一句话总结
“缓存一致性是在性能、复杂度、一致性之间的权衡,没有完美方案,只有适合场景的方案”
记住:面试官不只是想听正确答案,更想看到你的思考过程和设计能力。从简单方案说起,逐步深入,展示你处理复杂问题的能力,这样即使最初回答不完美,也能最终赢得认可!