当前位置: 首页 > news >正文

Redis延时双删详解

先来看一段代码:

// 写操作:更新DB + 删除缓存(延时双删)@Transactionalpublic void updateUser(User user) {// 1. 先更新数据库userMapper.update(user);String key = buildCacheKey(user.getId());// 2. 立即删除缓存redisTemplate.delete(key);// 3. 延时双删,避免并发脏读executor.submit(() -> {try {Thread.sleep(500); // 延迟0.5秒redisTemplate.delete(key);} catch (InterruptedException ignored) {}});}

不禁要问:Tell me why?

下面用实例时间线 + 原因 + 解决思路 来讲,越具体越直观。

核心结论(先给个一句话)

当写操作 先更新 DB 再删缓存 时,有可能会有并发的读操作在你删缓存之前或之后去读到旧数据并把旧值写回缓存,导致缓存里最终是旧(脏)数据。延时双删就是在「立刻删一次」之后再等短时间再删一次,把这些并发读写回来的脏值再刪掉,增加最终一致性的概率。


用具体时间线说明(最常见的一种情况:DB 主从复制 / 读副本延迟)

设:

  • 旧值为 O,新值为 N

  • 写线程为 W,读线程为 R。

  • 初始:DB 主/从还没同步,cache 可能不存在(miss)或已过期。

时间线举例:

t0: DB 主 = O,缓存 = (不存在或 O)
t1: W 开始执行 update -> 把 DB 主 更新为 N (还没完成从库同步)
t2: R 发起读取:cache miss -> 去读 DB,但正好读到的是从库(还没同步到 N) -> 读到 O -> R 将 O 写回缓存(cache = O)
t3: W 删除缓存(第一次删除),此时如果 R 的写已经发生,缓存可能已被设成 O;如果 R 的写还在稍后发生,则缓存又会被覆盖成 O
t4: W 启动延时任务(比如 500ms 后)再删一次缓存(第二次删除)
t5: 延时删除生效,删除掉 R 写回来的旧值 O(cache 变为空),后续读会从 DB(现在 DB 主有 N)回填 N

关键点:

  • R 能读到 O 的原因通常是读到的不是刚更新的主库数据(例如读到了尚未同步的从库),或者 R 在很早就已开始的长事务里读取到了旧快照。

  • 如果 R 在你第一次删除缓存 之后 写回了旧值(因为读的是旧数据),那么单次删除就“失效”了 —— 缓存又被旧值覆盖。延时第二次删除就是为了覆盖这个窗口期,把旧值再删掉一次,保证最终一致。


什么时候更容易出现这种问题?

  • 数据库主从是异步复制(从库有延迟)。

  • 读走的是从库以减轻主库压力(常见的读写分离架构)。

  • 读操作处于旧事务或慢查询,读到旧快照。

  • 系统中有很多并发读/写,时间窗口容易被触发。


为什么不是“永远有效”的万灵药?

  • 延时双删是经验性/幂等防御措施,能显著降低脏数据概率,但不是数学上绝对保证。

  • 如果延迟时间选择不当(太短可能没覆盖到窗口,太长会增加额外抖动和删除开销),效果会受限。

  • 如果某些读写非常慢或复制延迟很大,单次延时可能不足——需要配合重试或更强的设计。


更稳妥的替代或补充方案(按强一致性程度排序)

  1. 分布式锁(写的时候锁住该 key,阻止并发的读写回填)——严格但有性能代价、死锁风险要处理。

  2. 版本号/时间戳:把版本或 updated_at 一并写入缓存;读时比对 DB(或用乐观校验),如果缓存版本落后则刷新。

  3. 写入缓存而不是删除(Write-through / Write-behind):写操作同时更新 cache(或由 cache 组件同步到 DB),避免 delete->race,但实现复杂。

  4. 把删除操作放到可靠队列(MQ):DB 更新后把“删缓存”操作发到 MQ,确保最终能删除(防止进程宕机导致没删)。

  5. 用强一致性存储(如把热点放在强一致性的 DB/服务):牺牲性能换一致性。

  6. 本地缓存 + pub/sub 通知(多实例一致性):更新时通知其它实例清本地缓存,减少跨实例脏数据。


