4. Redis错误码体系:服务端错误、客户端错误、超时错误、连接错误、数据错误
在搜索服务的契约测试中,Redis 错误码是断言通信质量的核心指标。错误码体系可划分为五大类:服务端错误、客户端错误、超时错误、连接错误、数据错误。每一类错误在契约测试中都有对应的断言策略和阈值定义。
4.1 错误码分类总览
下表列出了五类错误码的典型场景、Redis 原生错误前缀以及契约测试中的断言关注点:
| 错误类别 | 典型场景 | Redis 错误前缀 | 契约断言关注点 |
|---|---|---|---|
| 服务端错误 | 内存超限、AOF 写失败、主从复制中断 | -ERR / -MISCONF |
错误码白名单、重试策略、降级开关 |
| 客户端错误 | 命令语法错误、参数类型不匹配、认证失败 | -WRONGTYPE / -NOAUTH |
请求格式校验、参数契约、异常响应结构 |
| 超时错误 | 命令执行超时、阻塞队列超时 | -READONLY (超时变体) |
超时阈值、熔断触发条件、客户端超时配置 |
| 连接错误 | 连接池耗尽、网络断开、DNS 解析失败 | -ECONNREFUSED / -ETIMEDOUT |
连接池大小、健康检查间隔、重连机制 |
| 数据错误 | 数据损坏、序列化失败、缓存穿透 | -LOADING / -BUSY |
数据完整性校验、序列化契约、缓存一致性 |
契约测试要点: 在搜索服务接口的契约中,
-ERR 和 -MISCONF 通常被标记为“可重试”错误,而 -WRONGTYPE 和 -NOAUTH 则属于“不可重试”错误,需要直接返回错误响应。
4.2 错误码分类与契约断言流程
下图展示了搜索服务在契约测试中如何根据 Redis 错误码类别进行断言分支处理:
4.3 服务端错误与客户端错误
服务端错误 通常由 Redis 实例的内部状态引发,例如内存达到 maxmemory 限制、AOF 重写失败或主从复制链路中断。在契约测试中,服务端错误应触发重试断言,验证搜索服务是否在指定次数内恢复。
客户端错误 则源于请求本身的问题,如命令语法错误、操作了错误的数据类型(-WRONGTYPE)或认证信息缺失(-NOAUTH)。契约测试应断言客户端错误不触发重试,而是直接返回 4xx 或 5xx 响应。
契约断言示例:
// 服务端错误:允许重试 3 次
assertRedisErrorRetry("ERR OOM", 3, 100);
// 客户端错误:禁止重试,直接返回
assertRedisErrorNoRetry("WRONGTYPE Operation against a key holding the wrong kind of value");
4.4 超时错误与连接错误
超时错误 在搜索服务中尤为关键。当 Redis 命令执行时间超过客户端配置的 timeout 阈值时,会抛出 RedisTimeoutException。契约测试需要断言:
- 超时阈值是否与搜索接口的 SLA 一致(通常 ≤ 50ms)。
- 超时后是否触发熔断器(Circuit Breaker)进入 OPEN 状态。
- 熔断恢复后的半开探测请求是否正常。
连接错误 包括连接池耗尽、网络分区、DNS 解析失败等。契约测试应验证:
- 连接池最大连接数是否与搜索服务的并发模型匹配。
- 连接断开后是否在预期时间内发起重连。
- 重连失败后是否返回降级数据(如本地缓存)。
4.5 数据错误
数据错误 涵盖数据损坏、序列化/反序列化失败、缓存穿透导致的数据不一致。在搜索服务中,数据错误通常表现为:
- 从 Redis 读取的搜索结果无法反序列化为预期的 Protobuf 结构。
- 缓存键的 TTL 设置错误导致数据提前过期。
- Redis 处于
LOADING状态(持久化恢复中)时拒绝服务。
契约测试应断言数据错误场景下的降级策略:是否返回空结果、是否回查数据库、是否记录告警日志。
常见陷阱: 在契约测试中,不要将
-LOADING 错误简单地归类为服务端错误。它实际上是数据层面的临时状态,应触发“等待并重试”策略,而非立即熔断。
4.6 错误码断言策略对比
| 错误类别 | 重试策略 | 熔断触发 | 降级策略 | 契约断言优先级 |
|---|---|---|---|---|
| 服务端错误 | 允许重试(3 次) | 连续 5 次触发 | 返回 503 | 高 |
| 客户端错误 | 禁止重试 | 不触发 | 返回 400 | 高 |
| 超时错误 | 允许重试(1 次) | 连续 3 次触发 | 返回 504 | 最高 |
| 连接错误 | 允许重试(无限,带退避) | 连续 10 次触发 | 返回本地缓存 | 中 |
| 数据错误 | 允许重试(2 次) | 连续 3 次触发 | 回查数据库 | 高 |
4.7 契约测试中的错误码断言实现
以下是一个基于 Java 的契约测试片段,展示如何针对不同 Redis 错误码进行断言:
@Test
public void testRedisErrorCodeAssertion() {
// 模拟服务端错误
when(redisClient.get("search:query:test"))
.thenThrow(new RedisException("ERR OOM command not allowed when used memory > 'maxmemory'"));
SearchResponse response = searchService.search("test");
assertThat(response.getStatus()).isEqualTo(503);
assertThat(response.getRetryCount()).isLessThanOrEqualTo(3);
// 模拟客户端错误
when(redisClient.get("search:query:test"))
.thenThrow(new RedisException("WRONGTYPE Operation against a key holding the wrong kind of value"));
response = searchService.search("test");
assertThat(response.getStatus()).isEqualTo(400);
assertThat(response.getRetryCount()).isEqualTo(0);
// 模拟超时错误
when(redisClient.get("search:query:test"))
.thenThrow(new RedisTimeoutException("Command timed out after 50ms"));
response = searchService.search("test");
assertThat(response.getStatus()).isEqualTo(504);
assertThat(response.getCircuitBreakerState()).isEqualTo("OPEN");
}
最佳实践: 在契约测试中,建议为每类错误码单独编写测试用例,并使用
@ParameterizedTest 或数据驱动方式覆盖所有已知错误码变体。同时,将错误码断言阈值(如重试次数、熔断计数)抽取为契约常量,便于统一维护。