更新时间:2020-08-27 14:44
基于 Saga 模式分布式事务的多年实践,本文提供了在 Saga 模式下服务设计的一些最佳实践与经验。
Saga 模式是 SEATA 提供的长事务解决方案。Saga 模式下,分布式事务内存在多个参与者,每一个参与者都是一个冲正补偿服务,用户需要根据业务场景实现其正向操作(原服务)和逆向回滚操作(补偿服务)。在事务执行过程中,首先会依次执行各参与者的正向操作,如所有正向操作执行成功,则事务提交。如任一正向操作执行失败,则事务会执行之前各参与者的逆向回滚操作,回滚已提交的参与者,直至事务退回至其初始状态。
空补偿,指的是原服务未执行,补偿服务已执行。大致场景如下:
针对该问题,在服务设计时,需要允许空补偿,即在没有找到要补偿的业务主键时,返回补偿成功,并将原业务主键记录下来,标记该业务流水已补偿成功。
悬挂,指的是补偿服务比原服务先执行。大致场景如下:
此时,需要检查当前业务主键是否已经在空补偿记录下来的业务主键中存在,如果存在则要拒绝执行该笔服务,以免造成数据不一致。
在分布式事务执行过程中,原服务与补偿服务都需要保证幂等性。由于网络连接可能超时,可以设置重试策略,重试发生时要通过幂等控制避免业务数据重复更新。
Saga 发起方作为服务方,需要根据状态机的执行状态,返回服务状态给调用方。可以参考以下代码示例:
StateMachineInstance inst = stateMachineEngine.startWithBusinessKey("testTransferBySaga", null, businessKey, params);
if (ExecutionStatus.SU.equals(inst.getStatus())
&& inst.getCompensationStatus() == null) {
//正向状态为成功, 补偿状态为空(没有触发回滚),则交易成功,返回 SU
}
else if (ExecutionStatus.SU.equals(inst.getCompensationStatus())) {
//补偿状态为成功,则交易回滚成功,返回失败 FA
//如果有多级调用,可以给调用方返回失败 FA,这样上层编排则不再进行补偿该服务
}
else if (ExecutionStatus.FA.equals(inst.getStatus())
&& inst.getCompensationStatus() == null) {
//正向状态为失败, 补偿状态为空(没为触发回滚),则交易失败且没有数据不一致,返回FA
}
else {
//其它情况说明是正向或补偿结果未知,返回未知UN给调用方,需要进行重试
}
Saga 模式由于一阶段已经提交本地数据库事务,且没有进行预留动作,因此无法保证隔离性。在极端情况下,可能由于脏写无法完成回滚操作。例如,在分布式事务中,需要先给用户 A 充值,然后给用户 B 扣减余额。如果在给用户 A 充值成功,在事务提交以前,用户 A 已经把余额消费掉了,如果事务发生回滚,此时则没有办法进行补偿了。
以上是一个极端场景下隔离性缺乏造成的典型问题。在实践中,一般可以采取以下方法应对该问题:
在文档使用中是否遇到以下问题
更多建议
匿名提交