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。
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)淘汰的是最近最少访问的键,这可能导致搜索服务的热门缓存被意外清除。
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 缓存失效场景的契约测试流程
下图展示了四种失效场景在契约测试中的执行流程,以及搜索服务如何根据错误码做出响应:
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 缓存异常时仍能返回符合契约的错误码,避免生产环境中的不可预期行为。