Skip to content

双写一致

一、基础定义

双写一致性:MySQL数据发生增删改时,同步维护Redis缓存,保证DB与缓存数据相同,避免缓存遗留旧脏数据

读操作通用逻辑

请求优先查询Redis:

  1. 缓存命中 → 直接返回数据;
  2. 缓存未命中 → 查询MySQL,结果回写入Redis并配置过期时间后返回。

二、方案1:延时双删(同步方案,中小项目常用)

执行顺序

第一次删缓存 → 更新MySQL → 延时等待 → 第二次删缓存

  1. 首次删缓存:清空旧缓存,防止后续读请求命中过期数据;
  2. 更新数据库:落地最新数据至MySQL;
  3. 延时二次删缓存:等待并发读请求查库回填缓存、主从数据同步完成,删除并发写入的脏缓存。

缺点:仍存在极小概率脏数据,仅能实现最终一致,无法保证强一致。

三、方案2:读写锁方案(读多写少业务,强一致性首选)

依托Redisson读写分布式锁,遵循规则:读读共享、读写互斥、写写互斥

写流程(加排他WriteLock)

加排他锁 → 修改数据库 → 删除缓存 → 释放锁 加锁期间所有读请求阻塞,避免改库过程中旧数据回填缓存。

读流程(加共享ReadLock)

加共享锁 → 查询缓存,缓存失效则查库并更新缓存 → 释放锁 多个读线程可同时加读锁并发读取,互不阻塞。

优点:实现强数据一致性; 缺点:写操作会阻塞读请求,高并发写场景性能较差,适合商品详情、资讯等读多写少业务。

四、方案3:MQ异步更新缓存(分布式中大型项目,异步最终一致)

流程

  1. 业务服务修改数据,先落库MySQL,再向MQ发送缓存更新消息;
  2. 独立缓存服务消费MQ消息,执行删除或更新Redis缓存操作。

关键点:需保障MQ消息可靠性,搭配生产者确认、消息持久化、死信队列,避免消息丢失造成缓存更新失败。

优点:业务线程无阻塞,服务解耦,任务失败支持重试; 缺点:额外依赖MQ中间件,整体架构复杂度提升。

五、方案4:Canal监听Binlog(零侵入业务,大厂主流)

流程

  1. 业务服务正常修改并写入MySQL,业务代码无需操作缓存、发送消息;
  2. Canal伪装成MySQL从节点,实时监听Binlog二进制日志,捕获数据变更事件;
  3. Canal推送变更数据,由缓存服务接收并更新/删除Redis缓存。

优点:完全解耦业务代码,数据变更与缓存更新互不影响; 缺点:引入Canal组件,增加运维成本。

六、四种方案选型总结

方案适用场景一致性性能
延时双删单体小型项目、低并发最终一致优秀
读写锁读多写少、要求强一致(商品/资讯)强一致写阻塞读,写多场景性能差
MQ异步中大型分布式项目、高并发最终一致优秀,异步解耦
Canal Binlog大型分布式、零代码侵入需求最终一致最优,业务无感知

七、面试高频问题解析

  1. 问:什么是缓存与数据库双写一致性?

    答: 对MySQL执行增删改操作时,同步维护Redis缓存,让数据库和缓存的数据保持一致,防止缓存中存在过期脏数据。常规读取逻辑为先查缓存,未命中再查询数据库并回填缓存。

  2. 问:简述延时双删的执行流程和优缺点?

    答: 流程为先删除缓存,再更新数据库,等待一段时间后再次删除缓存。该方案实现简单,适合中小项目;但无法做到强一致性,依旧存在产生脏数据的可能,仅能保证最终一致。

  3. 问:读写锁方案如何保证缓存强一致性,适用什么场景?

    答: 使用分布式读写锁,读写互斥、写写互斥,写数据时加排他锁阻塞所有读请求,杜绝旧数据回填缓存。该方案可以实现强一致,缺点是写操作会阻塞读请求,更适合商品、资讯这类读多写少的业务。

  4. 问:MQ异步更新缓存和Canal监听Binlog方案有什么区别?

    答: MQ方案需要业务代码主动发送消息,依赖消息队列实现异步更新;Canal通过监听MySQL二进制日志感知数据变更,业务代码完全无需改造,侵入性更低。二者都属于异步最终一致方案,Canal更适合追求代码解耦的大型项目。

  5. 问:四种双写一致性方案该如何选型?

    答: 小型单体低并发项目选用延时双删;读多写少且要求强一致选用读写锁;中大型高并发分布式项目选用MQ异步方案;大型项目、要求业务代码零侵入则使用Canal监听Binlog。

Powered by VitePress 1.6.4 | 持续更新中