缓存一致性:旁路缓存/写穿and读穿/写回

601 阅读3分钟

旁路缓存模式(Cache-Aside)深度解析

一、核心思想与命名由来

1. 为什么叫"旁路"缓存?

  • 字面含义:缓存系统(如Redis)像一条"旁路"支线,不直接参与主业务流程
  • 类比理解:类似高速公路的应急车道,只在需要时使用
  • 关键特征:应用程序主动管理缓存,而非依赖缓存系统自动处理

2. 核心流程记忆口诀

读流程:缓存有→直接取,缓存无→查库→回填
写流程:先改库→再删缓存

(可用谐音记忆:"读有取,无查填;写先库,后删缓")


二、读策略(Read Policy)

1. 标准流程

sequenceDiagram
    participant App as 应用程序
    participant Cache as 缓存
    participant DB as 数据库
    
    App->>Cache: 1. 查询缓存
    alt 缓存命中
        Cache-->>App: 返回数据
    else 缓存未命中
        App->>DB: 2. 查询数据库
        DB-->>App: 返回数据
        App->>Cache: 3. 回填缓存
    end

2. 关键设计要点

设计选择原因风险控制
缓存未命中时回填避免每次穿透到DB需加分布式锁防止并发回填
不强制读穿透保持业务逻辑控制权需处理回填失败场景
设置TTL兜底最终一致性TTL时间需根据业务特点调整

三、写策略(Write Policy)

1. 标准流程

sequenceDiagram
    participant App as 应用程序
    participant Cache as 缓存
    participant DB as 数据库
    
    App->>DB: 1. 更新数据库
    App->>Cache: 2. 删除缓存

2. 删除而非更新的原因

操作优点缺点适用场景
删除缓存避免并发写导致脏数据下次读取需回填常规场景
更新缓存减少一次回填查询可能覆盖其他线程的中间状态写多读极少场景

四、与其他模式的对比

1. 写穿模式(Write-Through)

graph LR
    App[应用] --> Cache[缓存]
    Cache --> DB[数据库]
  • 特点:所有写操作先更新缓存,由缓存系统同步写DB
  • 示例:Guava Cache的CacheLoader.write实现
  • 适用场景:需要强一致性的配置类数据

2. 读穿模式(Read-Through)

graph LR
    App[应用] --> Cache[缓存]
    Cache --> DB[数据库]
  • 特点:应用直接读缓存,缓存系统负责未命中时的DB查询和回填
  • 示例:Spring Cache注解的实现
  • 适用场景:希望完全解耦业务代码与缓存逻辑

3. 写回模式(Write-Back)

graph LR
    App[应用] --> Cache[缓存]
    Cache --异步批量--> DB[数据库]
  • 特点:写操作只更新缓存,异步批量刷盘
  • 风险:数据丢失风险(如宕机)
  • 适用场景:写吞吐量极高的场景(如点击流日志)

五、业务场景选型指南

1. 旁路缓存适用场景

场景特征示例业务原因
读多写少商品详情页减少DB压力
允许短暂不一致用户昵称展示最终一致可接受
缓存Key分散个性化推荐避免大Key问题

2. 不适用场景

场景特征问题替代方案
写密集型(如秒杀库存)频繁删缓存导致穿透写穿透+本地缓存
强一致性要求(如账户余额)无法接受不一致窗口分布式事务
数据关联复杂(如社交关系)维护成本过高图数据库

六、经典问题解决方案

1. 缓存击穿(单个热点Key失效)

解决方案

public Object getData(String key) {
    Object value = redis.get(key);
    if (value == null) {
        // 获取分布式锁
        if (redis.setnx(key + ":lock", "1")) {
            try {
                value = db.query(key);
                redis.setex(key, 300, value);
            } finally {
                redis.del(key + ":lock");
            }
        } else {
            // 重试或返回默认值
            Thread.sleep(100);
            return getData(key);
        }
    }
    return value;
}

2. 缓存雪崩(大量Key同时失效)

防御措施

  • 随机TTL:在基础过期时间上增加随机值(如300±60秒)
  • 预热机制:在低峰期主动加载热点数据
  • 熔断降级:当缓存失效比例超过阈值时直接走DB

七、设计本质总结

维度旁路缓存写穿/读穿写回
控制权应用层控制缓存系统控制混合控制
一致性最终一致强一致弱一致
复杂度
吞吐量极高

记忆要点

  • 旁路缓存像"手动挡"汽车,需要开发者精细控制
  • 写穿/读穿像"自动挡"汽车,由缓存系统自动处理
  • 写回像"赛车模式",追求极致性能但风险更高