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

【面试题】缓存先删后写如何避免窗口期的旧数据写入缓存

缓存先删后写如何避免窗口期的旧数据写入缓存

方案1:分布式锁控制读写顺序

通过分布式锁(如Redis的SET NX、ZooKeeper等),在更新操作的“删缓存-更数据库”窗口期内,阻塞读请求对缓存的写入,确保数据库更新完成后再允许读请求加载新数据。

流程:
  1. 更新操作

    • 对目标缓存键(如user:100)加写锁(互斥锁,同一时间只有一个更新操作能执行)。
    • 删除缓存(DEL user:100)。
    • 更新数据库(UPDATE user SET ... WHERE id=100)。
    • 释放写锁。
  2. 读请求

    • 检测缓存是否存在:若存在,直接返回;若不存在,尝试获取读锁(与写锁互斥,即写锁未释放时,读锁获取失败)。
    • 若读锁获取失败(说明有更新操作在执行),则等待一段时间(如100ms)后重试,直到缓存存在或读锁获取成功。
    • 若读锁获取成功,从数据库读取数据(此时数据库已更新),写入缓存,最后释放读锁。
优点:
  • 严格保证一致性,避免窗口期的旧数据写入缓存。
  • 适用于对数据一致性要求高的场景(如交易、库存)。
缺点:
  • 分布式锁会增加系统复杂度(需处理锁超时、释放失败等问题)。
  • 可能降低并发性能(读请求需等待写锁释放)。

方案2:延迟双删(Delete Twice)

在“先删后写”的基础上,增加一次延迟删除缓存的操作,清除窗口期可能被写入的旧数据。

流程:
  1. 第一次删除缓存(DEL user:100)。
  2. 更新数据库(UPDATE user SET ... WHERE id=100)。
  3. 延迟一段时间(如500ms,根据业务接口耗时设置)后,第二次删除缓存(DEL user:100)。
原理:
  • 若窗口期内有读请求写入了旧数据(缓存被旧数据填充),第二次延迟删除会将其清除。
  • 后续读请求会重新从数据库读取新数据并写入缓存,最终保证一致性。
优点:
  • 实现简单,无需锁机制,对性能影响小。
  • 适用于并发量不极高、对短暂不一致可容忍的场景(如用户信息、商品描述)。
缺点:
  • 延迟时间难以精确控制(若延迟过短,可能在旧数据写入缓存前执行第二次删除,无效;若过长,会存在短暂不一致)。
  • 极端情况下,第二次删除前若有新的读请求,仍可能读到旧缓存。

方案3:读写锁隔离(Read-Write Lock)

通过读写锁区分“更新操作”和“读请求”的权限,确保写操作执行时,读请求只能等待或读取数据库新数据。

流程:
  • 写锁:更新操作(删缓存+更数据库)期间持有写锁,此时不允许任何读请求写入缓存(但允许读请求直接读数据库,避免阻塞)。
  • 读锁:读请求只有在写锁释放后,才能获取读锁并将数据库数据写入缓存。
具体操作:
  1. 更新操作:

    • 获取写锁 → 删除缓存 → 更新数据库 → 释放写锁。
  2. 读请求:

    • 缓存存在 → 直接返回;
    • 缓存不存在 → 尝试获取读锁:
      • 若写锁未释放(获取读锁失败),直接读数据库(此时数据库可能已更新)并返回(不写入缓存,避免旧数据);
      • 若写锁已释放(获取读锁成功),读数据库(新数据)→ 写入缓存 → 释放读锁 → 返回。
优点:
  • 减少锁对读请求的阻塞(读请求可直接读数据库,不强制等待)。
  • 平衡一致性和性能,适合读多写少的场景。
缺点:
  • 实现较复杂(需设计读写锁的优先级和释放逻辑)。

方案4:版本号机制(Version Control)

给数据库和缓存的数据添加版本号,通过版本号校验避免旧数据覆盖新缓存。

流程:
  1. 数据库表中增加version字段(每次更新+1),缓存中存储(value, version)
  2. 更新操作
    • 删除缓存 → 更新数据库(version+1)。
  3. 读请求
    • 缓存不存在 → 读数据库(获取valuenew_version);
    • 尝试写入缓存时,若缓存此时已被其他请求写入(带有old_version),则比较版本号:
      • new_version > old_version:覆盖缓存(写入新数据+新版本);
      • new_version <= old_version:不写入(避免旧数据覆盖新缓存)。
原理:
  • 即使窗口期内有旧数据写入缓存,后续读请求的新版本数据会覆盖旧版本,最终保证缓存与数据库一致。
优点:
  • 无锁机制,性能好,适合高并发场景。
  • 版本号天然支持分布式环境。
缺点:
  • 需修改数据库表结构(增加版本号字段)。
  • 极端情况下可能存在短暂的版本号不一致(但最终会修正)。

总结与选择建议

  • 强一致性场景(如库存、支付):优先用分布式锁,严格控制读写顺序。
  • 高并发、可容忍短暂不一致:用延迟双删(简单)或版本号机制(性能好)。
  • 读多写少场景:用读写锁隔离,平衡性能和一致性。
http://www.dtcms.com/a/545121.html

相关文章:

  • 扩展名网站最新新闻事件摘抄
  • 网站免费推广方法网站正能量免费推广软件
  • Spring Boot3零基础教程,配置 GraalVM 环境,笔记88
  • TCN-Transformer-LSTM多特征分类预测Matlab实现
  • 进程 线程 协程基本概念和区别 还有内在联系
  • Linux(1)rsyslog(1)基础使用
  • Arbess零基础学习,安装与配置
  • 温州网站建设seo跨境电商平台shopee
  • Rust——Tokio的多线程调度器架构:深入异步运行时的核心机制
  • webpack - 常用的 CSS 加载器(webpack与其常见loader加载器使用方式)
  • PyTorch2 Python深度学习 - 张量(Tensor)的定义与操作
  • 微信小程序开发案例 | 通讯录小程序(上)
  • 做网站要学什么专业大连谷歌seo
  • 深圳做网站公司营口化工网站建设
  • CUDA-GDB(13)——支持的平台及常见问题解决
  • Warp是什么
  • Go语言设计模式:建造者模式详解
  • 外观模式:复杂系统的统一入口
  • 个人做网站模版是否有人买如何引用网站图片
  • 库卡机器人通讯-EtherKRL-XML格式
  • 自动驾驶技术全景解析:从感知、决策到控制的演进与挑战
  • 建站平台在线提交表格apache 配置网站
  • 前端HTTP,JavaScript,网络
  • 汽车充电站光储充项目方案
  • 为什么Service层和Mapper层需要实现interface接口
  • 杭州做网站公司排名建筑工人招工网
  • 服务RejectedExecutionException问题对Semaphore的思考
  • GitLab 钩子 + Jenkins 自动化构建项目
  • 安装Pytorch GPU+CPU版本【通过本地安装解决无法使用pip指令下载问题】
  • DevExpress WinForms中文教程:Data Grid - 如何自定义汇总函数?