3. 补偿事务原理:正向事务与补偿事务的配对关系、补偿操作的幂等性要求

Saga 事务模型的核心思想是将一个长事务拆分为多个本地事务(正向事务),并为每个正向事务定义一个补偿事务。当某个正向事务执行失败时,Saga 协调器会反向调用已成功正向事务对应的补偿事务,以撤销其影响。理解正向与补偿的配对关系以及补偿操作的幂等性,是设计可靠 Saga 的关键。

3.1 正向事务与补偿事务的配对关系

每个正向事务(T)必须有一个对应的补偿事务(C),二者形成一对一的配对关系。补偿事务的逻辑是正向事务的逆向操作,但并非简单的“反向执行”,而是业务语义上的撤销。

正向事务(T) 补偿事务(C) 配对说明
扣减库存 回滚库存(增加库存) 补偿操作必须能恢复库存数量到扣减前的状态
创建订单(状态:待支付) 取消订单(状态:已取消) 补偿操作将订单状态标记为取消,而非物理删除
扣减用户余额 增加用户余额 补偿操作必须增加相同金额,且考虑并发扣减场景
发送消息通知 发送撤销通知或标记消息为无效 补偿操作可能无法物理撤回已发送的消息,需业务兜底
关键原则:补偿事务必须从业务上“撤销”正向事务的效果,而不是简单地执行数据库回滚。例如,扣减库存的补偿是增加库存,而不是执行 ROLLBACK。

下图展示了 Saga 执行过程中正向事务与补偿事务的配对调用关系:

Saga 正向事务与补偿事务配对关系 Saga 正向事务与补偿事务配对关系 T1: 扣减库存 T2: 创建订单 T3: 扣减余额 成功 T3 执行失败 C3: 增加余额 C2: 取消订单 C1: 回滚库存 配对 配对 配对 说明: 1. 正向事务按顺序执行 T1 → T2 → T3。 2. T3 执行失败后,Saga 协调器按反向顺序调用补偿事务 C3 → C2 → C1。 3. 每个补偿事务与对应的正向事务形成业务上的“撤销”配对。 4. 补偿事务的执行顺序必须与正向事务相反(LIFO)。

3.2 补偿操作的幂等性要求

在分布式系统中,网络超时、节点故障、协调器重试等因素可能导致补偿事务被多次执行。如果补偿操作不具备幂等性,重复执行将引发数据不一致或业务异常。

幂等性定义:一个操作无论执行一次还是多次,其产生的业务结果相同。例如,“将订单状态设置为已取消”是幂等的,而“将余额增加 100 元”不是幂等的(重复执行会导致余额多增)。

3.2.1 幂等性失效的典型场景

  • 协调器重试:Saga 协调器在未收到补偿事务的确认响应时,会进行重试,导致补偿操作被多次调用。
  • 网络延迟与重复请求:客户端发送的补偿请求在网络中滞留,被服务端重复处理。
  • 数据库连接池超时:补偿事务因连接池等待超时而失败,重试时再次执行相同的补偿逻辑。

3.2.2 实现幂等性的常见策略

策略 实现方式 适用场景 注意事项
唯一请求 ID(幂等键) 为每个补偿请求生成全局唯一 ID,服务端记录已处理的 ID,重复请求直接返回成功 所有补偿操作 需要持久化存储幂等键,注意清理过期记录
状态机校验 补偿操作只允许在特定状态(如“待补偿”)下执行,执行后状态变更为“已补偿” 订单状态、流程状态 状态变更需原子性,防止并发覆盖
业务逻辑幂等 补偿操作本身设计为幂等,例如“设置库存为固定值”而非“增加库存” 库存、余额等数值型字段 需要业务支持绝对值的设置,而非相对增减
乐观锁/版本号 补偿操作携带版本号,更新时校验版本号是否匹配 高并发更新场景 版本号冲突时需重试或报错
最佳实践:推荐组合使用“唯一请求 ID + 状态机校验”。唯一请求 ID 防止重复请求,状态机校验确保业务状态正确,二者互补可覆盖绝大多数幂等性场景。

下图展示了基于唯一请求 ID 的幂等补偿流程:

基于唯一请求 ID 的幂等补偿流程 基于唯一请求 ID 的幂等补偿流程 协调器发起补偿 生成唯一请求 ID 检查幂等表 已存在 → 直接返回 不存在 → 执行补偿 记录幂等 ID 返回成功 关键点:幂等表使用唯一请求 ID 作为主键,插入时使用 INSERT ... ON DUPLICATE KEY 或唯一索引约束,确保同一 ID 只执行一次补偿操作。 即使协调器重试多次,幂等表也能保证补偿操作只生效一次。

3.3 补偿事务设计原则总结

  • 一对一配对:每个正向事务必须有且仅有一个对应的补偿事务,补偿逻辑必须能完全撤销正向事务的业务影响。
  • 反向顺序执行:补偿事务必须按照正向事务的相反顺序执行(后进先出),避免依赖关系错误。
  • 幂等性保障:所有补偿操作必须实现幂等,防止重试导致的数据不一致。
  • 业务语义补偿:补偿操作基于业务逻辑,而非数据库回滚。例如,扣减库存的补偿是增加库存,而不是执行 ROLLBACK。
  • 可观测性:补偿事务的执行状态、重试次数、幂等 ID 等信息应记录日志,便于故障排查。
案例提示:在“慢查询与连接池水位”的故障场景中,如果补偿事务因连接池等待超时而失败,协调器会重试补偿操作。此时,如果补偿操作不具备幂等性,重复执行将导致库存多回滚、余额多增加等严重问题。因此,在设计 Saga 时,必须将幂等性作为补偿事务的第一优先级要求。