4. 错误处理设计原则:面向API的错误码设计哲学

在缓存一致性治理中,API 错误处理不是简单的“返回 500”或“抛异常”。错误码是契约的核心组成部分,它决定了调用方能否快速定位问题、自动恢复或优雅降级。本节提出语义化、层次化、可扩展三大设计哲学,并给出具体的错误码结构规范。

4.1 三大设计哲学

哲学 核心要求 反例 正例
语义化 错误码本身携带业务含义,无需查文档即可大致理解 ERR-001 CACHE_STALE_READ
层次化 错误码按模块、场景、具体原因分层,便于聚合和告警 所有错误统一归为 500 4.3.2 → 缓存层·一致性·版本冲突
可扩展 新增错误码不破坏已有解析逻辑,不产生歧义 使用连续数字,中间插入新码导致范围冲突 使用预留段 + 命名空间前缀
设计原则:错误码是给机器读的,错误消息是给人读的。两者必须分离,且错误码的解析成本应低于 1μs。

4.2 语义化:从数字到可读标识符

传统 HTTP 状态码(如 400、500)粒度太粗,无法表达缓存一致性场景的细微差异。语义化要求错误码使用英文驼峰短语,让调用方 SDK 可以直接将其映射为枚举或异常类型。

// 推荐:语义化错误码
CACHE_MISS_AND_LOCK_FAILED
CACHE_VERSION_CONFLICT
CACHE_BACKEND_TIMEOUT
CASCADE_INVALIDATION_SKIPPED

// 不推荐:无意义数字
ERR_1001
ERR_1002
ERR_1003
缓存一致性场景示例:当写请求发现缓存版本号与数据库版本号不一致时,返回 CACHE_VERSION_CONFLICT,调用方收到后自动触发重读+重试,而不是直接抛 500。

4.3 层次化:错误码的树状结构

层次化设计将错误码拆分为域·模块·场景·原因四级,每级用点号或下划线分隔。这样既保留了机器可解析的紧凑性,又保留了人类可读的语义。

层次化错误码结构(四级模型) 错误码根 域:CACHE 域:DB 域:NET 一致性 失效 版本 超时 读修复 写修复 版本冲突 连接超时 锁获取失败 版本过期 CAS 比较失败 后端无响应 完整错误码示例:CACHE.CONSISTENCY.READ_REPAIR.LOCK_FAILED
层次化优势:监控系统可以按“域”聚合告警,按“模块”定位瓶颈,按“场景”分析失败模式,按“原因”触发自动化恢复脚本。

4.4 可扩展:预留段与命名空间

错误码系统必须预见到未来新增缓存策略、新增一致性模型、新增后端存储的情况。可扩展设计遵循以下规则:

  • 预留段:每个模块预留 20% 的错误码空间(如 0-79 为当前使用,80-99 为预留)。
  • 命名空间前缀:使用 CACHE_、DB_、NET_ 等前缀,避免与其他系统冲突。
  • 版本化:在错误码响应体中携带 error_schema_version 字段,支持向后兼容。
// OpenAPI 3.1 错误码 schema 示例
components:
  schemas:
    CacheError:
      type: object
      properties:
        error_code:
          type: string
          description: "语义化错误码,如 CACHE_VERSION_CONFLICT"
        error_hierarchy:
          type: string
          description: "层次化路径,如 4.3.2"
        error_schema_version:
          type: string
          description: "错误码 schema 版本,用于兼容性判断"
          example: "1.2"
        message:
          type: string
          description: "人类可读的错误描述"
        details:
          type: object
          description: "可扩展的上下文信息,如冲突的版本号"

4.5 缓存一致性场景错误码清单

错误码 层次路径 HTTP 状态码 说明
CACHE_MISS_AND_LOCK_FAILED 4.1.1 503 缓存未命中且分布式锁获取失败,无法回源
CACHE_VERSION_CONFLICT 4.3.2 409 CAS 写入时版本号不匹配,需重试
CACHE_BACKEND_TIMEOUT 4.4.1 504 缓存后端(Redis/Memcached)响应超时
CASCADE_INVALIDATION_SKIPPED 4.2.3 200 级联失效被跳过(非错误,但需记录)
DB_STALE_READ_DETECTED 3.1.4 200 检测到数据库脏读,返回旧数据并触发异步修复
注意:错误码一旦发布进入生产,禁止删除或修改语义。只能通过新增或废弃(deprecate)来演进。废弃的错误码在响应中仍可返回,但需附带 deprecated: true 标记。

4.6 错误码在 OpenAPI 契约中的声明方式

在 OpenAPI 3.1 中,错误码应作为 response 的 oneOf 或 discriminator 来定义,确保每个错误码都有独立的 schema 和示例。

paths:
  /cache/{key}:
    get:
      responses:
        '200':
          description: 成功返回缓存值
        '409':
          description: 版本冲突
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CacheError'
              example:
                error_code: "CACHE_VERSION_CONFLICT"
                error_hierarchy: "4.3.2"
                message: "缓存版本与数据库版本不一致,请重试"
                details:
                  expected_version: 5
                  actual_version: 3
        '503':
          description: 缓存后端不可用
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CacheError'
              example:
                error_code: "CACHE_BACKEND_TIMEOUT"
                error_hierarchy: "4.4.1"
                message: "缓存后端连接超时,已触发降级策略"
最佳实践:在 API 网关层统一拦截错误码,将层次化路径写入日志的 error_hierarchy 字段,便于后续的根因分析和告警聚合。

通过语义化、层次化、可扩展三大设计哲学,错误码从“调试辅助信息”升级为“可编程的契约元素”。调用方可以根据错误码自动选择重试、降级、熔断或告警,而无需解析冗长的错误消息。这是缓存一致性治理中错误处理链路设计的基石。