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

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

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

  • 场景描述

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

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

  • 发生条件

  • 解决方案

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

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

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

  • 场景描述

    • 更新数据库成功

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

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

  • 关键代码风险点

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

    • 重试 机制

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

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

  • 场景描述

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

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

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

  • 解决方案

    • 延迟双删

      java 复制代码
      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 + 延迟双删 + 重试队列" 的组合策略。

相关推荐
松涛和鸣2 分钟前
72、IMX6ULL驱动实战:设备树(DTS/DTB)+ GPIO子系统+Platform总线
linux·服务器·arm开发·数据库·单片机
likangbinlxa20 分钟前
【Oracle11g SQL详解】UPDATE 和 DELETE 操作的正确使用
数据库·sql
r i c k1 小时前
数据库系统学习笔记
数据库·笔记·学习
野犬寒鸦1 小时前
从零起步学习JVM || 第一章:类加载器与双亲委派机制模型详解
java·jvm·数据库·后端·学习
IvorySQL2 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
·云扬·2 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
IT邦德2 小时前
Oracle 26ai DataGuard 搭建(RAC到单机)
数据库·oracle
惊讶的猫2 小时前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写
不爱缺氧i3 小时前
完全卸载MariaDB
数据库·mariadb
期待のcode3 小时前
Redis的主从复制与集群
运维·服务器·redis