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

详解Redis数据库和缓存不一致的情况及解决方案

数据库与缓存不一致是分布式系统中常见问题,本质是数据在缓存层和存储层出现版本差异

一、并发写操作导致不一致(最常见)

  • 场景描述

    • 线程A更新数据库 → 线程B更新数据库 → 线程B更新缓存 → 线程A更新缓存

    • 结果:缓存中存储的是线程A的旧数据

  • 发生条件

  • 解决方案

    • 分布式锁:对同Key的写操作加锁

    • 串行化队列:将写请求放入MQ顺序执行

二、先更新数据库后删除缓存失败(Cache-Aside模式)

  • 场景描述

    • 更新数据库成功

    • 删除缓存失败(网络抖动/Redis超时)

    • 结果:缓存中残留旧数据

  • 关键代码风险点

    public void updateData(Data data) {db.update(data);          // 步骤1:数据库更新成功cache.delete(data.getId()); // 步骤2:缓存删除失败!
    }
  • 解决方案

    • 重试机制

      void deleteWithRetry(String key, int maxRetry) {int retry = 0;while (!cache.delete(key) && retry++ < maxRetry) {Thread.sleep(50);}
      }
    • 异步补偿:通过Binlog监听(如Canal)触发二次删除

三、主从延迟导致脏读(读写分离架构)

  • 场景描述

    • 主库更新成功 → 删除缓存

    • 读请求从未同步的从库读取旧值 → 回填缓存

    • 结果:缓存被旧数据污染

  • 解决方案

    • 延迟双删

      db.update(data);          // 更新主库
      cache.delete(key);        // 首次删除
      Thread.sleep(500);       // 等待主从同步
      cache.delete(key);        // 二次删除
    • 强制读主库:对一致性要求高的查询直连主库

四、缓存过期时高并发读(Cache Miss风暴)

  • 场景描述

    • 缓存过期瞬间涌入大量读请求

    • 请求1查DB → 请求2查DB → ... → 请求N查DB

    • 多个线程同时回填缓存(可能乱序)

    • 结果:缓存可能被中间状态数据覆盖

  • 极端案例

    • 请求1读取到旧值V1,回填耗时久

    • 请求2读取新值V2并先完成回填

    • 请求1最终将V1写入缓存(覆盖V2)

  • 解决方案

    • 互斥锁重建:仅允许一个线程重建缓存

    • 逻辑过期:物理缓存永不过期,通过逻辑时间控制

五、批量操作与部分失效

  • 场景描述

    • 场景1:批量更新数据库成功,但部分缓存删除失败

    • 场景2:分页查询缓存无法感知单条数据变更

    • 结果:缓存中存在部分脏数据

  • ​​​​​​​典型案例

    • 商品列表页缓存无法感知单个商品价格变更

    • 订单列表缓存未失效时,订单状态已更新

  • 解决方案

    • 缓存维度拆分:按查询条件设计缓存Key

    • 增量广播:通过消息队列通知相关缓存失效

    • 短过期时间:对聚合查询设置更短TTL

六、跨服务数据变更

  • 场景描述

    • 服务A更新数据库 → 服务B的缓存未失效

    • 原因:服务间缺乏缓存协同机制

    • 结果跨服务缓存残留旧数据

  • ​​​​​​​微服务常见问题

  • 解决方案

    • 领域事件通知:通过消息队列(Kafka/RabbitMQ)广播变更

    • 统一缓存层:所有服务通过SDK操作缓存,SDK统一处理失效

七、终极解决之道:取舍策略

        根据业务需求选择合适的一致性级别:

策略一致性强度性能影响适用场景
先删缓存再更新DB写少读多
先更新DB再删缓存通用方案(需重试)
双写+事务金融交易
延迟监听(Binlog)最终一致高并发写场景
忽略不一致+短TTL允许短暂脏读的业务

         重要原则:不要尝试绝对强一致,除非接受性能断崖式下降。通常建议采用 "更新DB + 延迟双删 + 重试队列" 的组合策略。

http://www.lqws.cn/news/480115.html

相关文章:

  • sc4336p硬件设计上电时序
  • DeepSeek技术解析:开源大模型的创新突围之路
  • 基于Python Websockets的客户端程序,能够连接服务端、处理ping/pong、发送订阅请求并保持长连接
  • 《算法笔记》之二(笔记)
  • 基于split-Bregman算法的L1正则化matlab仿真,对比GRSR算法
  • RA4M2开发IOT(8)----IIC驱动OLED
  • 分库分表下的 ID 冲突问题与雪花算法讲解
  • Qt项目,记事本
  • YOLOv8/11自定义seg分割数据集格式转换json2txt
  • 第八章 目录一致性协议 A Primer on Memory Consistency and Cache Coherence - 2nd Edition
  • 如何用AI开发完整的小程序<10>—总结
  • unity版本控制PlasticSCM转git
  • 需求初步探讨-从OR-AR
  • 《Redis》事务
  • 抽象工厂设计模式
  • 查询消耗 IO 多的 SQL -达梦
  • 一个免费的视频、音频、文本、图片多媒体处理工具
  • 数据库高性能应用分析报告
  • 鸿蒙 Column 组件指南:垂直布局核心技术与场景化实践
  • Python爬虫实战:研究Ghost.py相关技术
  • 【深度学习与机器学习的区别】从本质到应用的全景对比
  • 单例模式-Python示例
  • 多设备Obsidian笔记同步:WebDAV与内网穿透技术高效实现教程
  • 探秘Flink Connector加载机制:连接外部世界的幕后引擎
  • 考研408《计算机组成原理》复习笔记,第三章(1)——存储系统概念
  • 【数据结构试题】
  • 【JS-4.4-键盘常用事件】深入理解DOM键盘事件:提升用户交互体验的关键
  • idea——AI时代学习python的必要性
  • 学习打卡---回溯
  • linux jq命令详解