3. 错误处理基础理论:错误分类、传播模型与幂等补偿

在分布式支付系统中,错误是常态而非异常。中间件必须对错误进行精确分类、可控传播,并通过幂等与补偿机制保证最终一致性。本章从三个核心维度构建错误处理的理论基础。

3.1 错误分类:业务错误、系统错误、网络错误

支付链路中的错误按来源与影响范围可分为三类。错误的分类决定了中间件的处理策略:是重试、降级、告警,还是直接拒绝。

错误类型 定义 典型场景 处理策略
业务错误 因业务规则校验失败产生的可预期错误 余额不足、风控拦截、重复支付、优惠券过期 直接拒绝,返回明确错误码与提示,不重试
系统错误 服务内部状态异常或资源耗尽导致的错误 数据库连接池满、OOM、线程死锁、配置错误 快速失败,触发熔断/降级,记录完整堆栈
网络错误 通信链路不稳定或超时导致的临时性错误 TCP 超时、DNS 解析失败、TLS 握手中断 有限次重试(指数退避),配合幂等防止重复
实践要点:业务错误必须携带结构化错误码(如 PAY_BALANCE_INSUFFICIENT),便于客户端精确处理;系统错误应区分“可恢复”与“不可恢复”,不可恢复错误直接触发告警而非重试。

3.2 错误传播模型

错误在微服务调用链中的传播方式直接影响系统稳定性。支付中间件通常采用以下三种传播模型:

  • 直接传播(Propagate): 将底层错误原样抛给上层调用方。适用于业务错误,保持语义透明。
  • 包装传播(Wrap): 将底层错误包装为通用异常,附加上下文信息(如 TraceId、服务名)。适用于系统错误,防止内部细节泄露。
  • 静默吞没(Silent): 捕获错误后记录日志,返回默认值或空结果。仅适用于非关键路径(如日志上报、非核心指标采集)。
支付链路中的传播规则: 核心支付链路(扣款、转账)禁止静默吞没;所有跨服务调用必须携带错误传播上下文(TraceId + SpanId),确保错误根因可追溯。
错误传播模型对比 服务A(调用方) 服务B(抛出原始异常) 直接传播:异常类型、消息、堆栈原样传递 服务C(调用方) 服务D(包装异常) 包装传播:原始异常被包裹,附加 TraceId、服务名 服务E(调用方) 服务F(吞没异常) 静默吞没:异常被捕获,仅记录日志,返回默认值 支付链路传播规则 核心支付链路(扣款、转账)禁止使用静默吞没模型 所有跨服务调用必须携带 TraceId + SpanId 上下文

3.3 幂等性与补偿机制

3.3.1 幂等性设计

幂等性是指同一操作无论执行多少次,其结果与执行一次相同。在支付系统中,幂等性是防止重复扣款、重复退款的核心保障。

幂等性实现方案对比:

方案 原理 适用场景 注意事项
全局唯一ID 请求携带唯一幂等键,服务端去重 支付下单、退款申请 幂等键需全局唯一,建议使用 UUID 或雪花算法
状态机驱动 业务状态单向流转,重复请求不改变已终态 订单状态更新、转账流程 状态转换图需严格定义,禁止回退
乐观锁 使用版本号或时间戳,更新时校验 库存扣减、余额变更 高并发下需控制重试次数,避免活锁
去重表 数据库唯一索引约束,重复插入失败 消息消费、回调处理 需定期清理过期记录,避免表膨胀
幂等陷阱: 仅依赖数据库唯一索引无法覆盖“请求已处理但响应超时”的场景。必须结合请求去重缓存(如 Redis SETNX)与状态机双重校验,才能实现真正的幂等。

3.3.2 补偿机制

当分布式事务中的部分操作失败时,补偿机制用于回滚已成功的操作,恢复系统到一致状态。支付系统常用的补偿模式包括:

  • TCC(Try-Confirm-Cancel): 预留资源 → 确认提交 → 取消回滚。适用于账户冻结、库存预留。
  • Saga 模式: 将长事务拆分为多个本地事务,每个事务对应一个补偿操作。失败时按逆序执行补偿。
  • 本地消息表 + 定时任务: 将补偿操作记录为消息,由定时任务扫描并执行。
Saga 补偿流程示例(支付失败回滚) 扣减账户余额 冻结库存 生成订单 支付失败 取消订单(补偿) 解冻库存(补偿) 恢复余额(补偿) 正向流程(绿色)→ 支付失败触发 → 逆序执行补偿操作(橙色)

3.4 错误处理策略总结

支付中间件在接收到不同类型的错误时,应遵循以下决策树:

  1. 判断错误类型: 业务错误 → 直接拒绝,返回错误码;系统/网络错误 → 进入重试判断。
  2. 判断是否可重试: 幂等操作 → 有限次重试(通常 3 次,指数退避);非幂等操作 → 快速失败,触发补偿。
  3. 判断补偿可行性: 支持补偿 → 执行 Saga/TCC 回滚;不支持补偿 → 人工介入,发送告警。
核心原则: 业务错误不重试,系统错误限重试,网络错误必重试;所有重试必须配合幂等机制;所有补偿操作必须可观测、可追溯。