4. Redis 缓存失效场景分析:TTL过期、LRU淘汰、内存达到maxmemory、主从切换导致缓存清空

在契约测试中,缓存失效是导致搜索服务返回错误码的核心诱因之一。Redis 作为缓存层,其数据丢失或不可用会直接触发服务降级或异常响应。本章深入分析四种典型的缓存失效场景,帮助契约测试工程师精准构造失效条件,验证搜索服务在缓存缺失时的错误码处理逻辑。

4.1 四种缓存失效场景总览

Redis 缓存失效并非单一原因,而是由数据生命周期、内存策略、架构变更等多维度因素触发。下表对比了四种场景的触发机制、影响范围及契约测试关注点:

失效场景 触发机制 影响范围 契约测试关注点
TTL 过期 键设置了生存时间,到期自动删除 单个或批量键 过期后首次查询是否返回 MISS 及对应错误码
LRU 淘汰 内存使用接近 maxmemory,按最近最少使用算法驱逐键 被驱逐的冷数据键 淘汰后查询是否触发缓存重建流程及错误码
内存达到 maxmemory 写入新键时内存已满,触发淘汰策略 新写入失败或旧键被驱逐 写入失败时服务是否返回限流或降级错误码
主从切换导致缓存清空 主节点宕机,从节点晋升为主,但从节点数据可能不一致或为空 全库或部分数据 切换后查询是否因缓存空洞返回特定错误码

4.2 TTL 过期:最常见的缓存空洞

TTL(Time To Live)是 Redis 管理缓存生命周期的核心机制。搜索服务通常为每个查询结果设置 5~30 分钟的 TTL。当 TTL 归零时,Redis 自动删除该键,后续查询将触发缓存 MISS。

契约测试要点: 在契约中明确约定 TTL 过期后的错误码行为。例如,搜索服务应返回 ERR_CACHE_MISS(错误码 1003),而非直接返回空结果或 500 错误。测试时可通过 TTL 命令确认剩余时间,或直接使用 EXPIRE 命令将键设置为立即过期。
# 模拟 TTL 过期场景
> SET search:query:keyword "cached_result" EX 300
OK
> TTL search:query:keyword
(integer) 295
# 契约测试中强制过期
> EXPIRE search:query:keyword 0
(integer) 1
> GET search:query:keyword
(nil)
# 此时搜索服务应返回 ERR_CACHE_MISS

4.3 LRU 淘汰与 maxmemory 策略

当 Redis 内存使用达到 maxmemory 限制时,会根据配置的淘汰策略(如 allkeys-lru、volatile-lru)驱逐键。LRU(Least Recently Used)淘汰的是最近最少访问的键,这可能导致搜索服务的热门缓存被意外清除。

风险提示: 在契约测试中,LRU 淘汰难以精确预测被驱逐的键。建议通过以下方式构造可控场景:
1. 设置较小的 maxmemory(如 1MB)并写入大量数据触发淘汰。
2. 使用 OBJECT IDLETIME 命令确认键的空闲时间,优先淘汰空闲时间最长的键。
3. 在契约中约定:当缓存被淘汰时,搜索服务应返回 ERR_CACHE_EVICTED(错误码 1004),并触发异步重建。

4.4 主从切换导致缓存清空

Redis 主从架构中,当主节点故障时,哨兵或集群会自动将从节点提升为主节点。然而,从节点可能因复制延迟、数据不一致或配置差异导致缓存数据不完整甚至为空。这种场景在契约测试中常被忽略,但却是生产环境中最严重的缓存失效原因之一。

主从切换缓存失效的典型表现:
• 从节点未开启 slave-read-only yes,导致写入冲突。
• 从节点复制积压缓冲区不足,部分写操作丢失。
• 切换后新主节点内存中无热点数据,所有查询均 MISS。
• 搜索服务连续收到 ERR_CACHE_EMPTY(错误码 1005),触发数据库回源风暴。

4.5 缓存失效场景与错误码映射关系

契约测试需要为每种失效场景定义明确的错误码映射。下表展示了搜索服务推荐的错误码设计:

失效场景 错误码 错误描述 服务行为
TTL 过期 1003 ERR_CACHE_MISS 回源数据库,重建缓存
LRU 淘汰 1004 ERR_CACHE_EVICTED 异步重建,返回旧数据或降级
maxmemory 写入失败 1006 ERR_CACHE_FULL 限流或返回降级提示
主从切换缓存清空 1005 ERR_CACHE_EMPTY 触发全量回源,限流保护数据库

4.6 缓存失效场景的契约测试流程

下图展示了四种失效场景在契约测试中的执行流程,以及搜索服务如何根据错误码做出响应:

缓存失效场景契约测试流程 发起查询请求 Redis 缓存是否存在? 返回缓存结果 判断失效场景 TTL 过期 ERR_CACHE_MISS LRU 淘汰 ERR_CACHE_EVICTED maxmemory ERR_CACHE_FULL 主从切换 ERR_CACHE_EMPTY 回源数据库重建缓存

4.7 契约测试中的场景构造方法

针对上述四种场景,契约测试需要采用不同的构造手段:

  • TTL 过期: 使用 EXPIRE 或 PEXPIRE 命令将键的剩余生存时间设置为 0 或负数,立即触发过期。
  • LRU 淘汰: 通过 CONFIG SET maxmemory 1mb 临时降低内存上限,然后写入大量随机键触发淘汰。使用 INFO stats 确认 evicted_keys 计数增长。
  • maxmemory 写入失败: 设置 maxmemory-policy noeviction,然后写入新键直到内存满,观察写入失败时的错误码。
  • 主从切换: 在测试环境中使用 DEBUG SLEEP 模拟主节点故障,或通过哨兵 API 手动触发故障转移。切换后立即查询缓存,验证错误码。
契约测试断言示例:
// 契约:缓存 MISS 时应返回 1003 错误码
given:
  redis.set("search:query:test", "value", 0) // TTL=0 立即过期
when:
  response = searchService.query("test")
then:
  response.statusCode == 200
  response.body.errorCode == 1003
  response.body.errorMessage == "ERR_CACHE_MISS"

4.8 缓存失效对搜索服务的影响矩阵

不同失效场景对搜索服务的影响程度不同,契约测试需要覆盖从轻微降级到全量回源的各种情况。下表总结了影响矩阵:

失效场景 影响级别 响应时间变化 数据库压力 推荐契约覆盖
TTL 过期 低 +50~200ms 单次回源 必须覆盖
LRU 淘汰 中 +100~500ms 冷数据回源 建议覆盖
maxmemory 写入失败 高 立即返回错误 无 必须覆盖
主从切换缓存清空 严重 +500ms~数秒 全量回源风暴 强烈建议覆盖

通过系统性地分析这四种缓存失效场景,契约测试工程师可以设计出覆盖全面、可重复执行的测试用例,确保搜索服务在 Redis 缓存异常时仍能返回符合契约的错误码,避免生产环境中的不可预期行为。