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 错误码类别进行断言分支处理:

Redis 错误码契约断言流程 Redis 响应到达 是否错误? 正常响应断言 错误码分类 服务端错误 (-ERR) 客户端错误 (-WRONGTYPE) 超时错误 (-ETIMEDOUT) 连接错误 (-ECONNREFUSED) 数据错误 (-LOADING) 契约断言通过/失败

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 或数据驱动方式覆盖所有已知错误码变体。同时,将错误码断言阈值(如重试次数、熔断计数)抽取为契约常量,便于统一维护。