分布式事务补偿:失败后能收场,比强一致更现实
分布式事务补偿:失败后能收场,比强一致更现实
一、不是所有业务都需要强一致
分布式系统里,订单、库存、积分、通知、账单经常跨服务。很多团队一遇到一致性问题,就想上强事务。但强一致成本高、耦合重、可用性差。很多业务更适合最终一致和补偿机制。
补偿不是偷懒,而是在现实约束下设计可恢复流程。
二、先识别业务步骤
flowchart TD A[创建订单] --> B[扣库存] B --> C[扣优惠券] C --> D[发送通知] D --> E[完成] C --> F[失败补偿]每个步骤都要知道成功后如何补偿。扣库存可以释放,优惠券可以退回,通知可能无法撤回,只能追加说明。
saga_step: action: deduct_coupon compensation: refund_coupon idempotent: true没有补偿动作的步骤,要标记为高风险。
三、状态机比本地事务更重要
enum SagaState { CREATED, STOCK_DEDUCTED, COUPON_DEDUCTED, COMPLETED, COMPENSATING, FAILED }Saga 流程必须持久化状态。服务重启后,要知道执行到哪一步,哪些动作需要继续,哪些需要补偿。
只靠方法调用栈管理分布式流程,一旦中途失败就会失去上下文。
四、补偿也要幂等
补偿动作可能被重复执行。释放库存、退回优惠券、撤销冻结金额,都必须设计幂等。否则修复失败会制造新的不一致。
compensation_policy: idempotency_key_required: true retry_with_backoff: true manual_review_after_max_retry: true补偿失败后不要无限重试。达到上限要进入人工处理,并保留足够上下文。
还要区分技术失败和业务失败。网络超时可以重试,业务规则不允许就不能硬补偿。错误分类不清,补偿流程会很危险。
最后,补偿流程要可观测。每个 Saga 的状态、耗时、失败步骤、补偿次数都要能查。最终一致不是最终没人管。
补偿设计还要考虑用户体验。订单显示“处理中”多久,失败后用户看到什么,补偿完成后是否通知,都要和技术状态机对应。否则后台最终一致了,前台用户仍然困惑。
saga_user_state: processing: show_pending compensating: show_recovering failed_final: show_contact_support还要有超时策略。某个 Saga 卡在中间状态太久,系统应该自动扫描并推进补偿或告警。不能等待用户投诉才发现流程停住。
对账任务也很关键。最终一致系统必须定期比较订单、库存、优惠券、账务等结果,发现漏补偿或重复补偿。没有对账,补偿机制很难让人放心。
最后,补偿动作要经过权限和审计。自动补偿也是业务变更,不能因为是系统触发就跳过记录。
补偿流程还要考虑并发。用户可能取消订单,系统同时在补偿库存;支付回调可能迟到,Saga 又在走失败流程。如果没有状态锁或版本号,补偿可能和正常流程互相覆盖。
saga_concurrency: use_state_version: true reject_stale_transition: true lock_by_business_key: true状态转换要原子化。每一步从一个状态变到另一个状态时,都要确认当前状态仍符合预期。否则重复消息、迟到事件和人工操作会把流程打乱。
补偿机制的测试策略也值得深入讨论。传统的单元测试、集成测试很难完整覆盖分布式补偿场景,因为涉及到网络分区、部分失败、并发冲突、超时等复杂情况。一种有效的方法是"故障注入测试":在 Saga 执行过程中,模拟特定步骤失败、消息重复、数据库不可用等情况,验证补偿逻辑是否能够正确回滚。可以在测试环境中使用 Chaos Mesh 或类似工具,在 Saga 的不同阶段注入故障,观察系统的行为。这种测试虽然成本较高,但对于关键业务链路来说,是验证补偿机制正确性的重要手段。
还有一个现实问题:补偿失败的"终极处理"。即使设计了完善的补偿机制和重试策略,仍然可能存在某些情况,补偿动作本身持续失败(比如补偿依赖的外部系统永久下线)。这时候需要"人工兜底流程":系统将无法自动补偿的事务标记为"需要人工处理",通知相关人员,并提供足够的上下文(原始操作、已执行的补偿步骤、失败原因等)。在设计时就要考虑这个"最后一道防线",包括人工处理界面的设计、处理记录的审计、以及处理后的状态对齐确认。假装所有补偿都能自动化成功,是不负责任的系统设计。
五、总结
分布式事务补偿要把业务步骤、状态机、补偿动作、幂等和人工兜底设计清楚。
失败后能收场,比强行追求所有场景强一致更现实,也更容易落地。