实际工程中的改进建议(实践可落地)

  • 采用 更新 DB -> 立即删除 cache -> 延时再删除一次(例如 200ms–1s,根据你系统的复制延迟/并发情况调参)。

  • 把第二次删除做成异步可重试的任务(线程池/定时任务/发 MQ),避免删除失败丢失。

  • 对关键(强一致性必须)数据使用分布式锁或版本号策略。

  • 对缓存回填(读端)做幂等处理或限制:如果读取 DB 的结果比写入缓存的旧(检查 version),就不要写回缓存。

  • 在高并发场景做压测,测出“脏数据窗口”,据此设置延时值与重试策略。


小结(一句话回顾)

延时双删是一个 工程化的折中:在 update DB -> delete cache 的基础上,再等一小段时间再删一次,以清除那些在你删除后、读并写回旧值的并发读造成的脏缓存。它降低了脏数据概率,但不是绝对保证——对严格一致性的场景应考虑锁、版本号或强一致性方案。


文章转载自:

http://xeuUQuCV.tLbhq.cn
http://EyV0H10E.tLbhq.cn
http://bdaFO88g.tLbhq.cn
http://qIcIt6K6.tLbhq.cn
http://uV6BTnKG.tLbhq.cn
http://peb9NBBe.tLbhq.cn
http://k3Nlec8C.tLbhq.cn
http://jbbccPNt.tLbhq.cn
http://PxtopetM.tLbhq.cn
http://En91ZHGV.tLbhq.cn
http://jSowIuHE.tLbhq.cn
http://gRwjm5jI.tLbhq.cn
http://jpRadITT.tLbhq.cn
http://eFhcWQtn.tLbhq.cn
http://wjeiEisX.tLbhq.cn
http://QoqHI9BS.tLbhq.cn
http://EHXliiO6.tLbhq.cn
http://iZcHQt3x.tLbhq.cn
http://5dlnBWGs.tLbhq.cn
http://wXXoGZTU.tLbhq.cn
http://1H8ubNSM.tLbhq.cn
http://GvocYoRf.tLbhq.cn
http://oJGWIUai.tLbhq.cn
http://RmPJJmMN.tLbhq.cn
http://7G1zy6Sw.tLbhq.cn
http://5HJqdncB.tLbhq.cn
http://RvZl6fdU.tLbhq.cn
http://oOOrtNGT.tLbhq.cn
http://WL5hfNaa.tLbhq.cn
http://8kBeBDdE.tLbhq.cn
http://www.dtcms.com/a/380851.html

相关文章:

  • 关于商品数据采集的方式和注意事项
  • linux C 语言开发 (七) 文件 IO 和标准 IO
  • Java Servlet 完全解析:构建高效 Web 应用的关键技术
  • 【GIS】Cesium:快速加载地图
  • 【硬件-笔试面试题-92】硬件/电子工程师,笔试面试题(知识点:米勒效应,米勒平台)
  • 定点巡检、实时巡检详解和两者的区别对比
  • AI 编程工具选型速览(2025-09 版)
  • 2025年渗透测试面试题总结-66(题目+回答)
  • DOTA-Cys-Tyr-Leu-Ala-Ser-Arg-Val-His-Cys(一对二硫键)
  • ARPO: End-to-End Policy Optimization for GUI Agents with Experience Replay
  • 数模电2,9.12
  • 51c大模型~合集181
  • 【硬件-笔试面试题-89】硬件/电子工程师,笔试面试题(知识点:线性稳压电源LDO的效率计算)
  • Docker基础篇03:Docker常用命令
  • 【数据结构与算法Trip第3站】双指针
  • html实现右上角有个图标,鼠标移动到该位置出现手型,点击会弹出登录窗口。
  • mqtt学习笔记
  • C# DataGridView表头自定义设置全攻略
  • 《深入理解Java虚拟机》第三章读书笔记:垃圾回收机制与内存管理
  • 二叉树的最大深度
  • MySQL数据库-02(SQL语言基础)
  • Java POI实现对docx文件搜索指定文本进行批注/评论
  • Hugging Face NLP课程学习记录 - 3. 微调一个预训练模型
  • Java IO流(字节流和字符流)
  • Python 操作Office的PPT、Word、Excel,同时兼容WPS
  • SW - 剖面视图不显示剖面的显示选项
  • 基于STM32设计的智能蜂箱监测系统设计
  • 将Ansible与这些监控工具集成,实现自动化运维
  • USB3.0 Type C IO介绍
  • cuda编程笔记(19)-- Transformer注意力机制的实现