3. 缓存模式概述:Cache-Aside、Read-Through、Write-Through、Write-Behind 模式对比

在构建高并发、低延迟的 Redis 缓存系统时,选择正确的缓存模式是架构设计的核心决策。不同的模式决定了应用程序与缓存、数据库之间的交互方式,直接影响数据一致性、性能瓶颈和运维复杂度。本章将系统对比四种主流缓存模式:Cache-Aside、Read-Through、Write-Through 和 Write-Behind,帮助你在契约设计中做出合理选择。

3.1 四种模式核心概念

模式 读操作策略 写操作策略 缓存与数据库一致性 典型适用场景
Cache-Aside 应用先读缓存,未命中则读数据库并回填缓存 应用直接写数据库,同时删除或更新缓存 最终一致(需处理并发写冲突) 读多写少、业务逻辑灵活控制
Read-Through 缓存层自动回填(由缓存组件负责) 应用直接写数据库,缓存失效 最终一致(读路径自动修复) 读密集型、缓存层封装度高
Write-Through 同 Read-Through 应用写缓存,缓存同步写数据库 强一致(写路径同步) 写一致性要求高、数据不丢失
Write-Behind 同 Read-Through 应用写缓存,缓存异步批量写数据库 最终一致(存在写回延迟) 写吞吐极高、允许短时数据不一致
架构师提示:在 Redis 缓存契约设计中,Cache-Aside 是最灵活的模式,适合大多数业务场景;Write-Through 适合金融、订单等强一致场景;Write-Behind 适合日志、埋点等写吞吐量巨大的场景。

3.2 Cache-Aside 模式详解

Cache-Aside(旁路缓存)是应用最广泛的模式。应用程序负责管理缓存的读写逻辑,缓存层不直接与数据库交互。

  • 读流程:应用先查询缓存 → 命中则直接返回 → 未命中则查询数据库 → 将结果写入缓存 → 返回给调用方。
  • 写流程:应用先更新数据库 → 然后删除(或更新)缓存中的对应键。
  • 一致性保障:采用“先更新数据库,后删除缓存”策略,配合缓存过期时间,可达到最终一致性。
// Cache-Aside 读操作伪代码
public Object get(String key) {
    Object value = redis.get(key);
    if (value != null) {
        return value;  // 缓存命中
    }
    // 缓存未命中,从数据库加载
    value = database.query(key);
    if (value != null) {
        redis.set(key, value, 300);  // 设置过期时间
    }
    return value;
}

// Cache-Aside 写操作伪代码
public void update(String key, Object newValue) {
    database.update(key, newValue);  // 先更新数据库
    redis.del(key);                  // 再删除缓存
}
常见陷阱:并发写场景下,如果先删除缓存再更新数据库,会导致其他线程读到旧数据。务必采用“先更新数据库,后删除缓存”的顺序,或使用分布式锁。

3.3 Read-Through 模式详解

Read-Through(通读)模式将缓存未命中的回填逻辑下沉到缓存层。应用程序只与缓存交互,缓存组件(如 Redis 配合自定义加载器)负责从数据库加载数据。

  • 读流程:应用请求缓存 → 缓存未命中 → 缓存组件自动调用数据库加载器 → 加载数据并回填缓存 → 返回给应用。
  • 写流程:应用直接写数据库,或通过缓存失效机制触发下次读时重新加载。
  • 优势:应用代码简化,缓存层封装了数据加载逻辑,便于统一管理缓存预热和失效策略。
// Read-Through 模式示意(Redis 配合自定义加载器)
// 应用代码只需调用缓存接口
public Object get(String key) {
    // 缓存层内部自动处理未命中回填
    return redis.getWithLoader(key, loader -> {
        return database.query(key);
    });
}

3.4 Write-Through 模式详解

Write-Through(通写)模式要求每次写操作都必须同步写入缓存和数据库。缓存层作为写入的代理,保证数据在缓存和数据库中的强一致性。

  • 写流程:应用写缓存 → 缓存同步写数据库 → 两者都成功后才返回成功。
  • 读流程:同 Read-Through,缓存未命中时自动回填。
  • 一致性:强一致,但写延迟较高(因为需要等待数据库写入完成)。
