直读模式(Read Through Pattern) 是一种将缓存作为“主要数据源”的架构模式。与 旁路缓存 (Cache Aside) 不同,在 Read Through 模式下,应用程序不再直接与数据库交互,而是通过一个缓存代理(或特殊的缓存库)来获取数据。

其核心思想是:应用只看缓存,缓存负责加载数据。


核心操作流程

一、读操作 (Read Path)

场景 1:缓存命中 (Cache Hit)

sequenceDiagram
    participant App as 应用程序
    participant Cache as 缓存

    App->>Cache: 1. 查询数据
    Cache-->>App: 2. 返回命中数据

场景 2:缓存失效 (Cache Miss)

sequenceDiagram
    participant App as 应用程序
    participant Cache as 缓存
    participant DB as 数据库

    App->>Cache: 1. 查询数据
    Note over Cache, DB: 缓存层自动触发回填
    Cache->>DB: 2. 读取数据库
    DB-->>Cache: 3. 返回原始数据
    Cache->>Cache: 4. 写入缓存
    Cache-->>App: 5. 返回数据给应用
  1. 请求数据:应用程序向缓存层请求数据。
  2. 命中返回:如果缓存中有数据,直接返回。
  3. 自动加载:如果缓存缺失(Cache Miss),由缓存层自动去数据库读取数据
  4. 回填缓存:缓存层将从数据库读取到的数据写入自身。
  5. 返回结果:缓存层将数据返回给应用程序。

对于应用层代码来说,读取缓存的操作是透明的:它就像是在读取一个超大、高性能的数据库,而不需要关心数据是来自内存还是磁盘。

二、写操作 (配套模式)

从严格定义上讲,Read Through 仅代表一种读策略。但在实际应用中,由于 Read Through 的前提是“应用只与缓存交互”,因此其对应的写操作通常由以下两种配套模式完成:

  1. 直写模式 (Write Through):写操作同步经过缓存层并写入数据库。
  2. 回写模式 (Write Back):写操作只写入缓存后立即返回,由缓存层异步批量刷入数据库。

这种“读写全交给缓存”的组合架构,也被合称为透明缓存(In-Line Cache)


关键考量:并发保护与加载策略

一、请求合并 (Request Collapsing)

Cache Aside 模式下,如果多个线程同时发现缓存失效,可能会同时去查询数据库,导致缓存击穿

而优秀的 Read Through 实现(如 Caffeine, Guava Cache)通常内置了请求合并机制:

  • 当一个 Key 发生 Cache Miss 时,第一个到达的线程会触发“加载”动作。
  • 其他同时请求该 Key 的线程会被阻塞,进入等待队列。
  • 加载线程完成后,结果会同步给所有等待的线程。

这种机制从底层保证了同一个 Key 无论并发多高,只会产生一次数据库查询,极大地保护了后端数据库。

二、进阶加载策略

根据业务对延迟和一致性的要求,Read Through 可以有不同的执行策略:

  1. 同步加载(默认):Cache Miss 时,请求线程阻塞等待 DB 返回。适用于对一致性要求高的场景。
  2. 异步加载(Async):缓存返回 FutureCompletableFuture,应用线程不原地阻塞。
  3. 刷新提前(Refresh Ahead):在数据过期前,缓存层自动异步刷新。优点是零延迟,缺点是可能读到短暂的旧数据。

三、一致性挑战:数据新鲜度

虽然 Read Through 简化了业务逻辑,但它在一致性上存在两个潜在挑战:

  1. 脏数据回填风险:如果存在外部系统或后台任务直接绕过缓存修改数据库,Read Through 可能会在回填时加载到旧值并长期驻留。因此,该模式必须配合 TTL (过期时间) 作为最终一致性的保底手段。
  2. 闭环完整性:该模式最理想的状态是与 Write Through 配合形成闭环。如果写操作是零散的(如 Cache Aside 式的先更库再删缓存),那么在删缓存和 Read Through 重新回填的极短间隙内,依然存在并发导致的数据不一致风险。

适用场景

Read Through 模式通常出现在以下场景中:

  1. 进程内缓存库:如 Java 的 Guava CacheCaffeine。你只需配置一个 CacheLoader,查询时如果 Key 不存在,库会自动调用该 Loader。
  2. 数据库中间件/分布式系统:某些高端的分布式缓存系统支持配置外部数据源。
  3. 只读业务:对于那些几乎不更新,或者更新频率极低且对实时性要求不高的热点配置数据。

总结

  • 优点:业务逻辑解耦,代码极其简洁;底层内置并发控制,天然防止缓存击穿。
  • 缺点:实现成本较高,通常需要缓存库或中间件支持;数据模型灵活性较差。
  • 适用场景:本地进程内缓存(Caffeine, Guava);对读性能和一致性有严格要求的透明代理场景。