Java 面试题

缓存和数据库一致性:Cache-Aside vs 双写vs Canal

"Redis缓存和数据库怎么保证一致性?"

为什么面试官会问这道题

这是Redis面试里最微妙的一道题,"标准答案"取决于业务对一致性的容忍度,连资深工程师都容易翻车。是真的修过线上缓存不一致事故,还是只背过理论,这道题能看出来。

如何回答

  1. 1

    开场要诚实:两个独立系统之间做不到完美强一致,目标是"有界的过期",不是"零过期读"。

  2. 2

    讲三种经典模式——Cache-Aside(旁路缓存,读从缓存miss走DB回填,写先更DB再删缓存)、Write-Through(写穿,应用写缓存,缓存同步写DB)、Write-Behind(应用写缓存,缓存异步刷DB,吞吐高但有丢数据风险)。

  3. 3

    重点讲Cache-Aside里"删还是改"——写时应该删缓存而不是更新。更新会因为并发顺序被写入旧值;删除+下次miss重建更简单安全。

  4. 4

    讲先删缓存的并发坑:线程A更新DB,线程B读miss从从库读到旧值(或读到A提交前的快照),B把旧值写回缓存。解法:延时双删(删缓存、更DB、sleep、再删一次)或用Canal/Debezium订阅MySQL binlog异步失效。

  5. 5

    严格场景收尾:Canal订阅binlog,通过Kafka把变更事件发给消费者,消费者去失效/更新Redis。这种解耦业务代码且有重试保证,是生产黄金方案。

参考回答示例

实话说,两个独立系统之间不可能做到完美一致,没有2PC的情况下只能做"最终一致+有界的stale"。最常见的是Cache-Aside旁路缓存:读先查Redis,miss时从MySQL加载并回填;写时更新MySQL然后删除Redis。两个关键选择:第一,"删缓存还是改缓存?"必须删。如果改,并发更新下会写入旧值——A改完缓存B也改完缓存,但A的DB写在B之后,缓存最后停在A的旧值上。删除+懒加载天然避开这个问题。第二,"先改DB还是先删缓存?"工业界主流是先改DB后删缓存。先删缓存的风险是DB写失败时缓存已空,要有回滚机制。但先改DB也有并发坑——线程A更新DB,另一个线程B读miss,去读DB(主从架构下可能读到未同步的从库),拿到旧值,然后在A删除缓存之前把旧值回填到缓存里。两种常见解法:延时双删——删缓存、更DB、sleep 500毫秒等并发读落定、再删一次;基于binlog的失效——用Canal或Debezium订阅MySQL binlog,把变更事件丢到Kafka,消费者负责失效Redis。Binlog方案是严格场景的金标准,因为失效有重试保证,而且业务代码不用管缓存同步。对一致性要求没那么苛刻的业务,纯TTL+Cache-Aside就够了——设5分钟TTL,业务接受最坏5分钟的过期数据。关键是按业务对stale的容忍度来选方案,不是强上最严格的。

实用技巧

  • 开场说"必须删不是改",是差异化信号。

  • 能说出延时双删和Canal-based方案,是资深岗的加分项。

  • 一定要承认trade-off,面试官反感"某个方案永远最优"。

  • 忘了Canal/Debezium的名字,即答侠可以实时补词。

常见问题

为什么不能用2PC?

Redis不支持XA协议,且2PC会大幅增加延迟和失败模式。业界都是"最终一致"。

TTL能解决一致性吗?

可以限制stale时长。如果业务接受5分钟过期,纯TTL+Cache-Aside是最简单正确的方案。

Write-Through什么时候用?

读多写少的场景。写会变慢因为同时落缓存和DB,所以只在写不频繁且读必须fresh的场景用。

面试时担心忘词?即答侠实时助你

即答侠 AI 实时监听面试对话,自动识别问题并即时生成回答建议——无感辅助,让你从容应对每一道题。

免费试用即答侠