3、错误处理哲学:分布式系统中错误处理的黄金法则、幂等性、补偿与重试策略
在分布式审批流系统中,网络抖动、服务宕机、数据不一致是常态而非异常。错误处理不是“事后补救”,而是状态机设计的核心骨架。本章从哲学层面提炼三条黄金法则,并深入幂等性、补偿与重试三大实战策略。
3.1 分布式错误处理的黄金法则
任何分布式操作都必须遵循以下三条法则,它们构成了错误处理决策树的根节点:
| 法则 | 核心原则 | 在审批流中的体现 |
|---|---|---|
| 法则一:先写后做 | 在发起任何远程调用或副作用前,先将“意图”持久化到数据库。 | 审批节点状态先标记为“处理中”,再发送通知或调用外部服务。 |
| 法则二:幂等守护 | 同一操作无论执行一次还是多次,对系统状态的影响必须一致。 | “审批通过”操作重复执行不会导致重复扣款或重复发奖。 |
| 法则三:最终一致 | 允许短暂不一致,但必须通过补偿或重试在有限时间内达成一致。 | 审批完成后,下游库存扣减失败,通过定时任务补偿回滚。 |
架构师视角: 三条法则的优先级是递进的。先确保“意图不丢”(法则一),再保证“重复无害”(法则二),最后兜底“不一致可修复”(法则三)。违反任意一条,状态机都会出现不可逆的“幽灵状态”。
3.2 幂等性:状态机的防重盾牌
幂等性是分布式错误处理的基石。在审批流中,最常见的非幂等场景是“重复回调”与“重复提交”。
3.2.1 幂等实现的三种模式
| 模式 | 实现方式 | 适用场景 | 风险 |
|---|---|---|---|
| 唯一键去重 | 使用业务ID(如审批单号+节点ID)作为数据库唯一约束,重复插入直接忽略。 | 消息队列消费、回调接口 | 需保证唯一键在分布式环境下全局不重复 |
| 状态机前置校验 | 执行操作前检查当前状态是否允许目标状态转移。 | 用户重复点击“审批通过” | 需配合乐观锁防止并发 |
| Token 令牌机制 | 每次操作前从服务端获取一次性 Token,提交时校验并销毁。 | 前端防重复提交、支付回调 | 增加一次网络交互,需处理 Token 过期 |
// 唯一键去重示例(伪代码)
public boolean handleApprovalCallback(String approvalId, String nodeId) {
String uniqueKey = approvalId + ":" + nodeId + ":APPROVED";
try {
// 数据库唯一约束保证幂等
insertIntoEventLog(uniqueKey, "PROCESSING");
// 执行实际状态转移
stateMachine.transit(approvalId, nodeId, Event.APPROVE);
updateEventLog(uniqueKey, "SUCCESS");
return true;
} catch (DuplicateKeyException e) {
// 重复回调,直接返回成功
log.warn("幂等拦截:重复回调 {}", uniqueKey);
return true;
}
}
3.3 补偿策略:优雅回滚的艺术
当状态机执行到一半,某个下游服务失败且无法重试时,补偿是唯一出路。补偿不是简单的“删除数据”,而是执行一个业务语义上的反向操作。
3.3.1 补偿的三种粒度
- 事务级补偿: 在单个数据库事务内,通过 try-catch 直接回滚。适用于本地事务。
- 服务级补偿: 调用下游服务的“撤销”接口(如退款、取消订单)。适用于跨服务调用。
- 流程级补偿: 启动一个反向的审批流(如“撤回申请”),将整个流程恢复到上一个稳定状态。
避坑指南: 补偿操作本身也必须幂等。如果补偿失败,需要记录补偿日志并触发人工介入。不要设计“无限补偿”循环,应设置最大补偿次数。
3.4 重试策略:有限度的坚持
重试是应对瞬时故障的首选策略,但必须遵循“有限度、有间隔、有熔断”的原则。
3.4.1 重试策略对比
| 策略 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| 固定间隔重试 | 每次等待固定时间(如 5s)后重试 | 实现简单 | 容易造成“惊群效应”,对下游压力大 |
| 指数退避 | 等待时间按指数增长(1s, 2s, 4s, 8s...) | 对下游友好,减少瞬时压力 | 总等待时间可能过长 |
| 抖动退避 | 在指数退避基础上增加随机偏移 | 避免多个客户端同时重试 | 实现稍复杂 |
| 最大重试次数 | 达到上限后不再重试,转入补偿或人工 | 防止无限循环 | 需合理设置阈值 |
3.5 错误处理全景图
以下 SVG 图展示了从“操作发起”到“最终一致”的完整错误处理决策路径,包含幂等、重试与补偿的协同关系。
3.6 策略组合:从理论到实战
单一策略无法应对所有故障。以下表格展示了不同故障场景下的策略组合推荐:
| 故障场景 | 推荐策略组合 | 说明 |
|---|---|---|
| 网络超时(瞬时) | 幂等 + 指数退避重试(3次) | 重试成功后继续流程,失败则转入补偿 |
| 下游服务不可用(持续) | 幂等 + 熔断 + 补偿 | 直接熔断,不重试,执行补偿回滚 |
| 数据冲突(乐观锁失败) | 重试(固定间隔1次)+ 幂等 | 重新读取最新状态后重试,避免死锁 |
| 业务校验失败 | 不重试,直接补偿 | 业务规则错误,重试无意义,需人工介入 |
核心总结: 错误处理的本质是“确定性”。通过幂等保证重复无害,通过重试克服瞬时波动,通过补偿修复不可逆错误。三者构成一个闭环,让状态机在不可靠的分布式环境中依然保持可靠。