缓存基础回顾:常见缓存模式与 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 是实际项目中使用最广泛的缓存模式,其核心流程如下:
- 读操作: 应用先查询缓存,若命中则直接返回;若未命中,则查询数据库,将结果写入缓存并设置 TTL,最后返回数据。
- 写操作: 应用先更新数据库,然后删除缓存(或更新缓存),确保下次读取时获取最新数据。
- 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 配置在集成测试中的关键验证点
在回归冒烟测试中,针对 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,可能导致缓存中数据与数据库不一致的时间窗口被延长。建议优先使用删除缓存策略。