缓存基础回顾:常见缓存模式与 TTL 概念

在集成测试中验证缓存过期时间配置之前,必须牢固掌握缓存的核心工作模式以及 TTL(Time-To-Live)的基本原理。本章将系统回顾三种最常见的缓存模式——Cache-Aside、Read-Through、Write-Through,并深入解析 TTL 在缓存一致性中的关键作用。

一、三种常见缓存模式对比

不同的缓存模式决定了应用程序与缓存层、数据库层之间的交互方式。下表从读写路径、数据一致性、适用场景三个维度进行对比:

缓存模式 读路径 写路径 数据一致性 典型场景
Cache-Aside 应用先查缓存,未命中则查 DB 并回填缓存 应用直接写 DB,然后主动删除或更新缓存 最终一致性(需配合 TTL 兜底) 读多写少、业务逻辑灵活控制
Read-Through 缓存层自动从 DB 加载数据并回填 应用直接写 DB,缓存由加载器同步 较强一致性(加载器保证) 标准化数据访问、ORM 集成
Write-Through 与 Read-Through 相同 应用写缓存,缓存同步写 DB 强一致性(写操作同步穿透) 写频繁、要求数据即时一致
测试提示: 在回归冒烟测试中,Cache-Aside 模式最容易出现因 TTL 配置错误导致的缓存与数据库不一致问题,应作为验证重点。

二、Cache-Aside 模式详解

Cache-Aside 是实际项目中使用最广泛的缓存模式,其核心流程如下:

  1. 读操作: 应用先查询缓存,若命中则直接返回;若未命中,则查询数据库,将结果写入缓存并设置 TTL,最后返回数据。
  2. 写操作: 应用先更新数据库,然后删除缓存(或更新缓存),确保下次读取时获取最新数据。
  3. TTL 兜底: 即使写操作后未及时删除缓存,TTL 过期后缓存自动失效,保证最终一致性。
关键测试点: Cache-Aside 模式下,TTL 配置过短会导致缓存命中率下降,增加数据库压力;TTL 配置过长则可能延长数据不一致窗口。集成测试应覆盖 TTL 边界值(0、1秒、最大值)以及缓存删除失败时的降级行为。

三、Read-Through 与 Write-Through 模式

这两种模式将缓存与数据库的同步逻辑封装在缓存层内部,对应用透明:

  • Read-Through: 缓存层内置 CacheLoader,当缓存未命中时自动加载数据库数据并回填。应用无需感知数据库的存在。
  • Write-Through: 应用写入缓存时,缓存层同步将数据写入数据库。写操作必须等待数据库确认后才返回,保证强一致性。
注意: Write-Through 模式虽然一致性最强,但写延迟较高,且缓存层故障会直接影响数据库写入。在集成测试中,需要验证缓存层故障时的降级策略(如熔断、队列缓冲)。

四、TTL 概念与核心作用

TTL(Time-To-Live)是缓存条目在缓存中存活的最大时间,单位为秒或毫秒。TTL 在缓存系统中承担以下核心作用:

作用 说明 测试验证点
自动失效 缓存条目到达 TTL 后自动被移除或标记为过期 验证过期后缓存是否确实返回 null 或触发重新加载
最终一致性 即使写操作未及时更新缓存,TTL 过期后数据自动同步 验证 TTL 窗口内数据不一致的最大容忍时间
内存管理 防止缓存无限增长,淘汰冷数据 验证 TTL 配合 LRU/LFU 淘汰策略时的行为
热点数据保护 通过设置合理的 TTL 避免缓存雪崩 验证 TTL 抖动(加随机偏移)是否生效

五、缓存模式与 TTL 交互流程

下图展示了三种缓存模式在读写流程中与 TTL 的交互关系:

缓存模式与 TTL 交互流程 应用层 Cache-Aside 模式 Read-Through 模式 Write-Through 模式 读:查缓存 → 未命中 → 查 DB → 回填 + TTL 写:更新 DB → 删除缓存 TTL 兜底最终一致性 读:查缓存 → 未命中 → CacheLoader 自动加载 DB → 回填 + TTL 写:应用写 DB,缓存由加载器同步 写:应用写缓存 → 缓存同步写 DB 读:同 Read-Through TTL 用于内存管理 + 冷数据淘汰 缓存层(Redis / Memcached / Local Cache) TTL 管理:过期淘汰 + 一致性兜底 数据库层(MySQL / PostgreSQL) TTL 配置错误 → 缓存不一致 集成测试需覆盖 TTL 边界值

六、TTL 配置在集成测试中的关键验证点

在回归冒烟测试中,针对 TTL 配置需要重点验证以下场景:

  • TTL 精确性: 验证缓存条目是否在设定的 TTL 时间后准确过期,误差在可接受范围内(通常 ±1 秒)。
  • TTL 边界值: 测试 TTL=0(立即过期)、TTL=1秒(最小有效值)、TTL=最大值(如 86400 秒)时的缓存行为。
  • TTL 与写操作交互: 验证在 TTL 未过期时执行写操作,缓存是否被正确删除或更新,TTL 是否重置。
  • TTL 与淘汰策略: 验证当缓存满时,TTL 未过期的条目是否被 LRU/LFU 策略淘汰,以及淘汰后的重新加载流程。
  • TTL 抖动: 验证是否配置了 TTL 随机偏移(如 ±10%),以防止缓存雪崩。
测试设计建议: 使用时间戳记录缓存写入时间,在 TTL 过期前后分别读取缓存,对比返回结果与数据库数据。建议编写参数化测试用例,覆盖 TTL 的 0、1、60、3600、86400 秒等典型值。

七、三种模式下的 TTL 配置示例

以下代码展示了三种缓存模式下 TTL 的典型配置方式:

// Cache-Aside 模式:手动设置 TTL
String key = "user:123";
User user = cache.get(key);
if (user == null) {
    user = database.query("SELECT * FROM users WHERE id = 123");
    cache.set(key, user, 300); // TTL = 300 秒
}
return user;

// Read-Through 模式:CacheLoader 自动设置 TTL
LoadingCache<String, User> cache = Caffeine.newBuilder()
    .expireAfterWrite(10, TimeUnit.MINUTES) // TTL = 10 分钟
    .build(key -> database.query("SELECT * FROM users WHERE id = " + key));

// Write-Through 模式:缓存同步写 DB 并设置 TTL
@CachePut(value = "users", key = "#user.id", cacheManager = "redisCacheManager")
public User saveUser(User user) {
    return userRepository.save(user); // TTL 由 @CachePut 注解的配置决定
}
常见陷阱: 在 Cache-Aside 模式中,如果写操作后更新缓存而不是删除缓存,且未重置 TTL,可能导致缓存中数据与数据库不一致的时间窗口被延长。建议优先使用删除缓存策略。