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

在数据库缓存工程中,缓存与数据库之间的数据一致性是核心挑战。不同的缓存更新模式决定了系统如何在读、写操作中协调缓存与底层存储,直接影响数据一致性、延迟和吞吐量。本章深入对比四种主流模式:Cache-Aside、Read-Through、Write-Through 和 Write-Behind。

3.1 模式总览与核心差异

下表从数据一致性、延迟、实现复杂度、适用场景四个维度对四种模式进行横向对比:

模式 数据一致性 读延迟 写延迟 实现复杂度 典型场景
Cache-Aside 最终一致(需应用层维护) 低(缓存命中时) 低(仅更新DB) 低 读多写少、可容忍短暂不一致
Read-Through 最终一致(由缓存层维护) 低(缓存命中时) 低(仅更新DB) 中 需要缓存层自动加载数据
Write-Through 强一致(同步写缓存+DB) 低(缓存命中时) 高(双写延迟) 中 对一致性要求高的核心数据
Write-Behind 最终一致(异步写DB) 低(缓存命中时) 极低(仅写缓存) 高 写密集型、可容忍短暂不一致
架构决策提示: 没有银弹。选择哪种模式取决于业务对一致性的容忍度、读写比例以及系统对延迟的敏感度。通常,Cache-Aside 是大多数互联网应用的默认选择,而 Write-Behind 则适用于需要极高写入吞吐的场景。

3.2 Cache-Aside 模式

Cache-Aside(旁路缓存)是最常用的模式。应用代码直接同时与缓存和数据库交互。读操作时,先查缓存,未命中则从数据库加载并回填缓存;写操作时,先更新数据库,然后使缓存失效(或更新)。

3.2.1 读流程

  1. 应用查询缓存,若命中则直接返回。
  2. 若未命中,查询数据库。
  3. 将查询结果写入缓存,设置过期时间。
  4. 返回数据给调用方。

3.2.2 写流程

  1. 应用更新数据库。
  2. 使缓存中的对应键失效(删除缓存)。
常见陷阱: 写操作时,先更新数据库再删除缓存 是推荐做法。如果先删除缓存再更新数据库,在并发场景下可能导致缓存中写入旧数据(脏读)。使用延迟双删(先删缓存,更新DB,再延迟删一次)可进一步降低风险。
Cache-Aside 模式读写流程 应用 缓存 数据库 ① 读缓存 ③ 回填缓存 ② 读DB(未命中时) ④ 更新DB ⑤ 删除缓存 读流程 写流程

3.3 Read-Through 模式

Read-Through(穿透读)将缓存层抽象为一个代理。应用只与缓存交互,当缓存未命中时,由缓存组件(如 Redis 的客户端库或代理层)自动从数据库加载数据并回填。应用无需感知数据库的存在。

3.3.1 核心流程

  • 读操作: 应用请求缓存 → 缓存未命中 → 缓存层加载数据库 → 缓存层回填并返回。
  • 写操作: 通常与 Cache-Aside 类似,应用直接更新数据库并失效缓存;或结合 Write-Through 使用。
优势: 应用代码更简洁,无需处理缓存未命中的回填逻辑;缓存层可统一实现缓存预热、过期策略和批量加载。

3.4 Write-Through 模式

Write-Through(穿透写)保证写入操作同步更新缓存和数据库。应用写入缓存后,缓存层立即将数据同步写入数据库,只有在数据库写入成功后,写操作才返回成功。

3.4.1 核心流程

  1. 应用写入缓存。
  2. 缓存层同步将数据写入数据库。
  3. 数据库写入成功后,缓存层向应用返回成功。

3.4.2 优缺点

优点 缺点
缓存与数据库之间保持强一致性 写延迟较高(需等待数据库写入完成)
应用无需关心数据库写入逻辑 缓存层成为写瓶颈
数据不会丢失(缓存故障时数据库有完整数据) 不适合写密集型场景

3.5 Write-Behind 模式

Write-Behind(异步写/写回)是 Write-Through 的异步变体。应用只写入缓存,缓存层立即返回成功,然后异步批量地将数据写入数据库。这种模式将写操作从同步路径中剥离,极大降低了写延迟。

3.5.1 核心流程

  1. 应用写入缓存。
  2. 缓存层立即返回成功。
  3. 缓存层将写操作放入队列(如消息队列或内部缓冲区)。
  4. 后台线程或消费者异步批量写入数据库。
风险提示: 如果缓存节点在数据被持久化到数据库之前发生故障,可能导致数据丢失。通常需要配合持久化缓存(如 Redis AOF/RDB)或 WAL(Write-Ahead Log)来保证数据可靠性。
Write-Behind 异步写流程 应用 缓存 写队列 数据库 ① 写缓存(立即返回) ② 入队 ③ 异步批量写入 说明:写队列可基于内存缓冲区或消息队列(如 Kafka、RabbitMQ)实现。 批量写入可合并多个写操作,减少数据库 I/O 次数。

3.6 模式选型决策树

在实际工程中,选择哪种模式通常遵循以下决策路径:

  • 读多写少,一致性要求不高: 优先选择 Cache-Aside,实现简单,性能良好。
  • 需要缓存层自动管理数据加载: 选择 Read-Through,减少应用代码中的回填逻辑。
  • 写操作必须保证缓存与数据库强一致: 选择 Write-Through,但需接受更高的写延迟。
  • 写密集型,可容忍短暂数据不一致: 选择 Write-Behind,配合持久化机制防止数据丢失。
混合使用建议: 在实际系统中,可以针对不同数据采用不同模式。例如,核心账户数据使用 Write-Through 保证强一致,而用户行为日志使用 Write-Behind 提升写入吞吐。缓存层(如 Redis)本身可以同时支持多种模式,通过客户端库或代理层进行策略路由。