3. 补偿事务原理:正向事务与补偿事务的配对关系、补偿操作的幂等性要求
Saga 事务模型的核心思想是将一个长事务拆分为多个本地事务(正向事务),并为每个正向事务定义一个补偿事务。当某个正向事务执行失败时,Saga 协调器会反向调用已成功正向事务对应的补偿事务,以撤销其影响。理解正向与补偿的配对关系以及补偿操作的幂等性,是设计可靠 Saga 的关键。
3.1 正向事务与补偿事务的配对关系
每个正向事务(T)必须有一个对应的补偿事务(C),二者形成一对一的配对关系。补偿事务的逻辑是正向事务的逆向操作,但并非简单的“反向执行”,而是业务语义上的撤销。
| 正向事务(T) | 补偿事务(C) | 配对说明 |
|---|---|---|
| 扣减库存 | 回滚库存(增加库存) | 补偿操作必须能恢复库存数量到扣减前的状态 |
| 创建订单(状态:待支付) | 取消订单(状态:已取消) | 补偿操作将订单状态标记为取消,而非物理删除 |
| 扣减用户余额 | 增加用户余额 | 补偿操作必须增加相同金额,且考虑并发扣减场景 |
| 发送消息通知 | 发送撤销通知或标记消息为无效 | 补偿操作可能无法物理撤回已发送的消息,需业务兜底 |
关键原则:补偿事务必须从业务上“撤销”正向事务的效果,而不是简单地执行数据库回滚。例如,扣减库存的补偿是增加库存,而不是执行
ROLLBACK。
下图展示了 Saga 执行过程中正向事务与补偿事务的配对调用关系:
3.2 补偿操作的幂等性要求
在分布式系统中,网络超时、节点故障、协调器重试等因素可能导致补偿事务被多次执行。如果补偿操作不具备幂等性,重复执行将引发数据不一致或业务异常。
幂等性定义:一个操作无论执行一次还是多次,其产生的业务结果相同。例如,“将订单状态设置为已取消”是幂等的,而“将余额增加 100 元”不是幂等的(重复执行会导致余额多增)。
3.2.1 幂等性失效的典型场景
- 协调器重试:Saga 协调器在未收到补偿事务的确认响应时,会进行重试,导致补偿操作被多次调用。
- 网络延迟与重复请求:客户端发送的补偿请求在网络中滞留,被服务端重复处理。
- 数据库连接池超时:补偿事务因连接池等待超时而失败,重试时再次执行相同的补偿逻辑。
3.2.2 实现幂等性的常见策略
| 策略 | 实现方式 | 适用场景 | 注意事项 |
|---|---|---|---|
| 唯一请求 ID(幂等键) | 为每个补偿请求生成全局唯一 ID,服务端记录已处理的 ID,重复请求直接返回成功 | 所有补偿操作 | 需要持久化存储幂等键,注意清理过期记录 |
| 状态机校验 | 补偿操作只允许在特定状态(如“待补偿”)下执行,执行后状态变更为“已补偿” | 订单状态、流程状态 | 状态变更需原子性,防止并发覆盖 |
| 业务逻辑幂等 | 补偿操作本身设计为幂等,例如“设置库存为固定值”而非“增加库存” | 库存、余额等数值型字段 | 需要业务支持绝对值的设置,而非相对增减 |
| 乐观锁/版本号 | 补偿操作携带版本号,更新时校验版本号是否匹配 | 高并发更新场景 | 版本号冲突时需重试或报错 |
最佳实践:推荐组合使用“唯一请求 ID + 状态机校验”。唯一请求 ID 防止重复请求,状态机校验确保业务状态正确,二者互补可覆盖绝大多数幂等性场景。
下图展示了基于唯一请求 ID 的幂等补偿流程:
3.3 补偿事务设计原则总结
- 一对一配对:每个正向事务必须有且仅有一个对应的补偿事务,补偿逻辑必须能完全撤销正向事务的业务影响。
- 反向顺序执行:补偿事务必须按照正向事务的相反顺序执行(后进先出),避免依赖关系错误。
- 幂等性保障:所有补偿操作必须实现幂等,防止重试导致的数据不一致。
- 业务语义补偿:补偿操作基于业务逻辑,而非数据库回滚。例如,扣减库存的补偿是增加库存,而不是执行
ROLLBACK。 - 可观测性:补偿事务的执行状态、重试次数、幂等 ID 等信息应记录日志,便于故障排查。
案例提示:在“慢查询与连接池水位”的故障场景中,如果补偿事务因连接池等待超时而失败,协调器会重试补偿操作。此时,如果补偿操作不具备幂等性,重复执行将导致库存多回滚、余额多增加等严重问题。因此,在设计 Saga 时,必须将幂等性作为补偿事务的第一优先级要求。