// Write-Through 写操作伪代码
public void write(String key, Object value) {
    // 缓存层保证原子性写入
    redis.set(key, value);
    database.update(key, value);  // 同步写入数据库
    // 只有两者都成功,才返回成功
}
性能考量:Write-Through 模式适合写频率不高但要求数据不丢失的场景。如果写并发高,数据库会成为瓶颈,建议改用 Write-Behind 模式。

3.5 Write-Behind 模式详解

Write-Behind(写后)模式将写操作先写入缓存,然后异步批量写入数据库。缓存层负责积累写操作,在后台按批次或定时策略同步到数据库。

  • 写流程:应用写缓存 → 立即返回成功 → 后台线程异步批量写入数据库。
  • 读流程:同 Read-Through,缓存未命中时自动回填。
  • 优势:写吞吐极高,适合日志、埋点、计数器等场景。
  • 风险:缓存宕机可能导致数据丢失,需要持久化或副本机制保障。
// Write-Behind 写操作伪代码
public void write(String key, Object value) {
    redis.set(key, value);  // 先写入缓存
    // 后台异步批量写入数据库
    asyncBatchQueue.add(new WriteTask(key, value));
}

// 后台线程定时刷盘
public void flushToDatabase() {
    List<WriteTask> batch = asyncBatchQueue.drainAll();
    database.batchUpdate(batch);  // 批量写入
}

3.6 四种模式对比总结

下表从多个维度对四种模式进行横向对比,帮助你在契约设计中快速决策:

对比维度 Cache-Aside Read-Through Write-Through Write-Behind
读延迟 低(命中时) 低(命中时) 低(命中时) 低(命中时)
写延迟 低(仅操作数据库+缓存删除) 低(仅操作数据库) 高(需等待数据库写入) 极低(仅操作缓存)
数据一致性 最终一致 最终一致 强一致 最终一致(有窗口期)
缓存与数据库耦合 低(应用控制) 中(缓存层控制读) 高(缓存层控制读写) 高(缓存层控制读写)
实现复杂度 低 中 中 高(需异步队列、容错)
适用写吞吐 中 中 低 极高
缓存宕机影响 可恢复(数据库有全量数据) 可恢复 可恢复(数据库有全量数据) 可能丢失未刷盘数据

3.7 四种模式架构对比图

下图展示了四种缓存模式的核心架构差异,重点关注应用、缓存、数据库之间的数据流向:

四种缓存模式架构对比 Cache-Aside 模式 应用 缓存 数据库 读/写 回填 读:应用→缓存→(未命中)→数据库→回填缓存 写:应用→数据库→删除缓存 一致性:最终一致(先更新DB,后删缓存) Read-Through 模式 应用 缓存 数据库 读 自动回填 读:应用→缓存→(未命中)→缓存自动加载DB 写:应用直接写数据库,缓存失效 一致性:最终一致(读路径自动修复) Write-Through 模式 应用 缓存 数据库 写 同步写 写:应用→缓存→同步写数据库(两者都成功才返回) 读:同 Read-Through,缓存未命中自动回填 一致性:强一致(写路径同步) Write-Behind 模式 应用 缓存 数据库 写 异步批量 写:应用→缓存→立即返回→后台异步批量写DB 读:同 Read-Through,缓存未命中自动回填 一致性:最终一致(存在写回延迟窗口)

3.8 模式选择决策流程

在实际项目中,选择哪种缓存模式取决于业务对一致性、延迟和吞吐的要求。以下决策流程可以帮助你快速定位:

缓存模式选择决策流程 开始选择缓存模式 写操作是否需要强一致性? Write-Through 强一致,写延迟较高 判断写吞吐要求 写吞吐是否极高? 是 否 Write-Behind 极高吞吐,允许短时不一致 是 Cache-Aside / Read-Through 读多写少,灵活控制 否(读多写少)

3.9 契约设计中的模式选择建议

架构师最佳实践:
  • 默认选择 Cache-Aside:大多数业务系统适用,应用层控制灵活,易于调试和扩展。
  • 读密集型且缓存层封装度高:选择 Read-Through,减少应用层重复代码。
  • 写一致性要求严格(如订单、支付):选择 Write-Through,确保缓存与数据库强一致。
  • 写吞吐极高(如日志、埋点、计数器):选择 Write-Behind,配合 Redis 持久化(AOF/RDB)降低数据丢失风险。
  • 混合使用:同一系统可按业务维度混合使用不同模式,例如订单用 Write-Through,商品详情用 Cache-Aside。