3. 数据库缓存典型架构:应用层缓存、数据库查询缓存、分布式缓存的集成方式
在集成测试中验证缓存过期时间配置,首先需要理解缓存所处的架构层次。现代数据库缓存体系通常由应用层缓存、数据库查询缓存和分布式缓存三层构成,它们各自承担不同的职责,并共同影响数据的一致性与过期行为。
3.1 三层缓存架构概览
下表对比了三种缓存层在集成测试中的关键差异,帮助测试人员快速定位过期时间配置的生效范围:
| 缓存层 | 典型实现 | 数据粒度 | 过期时间作用域 | 集成测试关注点 |
|---|---|---|---|---|
| 应用层缓存 | 进程内 Map、Guava Cache、Caffeine | 对象/业务实体 | 单进程 JVM 堆内 | 本地时间漂移、GC 停顿导致的过期延迟 |
| 数据库查询缓存 | MySQL Query Cache、Oracle Result Cache | SQL 语句 + 结果集 | 数据库实例级别 | 表结构变更、写操作对缓存的失效影响 |
| 分布式缓存 | Redis、Memcached | 键值对 / 数据结构 | 集群全局 | 网络延迟、时钟同步、淘汰策略与 TTL 交互 |
测试提示: 三层缓存的过期时间配置往往独立管理,但数据流会串联经过它们。集成测试应覆盖“缓存穿透”场景,即应用层缓存过期后,查询是否正确地回源到分布式缓存或数据库,并重新设置正确的 TTL。
3.2 应用层缓存:本地缓存与过期行为
应用层缓存位于业务代码内部,通常使用 @Cacheable 注解或 Guava Cache 构建。其过期时间配置在代码中硬编码或通过配置文件注入。
// 示例:使用 Caffeine 设置写入后过期 5 秒
Cache<String, User> userCache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.maximumSize(10_000)
.build();
// 集成测试验证点:等待 5 秒后,断言缓存已失效,触发数据库回源
User user = userCache.get("user_1001", key -> userRepository.findById(key));
Thread.sleep(6000);
assertNull(userCache.getIfPresent("user_1001"));
常见陷阱: 应用层缓存的过期时间依赖于本地系统时钟。在容器化部署中,不同 Pod 的时钟偏差可能导致过期时间不一致。集成测试应模拟时钟偏移场景,验证缓存是否仍能按预期过期。
3.3 数据库查询缓存:MySQL Query Cache 的集成方式
数据库查询缓存是数据库内核自带的优化机制。MySQL 8.0 已移除该特性,但在 MariaDB 或早期版本中仍存在。其过期时间由 query_cache_type 和 query_cache_limit 控制,且对写操作极其敏感。
- 失效条件: 对表执行 INSERT、UPDATE、DELETE 后,该表的所有查询缓存立即失效。
- 过期时间配置: 无显式 TTL,依赖内存淘汰策略(LRU)和碎片整理。
- 集成测试策略: 先执行 SELECT 填充缓存,再执行写操作,然后验证后续 SELECT 是否重新查询数据库。
-- 集成测试步骤(MySQL 5.7)
FLUSH QUERY CACHE;
SELECT * FROM orders WHERE id = 1001; -- 缓存命中
UPDATE orders SET status = 'shipped' WHERE id = 1001; -- 使缓存失效
SELECT * FROM orders WHERE id = 1001; -- 应重新从磁盘读取
3.4 分布式缓存:Redis/Memcached 的集成方式
分布式缓存是集成测试中过期时间配置验证的核心战场。Redis 和 Memcached 都支持精确到秒或毫秒的 TTL,但实现机制不同。
| 特性 | Redis | Memcached |
|---|---|---|
| 过期精度 | 毫秒级(PEXPIRE) | 秒级 |
| 过期策略 | 惰性删除 + 定期抽样删除 | 惰性删除 + LRU 淘汰 |
| 键过期回调 | 支持 Keyspace Notification | 不支持 |
| 集成测试难点 | 过期事件延迟、主从同步延迟 | 内存不足时提前淘汰,TTL 未到即丢失 |
集成测试关键验证点:
- 设置 TTL 后,等待过期时间,确认键被自动删除。
- 在 TTL 未到期时,手动删除键,验证应用是否回源数据库。
- 模拟网络分区,验证 Redis 主从切换后,过期时间是否仍被正确同步。
3.5 三层缓存集成架构图
下图展示了应用层缓存、数据库查询缓存与分布式缓存在一次完整查询请求中的协作关系,以及过期时间配置的生效位置:
3.6 集成测试中的缓存穿透与回源验证
三层缓存架构中,最关键的集成测试场景是缓存穿透——当所有缓存层都未命中时,请求必须回源到数据库。过期时间配置错误会导致两种典型问题:
- 缓存雪崩: 大量键在同一时间过期,导致数据库瞬间压力暴增。集成测试应验证过期时间是否添加了随机偏移量。
- 缓存击穿: 热点键过期后,高并发请求同时回源。集成测试应验证互斥锁或分布式锁是否生效。
// 集成测试:验证缓存穿透后正确回源并设置新 TTL
@Test
public void testCachePenetrationWithTTL() {
// 清空所有缓存
redisTemplate.delete("product_2001");
localCache.invalidate("product_2001");
// 第一次查询:应回源数据库,并设置 TTL 为 30 秒
Product product = productService.getProduct(2001);
assertNotNull(product);
// 验证 Redis 中 TTL 已设置
Long ttl = redisTemplate.getExpire("product_2001", TimeUnit.SECONDS);
assertTrue(ttl >= 28 && ttl <= 30); // 允许 2 秒误差
// 等待 TTL 过期
Thread.sleep(31000);
// 第二次查询:应再次回源数据库
Product productAgain = productService.getProduct(2001);
assertNotNull(productAgain);
}
3.7 三层缓存过期时间配置对比总结
下表汇总了三种缓存层在集成测试中需要验证的过期时间配置项及其典型值:
| 缓存层 | 配置项 | 典型值 | 验证方法 |
|---|---|---|---|
| 应用层缓存 | expireAfterWrite / expireAfterAccess | 5s ~ 60s | Thread.sleep + getIfPresent 断言 |
| 数据库查询缓存 | query_cache_type / query_cache_limit | ON / 1MB | FLUSH QUERY CACHE + 写操作后验证 |
| 分布式缓存 (Redis) | EXPIRE / PEXPIRE / SETEX | 30s ~ 3600s | TTL 命令 + 等待过期 + Keyspace Notification |
| 分布式缓存 (Memcached) | set 命令的 timeout 参数 | 0 ~ 2592000s (30天) | get 命令返回 null + 统计 miss 次数 |
重要提醒: 在集成测试环境中,不要依赖生产环境的缓存过期时间。测试环境应使用更短的 TTL(如 5~10 秒),以加速测试执行。同时,确保测试用例之间清理缓存状态,避免测试间相互污染。