后端缓存策略:Cache Aside、读写穿透与刷新

FreeGuideOnline 最新 2026-06-16

后端缓存策略:从基础到进阶

在现代高并发系统中,缓存是提升性能和可扩展性的核心手段。它通过将频繁访问的数据存储在内存等高速介质中,大幅降低数据库压力并缩短响应时间。然而,不合理的缓存使用会引发数据不一致、雪崩等问题。本教程将详解三种经典后端缓存策略:Cache Aside(旁路缓存)Read/Write Through(读写穿透)Write Behind(异步刷新),帮助你根据业务场景做出正确选择。

什么是缓存策略?

缓存策略定义了应用程序如何与缓存和数据库进行交互,主要解决两个问题:

  • 数据读取时,从哪里获取数据?
  • 数据写入时,如何同步缓存与数据库的状态?

不同的策略在一致性保证、系统复杂度和性能之间进行权衡。没有“银弹”,只有适合场景的策略。

Cache Aside 模式(旁路缓存)

这是最常用、最容易实现的策略,也称为“懒加载”模式。名字来源于应用代码直接“绕过”缓存与数据库进行通信,缓存层被夹在应用和数据库之间,但控制权在应用程序手中。

读操作流程

  1. 应用程序请求数据。
  2. 先查缓存,如果命中(cache hit),直接返回。
  3. 如果未命中(cache miss),则从数据库查询。
  4. 将查询到的数据写入缓存,并返回给应用程序。
  5. 下次读取同一数据时,即可命中缓存。
读请求 → 命中缓存?
 ├─ 是 → 返回缓存数据
 └─ 否 → 查数据库 → 数据写入缓存 → 返回数据

写操作流程

关键点:先更新数据库,再删除缓存,而不是更新缓存。

  1. 应用程序需要更新一条数据。
  2. 直接更新数据库。
  3. 使缓存中对应的数据失效(删除缓存键)。
  4. 后续读请求因缓存未命中,会从数据库加载最新数据并回填到缓存。
写请求 → 更新数据库 → 删除缓存 → 完成

为什么删除缓存而不是更新缓存?

  • 避免并发写导致的数据错乱:假设A请求更新数据库后,B请求立即也更新了数据库并更新缓存,然后A请求才更新缓存。由于网络延迟,A晚到的更新可能覆盖B的正确值,导致缓存脏数据。而删除缓存是幂等操作,不存在顺序问题。
  • 降低不必要的缓存更新:如果一条数据被更新后很少被读取,过早更新缓存是无法利用的资源浪费。只有被实际读取时才加载,即“懒加载”思想。
  • 简化实现:无需构建复杂的缓存更新逻辑。

Cache Aside 存在的问题与解决方案

问题:先删缓存,再更新数据库?还是先更新数据库,再删缓存?

  • 顺序一:先删除缓存,再更新数据库 假设线程A删除了缓存,准备更新数据库。此时线程B过来读取,发现缓存未命中,读取到旧的数据库值,并写回缓存。随后线程A才完成数据库更新。这导致缓存中长期驻留旧值,直到下一次删除或过期。虽然发生概率在并发量大的系统中不低,但可通过“延迟双删”策略缓解:先删除缓存,更新数据库后,休眠一段时间(如几百毫秒),再次删除缓存。休眠时间需大于一次读操作补缓存的耗时。

  • 顺序二:先更新数据库,再删除缓存(推荐) 线程A更新了数据库,但还没删除缓存。此时线程B读取,发现缓存命中,读取到旧值。然后线程A才删除缓存。这种“读旧值”的窗口极短,实际影响较小。如果对一致性要求极高,可以配合订阅数据库 binlog,采用异步重试删除缓存(最终一致性)。

适用场景

  • 读写比例严重不均衡,读多写少。
  • 能够容忍短暂的数据不一致(最终一致性)。
  • 希望最小化缓存层对业务代码的侵入性。

Read/Write Through 模式(读写穿透)

在 Cache Aside 模式中,应用程序需要对缓存和数据库的交互逻辑负责。而 Read/Write Through 将这种逻辑委托给缓存层本身,对应用而言,缓存就像是一个数据库的“代理”,应用只与缓存交互,完全看不到数据库。

Read Through

  1. 应用程序请求数据,只调用缓存。
  2. 如果缓存命中,直接返回。
  3. 如果缓存未命中,缓存服务本身负责从数据库加载数据,并写入自己,然后返回给应用。
  4. 应用完全不参与数据库的读取过程。
应用 → 读缓存 → 未命中?→ 缓存自动查数据库并加载 → 返回数据给应用

