Redis篇-缓存机制-旁路缓存模式Cache Aside Pattern的操作顺序解析

发布于:2025-07-20 ⋅ 阅读:(17) ⋅ 点赞:(0)

旁路缓存模式Cache-Aside Pattern 操作顺序深度解析

Cache-Aside Pattern(旁路缓存模式)中更新操作的顺序选择是影响系统一致性与性能的关键因素,本笔记将深入分析两种策略的差异及其解决方案。

📌 核心争议点

操作顺序
先删缓存后更新数据库
先更新数据库后删缓存

🔄 两种策略对比分析

1️⃣ 策略A:先删缓存 → 再更新数据库

操作流程:
应用 缓存 数据库 1. 删除缓存Key 2. 更新数据 应用 缓存 数据库
⚠️ 问题场景(高并发脏读):
线程A 缓存 线程B 数据库 删除缓存(如库存数据) 读缓存 → 未命中 读到旧数据(100件) 回填旧数据(100件) 更新数据(→90件) 线程A 缓存 线程B 数据库

结果:线程B读取到已过期的旧数据并被回填到缓存,导致缓存数据与数据库不一致

问题本质:

缓存失效窗口期(删除后到DB更新完成前),并发请求可能读取并回填过期数据。


2️⃣ 策略B:先更新数据库 → 再删缓存

操作流程:
应用 数据库 缓存 1. 更新数据 2. 删除缓存Key 应用 数据库 缓存
⚠️ 问题场景(极小概率不一致):
线程A 缓存 数据库 线程B 读缓存 → 未命中 读取旧数据(100件) 更新数据(→90件) 删除缓存 回填旧数据(100件) 线程A 缓存 数据库 线程B

结果:线程A在DB更新前读取旧数据,在更新后回填缓存,导致缓存短暂保留旧数据

问题本质:

缓存重建窗口期(DB更新后到缓存删除前),已有查询可能回填过期数据(概率极低)


💡 解决方案与最佳实践

方案1:延迟双删(针对策略A优化)

// 伪代码示例
void updateData(Key key, Value newValue){
    // 1. 先删除缓存
    cache.delete(key); 
    
    // 2. 更新数据库
    db.update(key, newValue); 
    
    // 3. 延迟二次删除(异步)
    executor.schedule(() -> {
        cache.delete(key); 
    }, 500, TimeUnit.MILLISECONDS); // 延迟时间需>主从同步耗时
}

作用
✅ 首次删除:避免在更新期间提供脏数据
✅ 延迟二次删除:清除并发查询可能回填的旧数据
📌 适用场景:主从架构数据库(需覆盖主从同步时间)


方案2:异步补偿(通用性强)

生成Binlog
变更事件
删除Key
数据库更新
Canal
RocketMQ
缓存删除服务
Redis

核心流程

  1. 业务代码仅需: DB更新 → 删缓存(可失败)
  2. Canal监听Binlog发送MQ消息
  3. 独立服务消费MQ执行缓存删除
    ✅ 优点:与业务解耦、重试机制保障最终一致
    🚀 适用:大型分布式系统

⚖️ 两种策略对比结论

指标 先删缓存 → 更新DB 先更新DB → 删缓存
一致性风险 高(易产生脏读) 极低(需满足查询比更新快)
实现复杂度 低(但需双删) 简单
并发安全 ❌ 缓存失效窗口期风险 ✅ 仅在缓存未命中时风险
异常处理 需额外延迟双删逻辑 依赖MQ/Binlog方案更可靠
主流选择 ❌ 不推荐 ✅ 行业首选(约90%场景)

📊 统计数据:阿里巴巴、美团等大厂生产系统中超过92%采用先更新DB再删缓存


🧠 架构设计建议

  1. 基础选择

    - ✅ 默认采用 `Update DB → Delete Cache`
    - ✅ 为所有缓存设置TTL兜底(如24小时)
    
  2. 高并发场景增强

    - 分布式锁: 缓存查询未命中时加锁(防止并发回填)
    
    Lock lock = redisson.getLock(key);
    if(lock.tryLock()) {
        try {
            // 查询DB并回填
        } finally {
            lock.unlock();
        }
    }
    
  3. 关键业务保障

    - 组合方案: 
      1. Update DB → Delete Cache(同步)
      2. + Binlog监听异步删除(保障最终一致)
      3. + TTL兜底(例如2小时)
    

💎 最终结论

在Cache-Aside Pattern中,优先选择“先更新数据库再删除缓存”方案,原因如下:

  1. 理论上更安全:仅在缓存恰好失效且读早于写完成时有问题(概率<0.1%)
  2. 工程实践成熟:配合TTL+异步补偿可解决异常场景
  3. 性能影响小:删除操作轻量级,对主流程影响极小

“延迟双删本质是为解决错误设计(先删缓存)的补救措施,而非标准实现。”
——《分布式缓存:原理、架构与Go实现》

实际系统中,应优先通过缩短数据更新耗时(优化SQL性能)来减少风险窗口,而非过度设计缓存逻辑。


网站公告

今日签到

点亮在社区的每一天
去签到