3. 错误码设计原则:HTTP 状态码与业务错误码的映射、错误码的层级结构

在搜索服务的高并发场景下,错误码不仅是客户端判断请求成败的依据,更是后端排查慢查询、定位系统瓶颈的关键索引。一个设计混乱的错误码体系会导致告警误报、日志无法聚合、SLA 统计失真。本章从 HTTP 状态码与业务错误码的映射关系出发,构建可观测、可归因的错误码层级结构。

3.1 HTTP 状态码与业务错误码的映射原则

HTTP 状态码用于表达传输层的语义(如 4xx 表示客户端错误,5xx 表示服务端错误),而业务错误码用于表达应用层的具体失败原因(如“搜索索引不存在”“查询超时”)。两者应遵循 “粗粒度映射 + 细粒度补充” 原则:

  • 2xx:仅映射业务成功(错误码为 0 或 SUCCESS)。
  • 4xx:映射客户端可自行修复的错误,如参数校验失败、鉴权过期、请求频率超限。
  • 5xx:映射服务端内部故障,如数据库连接池耗尽、缓存雪崩、索引加载失败。
  • 禁止:将业务逻辑错误(如“商品已下架”)映射为 500,或将服务端超时映射为 400。
HTTP 状态码 语义 典型业务错误码范围 示例场景
200 请求成功 0 搜索正常返回结果
400 请求参数错误 100001 – 100099 缺少必填字段、关键词过长
401 鉴权失败 100101 – 100199 Token 过期、签名不匹配
403 权限不足 100201 – 100299 无索引读权限
429 请求频率超限 100301 – 100399 QPS 超过配额
500 服务端内部错误 200001 – 200099 数据库连接失败、panic 未恢复
503 服务暂时不可用 200101 – 200199 索引重建中、熔断降级
504 网关超时 200201 – 200299 后端查询超过 5s 未返回
实战建议:在 Go 中间件中统一拦截业务错误码,自动映射到对应的 HTTP 状态码。避免在每个 handler 中重复写 w.WriteHeader(),减少遗漏和歧义。

3.2 错误码的层级结构:系统级 / 模块级 / 业务级

为了在日志聚合、告警降噪、慢查询归因时能快速定位故障域,错误码应采用 三段式层级结构。每一层承载不同的定位粒度:

  • 系统级(1 位):标识错误来源系统,如搜索服务、用户中心、商品中心。
  • 模块级(2 位):标识子系统内的功能模块,如索引模块、查询模块、排序模块。
  • 业务级(3 位):标识具体的失败原因,如索引不存在、查询超时、排序字段非法。

推荐编码格式:X-YY-ZZZ,例如 1-03-007 表示“搜索服务 - 查询模块 - 查询超时”。

编码示例:
// 系统级:1 = 搜索服务,2 = 用户中心,3 = 商品中心
// 模块级:01 = 索引模块,02 = 缓存模块,03 = 查询模块
// 业务级:001 = 索引不存在,007 = 查询超时,012 = 排序字段非法

const (
    ErrSearchIndexNotFound = "1-01-001"
    ErrSearchQueryTimeout  = "1-03-007"
    ErrSearchSortInvalid   = "1-03-012"
)

下表展示了搜索服务中常见的错误码层级分配:

层级 编码段 示例 说明
系统级 1 1-xx-xxx 搜索服务
模块级 01 – 10 1-03-xxx 03 = 查询模块
业务级 001 – 999 1-03-007 007 = 查询超时
避坑提醒:不要将错误码设计为纯数字且无分段(如 1003007),在日志分析时无法快速区分系统归属。也不要将模块级编码超过 99,否则会破坏三段式的可读性。

3.3 错误码层级结构图

下图展示了搜索服务中错误码的三段式层级关系,以及从 HTTP 状态码到业务错误码的映射路径:

错误码层级结构与 HTTP 映射 HTTP 状态码 HTTP 状态码 HTTP 状态码 系统级:1 系统级:2 系统级:3 模块级:01-10 模块级:01-10 模块级:01-10 业务级:001-999 业务级:001-999 业务级:001-999 映射规则 2xx → 0 4xx → 1xx 5xx → 2xx 系统级 标识服务 模块级 标识功能域 业务级 标识具体原因

3.4 慢查询场景下的错误码断言实战

在 Go 搜索服务中,错误码断言常用于慢查询根因分析。以下是一个典型的错误码断言流程:

  1. 捕获慢查询:在中间件中记录请求耗时超过 500ms 的日志。
  2. 提取错误码:从响应体中解析业务错误码字段 code。
  3. 断言层级:先判断系统级是否为搜索服务(1),再判断模块级是否为查询模块(03),最后定位业务级原因。
  4. 触发告警:如果错误码为 1-03-007(查询超时),则触发 P0 告警并自动拉取对应时间段的数据库慢查询日志。
Go 代码示例:错误码断言与慢查询关联
type ErrorCode string

const (
    SystemSearch   = "1"
    ModuleQuery    = "03"
    ErrQueryTimeout = "1-03-007"
)

// AssertSlowQuery 断言慢查询根因
func AssertSlowQuery(code ErrorCode, latency time.Duration) string {
    parts := strings.Split(string(code), "-")
    if len(parts) != 3 {
        return "invalid_code_format"
    }
    if parts[0] != SystemSearch {
        return "not_search_service"
    }
    if parts[1] != ModuleQuery {
        return "not_query_module"
    }
    if code == ErrQueryTimeout && latency > 500*time.Millisecond {
        return "trigger_slow_query_alert"
    }
    return "other_error"
}
可观测性集成:将错误码三段式结构作为 Prometheus 的 label 上报,例如 error_code_total{system="1", module="03", biz="007"}。在 Grafana 中可以直接按模块维度下钻,快速定位慢查询高发模块。

3.5 错误码设计检查清单

检查项 通过标准 常见反例
HTTP 映射一致性 4xx 不映射业务成功,5xx 不映射客户端错误 将“参数缺失”返回 500
层级可解析 错误码字符串可被正则或 split 拆分为三段 纯数字无分隔符
模块级不超 99 模块编码范围 01-99 模块编码 100
业务级不超 999 业务编码范围 001-999 业务编码 1000
唯一性 全系统内错误码不重复 搜索服务和用户中心共用 001