Write Through

  1. 应用程序要更新数据,向缓存发起写入请求。
  2. 缓存服务更新自身存储。
  3. 同时,缓存服务将数据同步写入数据库
  4. 两个写入操作都完成后,才向应用返回成功。
应用 → 写缓存 → 缓存自动同步写入数据库 → 返回成功

特点与代价

  • 一致性更强:由于写操作同步更新缓存和数据库,并且由缓存层保证事务性(或近似事务性),应用读取时总是能读到最新数据(除非有并发写,但大部分实现通过锁保证)。
  • 对应用透明:代码大幅简化,应用只需对接缓存API。
  • 性能开销:写操作必须等待数据库写入成功,延迟较高,不适于写密集场景。
  • 缓存永远与数据库同步:因为没有Cache Aside那种“删除缓存等待懒加载”的窗口,缓存始终是热的。

适用场景

  • 对数据一致性要求较高,不允许出现脏读。
  • 应用程序逻辑希望完全解耦数据库访问(例如使用本地缓存库如 Ehcache,或某些分布式缓存提供这种模式)。
  • 读操作远多于写操作时,写穿透带来的额外延迟可接受。

Write Behind 模式(异步刷新 / 写后)

也称作 Write Back。与 Write Through 完全相反,该模式把数据库的更新延后处理,追求极致的写入性能。

工作流程

  1. 应用程序要写入数据,只写缓存,写缓存即视为成功,立即返回
  2. 缓存层在内存中累积这些修改,并在特定的时间点(如定期、批量化或缓存空间满时)将数据异步批量写入数据库。
  3. 读取时直接从缓存读取,如果数据还在缓存中未刷新到数据库,缓存保证可以返回正确数据。
应用写 → 写缓存成功(立即返回)→ 后台异步批量刷入数据库

优势与风险

  • 极高的写入吞吐量和低延迟:对持久化存储的写入被合并和延迟,数据库压力大幅降低。
  • 适合突发流量和写密集场景
  • 数据丢失风险高:如果在数据刷入数据库之前缓存节点宕机或断电,未持久化的写入将永久丢失。必须通过持久化缓存日志、复制等机制弥补。
  • 实现复杂度高:需要处理脏数据的追踪、与数据库的冲突合并、异步任务管理、幂等性等。
  • 数据不一致窗口大:其他节点读取时可能从缓存读到还未持久化的数据,无法反映数据库真实状态。

适用场景

  • 写操作极高频,且能容忍少量数据丢失的场景(如用户行为统计、点击计数)。
  • 混合持久化:缓存本身具备持久化能力(如 Redis+AOF),即使宕机也能恢复。
  • 限流和削峰填谷:例如电商大促秒杀系统,先将请求写入缓存,异步落库。

三种策略对比总结

策略 读交互 写交互 性能 一致性 复杂度 典型应用
Cache Aside 应用查缓存,未命中则查库回填 应用更新库,然后删除缓存 读快,写一般 最终一致性,可容忍短暂不一致 低,应用控制逻辑 通用Web应用,如MySQL+Redis
Read/Write Through 应用只管缓存,缓存负责查库 应用写缓存,缓存同步写库 写延迟较高 强一致性 中,依赖缓存库实现 本地缓存框架,或严格一致性的服务
Write Behind 应用读缓存(已包含最新数据) 应用写缓存即成功,缓存异步写库 写极快 弱一致性,可能丢数据 高,需处理异步与容错 日志收集、高频计数器、不要求强一致的会话存储

如何选择适合你的策略?

  • 大部分业务场景,从 Cache Aside 开始。它足够简单、稳定,配合设置合理的过期时间和延迟双删能应对多数挑战。
  • 如果你的架构已经采用一个可感知数据库的缓存中间件(如 Redis 配合自定义插件,或某些 ORM 自带二级缓存),并且对一致性有明确要求,可以考虑 Read/Write Through。
  • 仅在写入性能成为绝对瓶颈,且业务能接受数据丢失风险时,才引入 Write Behind。务必做好持久化和故障恢复预案。

写在最后

缓存策略的本质是权衡一致性、可用性和性能。没有任何一种模式能完美解决所有问题。实际系统常组合使用不同策略,例如核心交易数据用 Write Through 保证一致性,用户会话用 Cache Aside 兼顾性能,而埋点统计则用 Write Behind 实现高速写入。深入理解这三种基础模式,将帮助你构建健壮且高效的后端缓存体系。


延伸思考:在实际分布式环境下,你还可以结合缓存预热缓存击穿/雪崩防护多级缓存等技巧,进一步优化系统表现。试试在你的下一个项目中有意识地应用这些策略,并观察一致性窗口和性能指标的变化吧!