redis面试:如何保证双写一致

一、实际问题

在实际开发中,我们通常会用 Redis 作为缓存来加速数据读取。但当数据需要更新时,就面临一个经典问题:应该先删除缓存,还是先更新数据库?

遗憾的是,无论选择哪种顺序,在并发场景下都可能产生脏数据。

以"先删缓存,再更新数据库"为例,考虑以下时序:

  1. 线程 A 删除缓存
  2. 线程 B 读取缓存未命中,从数据库读到旧值,写入缓存
  3. 线程 A 更新数据库为新值

此时数据库中是新值,而缓存中却是线程 B 写入的旧值,数据不一致就此产生。反过来"先更新数据库,再删缓存"同样存在类似的竞态窗口。

由于线程调度的不确定性,这类问题在高并发下并非小概率事件。要解决它,常见的思路有两种:延迟双删读写锁,各有适用场景。

二、延迟双删

复制代码
1. 删除 Redis 缓存          ← 第一次删
2. 更新数据库
3. 发送一条延时消息到 MQ或者Canal监听binlog日志
4. MQ 消费者收到消息后再次删除缓存  ← 第二次删

延迟双删的核心思想很简单:既然一次删除缓存无法保证一致性,那就删两次,中间给数据库更新留出时间窗口。

基本流程:

  1. 删除 Redis 缓存 ← 第一次删除
  2. 更新数据库
  3. 发送一条延迟消息到 MQ(或通过 Canal 监听 binlog)
  4. MQ 消费者收到消息后再次删除缓存 ← 第二次删除

第一次删除是为了让后续读请求能走到数据库;更新数据库后,再补一次删除,把可能在第一步和第二步之间被脏数据回填的缓存清理掉。

两种实现方式:

  • MQ 延迟消息:业务代码中,更新数据库后向消息队列发送一条延迟消息,消费者到期后执行缓存删除。常用 RocketMQ 的延迟消息或 RabbitMQ 的 TTL + 死信队列来实现。
  • Canal 监听 binlog :Canal 伪装为 MySQL 的从节点,通过读取 binlog 日志感知数据变更,自动触发缓存删除。这种方式的优势在于不需要修改业务代码,对应用层完全透明。

两个需要注意的点:

第一,第二步发送的应该是延迟消息而非立即删除。这是为了给数据库主从复制留出同步时间------如果主库刚写完、从库还没同步,此时删缓存后读请求打到从库,读到的可能还是旧值,缓存又会被旧数据回填。

第二,延迟双删并不能做到强一致,两次删除之间仍然存在一个短暂的不一致窗口。如果业务对一致性要求不高(比如缓存热点商品数据、文章浏览量等),这种方案完全够用,且实现成本低、性能影响小。

三、读写锁

如果对数据一致性有严格要求,延迟双删的短暂不一致窗口是不可接受的,这时可以考虑读写锁方案。

Redis 提供了两种锁的语义:

  • 读锁(共享锁):多个线程可以同时持有读锁进行读操作,但读锁存在时,写操作必须等待。
  • 写锁(排他锁):一个线程持有写锁后,其他线程的读操作和写操作全部阻塞。

使用方式:

  • 读数据时加读锁,从缓存读取;缓存未命中则加写锁,从数据库加载并回填缓存。
  • 写数据时加写锁,先更新数据库,再删除(或更新)缓存。

读写锁能够完全保证数据一致性,因为同一时刻要么只有读操作在并发执行,要么只有一个写操作在执行,从根本上消除了竞态条件。

代价是性能------写操作会阻塞所有读写请求,在读多写少的场景下影响尤为明显。因此,读写锁适合对一致性要求极高的业务,比如优惠券计算、订单交易、库存扣减等场景。

四、方案对比

延迟双删 读写锁
一致性 最终一致,存在短暂窗口 强一致
性能 影响小,异步删除 写操作阻塞读写,性能下降明显
实现复杂度 中等(需引入 MQ 或 Canal) 较低(Redis 原生支持)
适用场景 热点数据、一致性要求不高的读多场景 交易、库存等强一致性场景

实际选型时,不需要非此即彼。同一个系统中,对不同业务模块可以根据一致性要求分别采用不同策略------大部分读场景用延迟双删,关键写场景用读写锁,这样能在一致性和性能之间取得较好的平衡。

相关推荐
水无痕simon1 小时前
6 数据库同义词
数据库
杜子不疼.2 小时前
【Qt初识】信号槽(三):机制意义、断开连接与 Lambda 表达式
开发语言·数据库·qt
BomanGe13 小时前
NSK W1501FA高速精密滚珠丝杠技术手册
数据库·经验分享·规格说明书
kiki-bf3 小时前
可用的mysql本地安装教程
数据库·mysql
多巴胺梦想家4 小时前
数据库恢复技术:当灾难来临
网络·数据库·oracle
北冥you鱼4 小时前
Go 语言初始化 MySQL 数据库实战指南:从零到生产环境部署
数据库·mysql·golang
SelectDB5 小时前
云数仓费钱?用 SelectDB Serverless 三步配出秒级弹性,最高降本 70%
数据库·云原生·数据分析
time展天15 小时前
Dddify:给 ASP.NET Core 项目一套轻量、清晰、可落地的 DDD 基础设施
数据库·后端·asp.net
杜子不疼.5 小时前
【Qt初识】工程起步:项目创建、代码解读与对象树
数据库·c++·qt