补偿模式

补偿事务可能无法简单地用系统在操作开始时的状态替换当前状态,因为这种方法可能覆盖应用程序的其他并发实例所做的更改。

相反,它必须是一个考虑到并发实例所做的任何工作的智能流程。

这个过程通常是特定于应用程序的,由原始操作执行的工作的性质驱动。

概念

撤销一系列步骤执行的工作,如果一个或多个步骤失败,这些步骤一起定义一个最终一致的操作。

遵循最终一致性模型的操作通常出现在实现复杂业务流程和工作流的云托管应用程序中。

问题与情景

在云中运行的应用程序经常修改数据。

这些数据可能分布在不同地理位置的不同数据源上。为了避免争用和提高分布式环境中的性能,应用程序不应该试图提供强大的事务一致性。

相反,应用程序应该实现最终的一致性。在这个模型中,典型的业务操作由一系列独立的步骤组成。

虽然正在执行这些步骤,但是系统状态的总体视图可能不一致,但是当操作完成并且所有步骤都执行之后,系统应该再次变得一致。

最终一致性模型中的一个挑战是如何处理失败的步骤。在这种情况下,可能需要撤消由操作中的前几个步骤完成的所有工作。但是,不能简单地回滚数据,因为应用程序的其他并发实例可能已经更改了它。即使在数据没有被并发实例更改的情况下,取消一个步骤也可能不仅仅是恢复原始状态。可能需要应用各种特定于业务的规则。

如果一个实现最终一致性的操作跨越了多个异构数据存储,取消操作中的步骤将需要依次访问每个数据存储。必须可靠地撤消在每个数据存储中执行的工作,以防止系统保持不一致。

不是所有受实现最终一致性的操作影响的数据都可以保存在数据库中。在面向服务的体系结构(service oriented architecture, SOA)环境中,操作可以调用服务中的某个操作,并导致该服务的状态发生更改。要撤消操作,还必须撤消状态更改。这可能涉及再次调用服务,并执行另一个操作,以逆转第一个操作的影响。

解决方案

解决方案是实现一个补偿事务。补偿事务中的步骤必须撤销原始操作中的步骤的影响。

补偿事务可能无法简单地用系统在操作开始时的状态替换当前状态,因为这种方法可能覆盖应用程序的其他并发实例所做的更改。

相反,它必须是一个考虑到并发实例所做的任何工作的智能流程。这个过程通常是特定于应用程序的,由原始操作执行的工作的性质驱动。

一种常见的方法是使用工作流来实现最终一致的需要补偿的操作。

随着原始操作的进行,系统记录关于每个步骤的信息,以及该步骤执行的工作是如何被撤消的。

如果操作在任何时候失败,工作流就会回到它已经完成的步骤,并执行反转每个步骤的工作。请注意,补偿事务可能不必以与原始操作完全相反的顺序撤消工作,并且可以并行执行一些撤消步骤。

Saga

补偿事务也是最终一致的操作,它也可能失败。系统应该能够在故障点恢复补偿事务并继续执行。可能需要重复失败的步骤,因此补偿事务中的步骤应该定义为幂等命令。

有关更多信息,请参见乔纳森·奥利弗的博客上的幂等模式

在某些情况下,可能无法从失败的步骤中恢复,除非通过人工干预。在这种情况下,系统应该发出警报并提供尽可能多的关于故障原因的信息。

Issues and considerations

在决定如何实现此模式时,请考虑以下几点:

要确定实现最终一致性的操作中的某个步骤何时失败并不容易。一个步骤可能不会立即失败,但是可以阻止。可能需要实现某种形式的超时机制。

-补偿逻辑不容易概括。补偿事务是特定于应用程序的。它依赖于应用程序拥有足够的信息来撤销失败操作中的每个步骤的影响。

您应该将补偿事务中的步骤定义为幂等命令。这使得在补偿事务本身失败时可以重复这些步骤。

处理原始操作中的步骤和补偿事务的基础结构必须具有弹性。它不能丢失补偿失败步骤所需的信息,而且它必须能够可靠地监视补偿逻辑的进展。

补偿事务不一定将系统中的数据返回到原始操作开始时的状态。相反,它补偿了在操作失败之前成功完成的步骤所执行的工作。

补偿事务中的步骤顺序不一定与原始操作中的步骤完全相反。例如,一个数据存储可能比另一个对不一致更敏感,因此补偿事务中撤消对该存储的更改的步骤应该首先发生。

在完成操作所需的每个资源上设置一个基于超时的短期锁,并提前获取这些资源,可以帮助增加整个活动成功的可能性。只有在获得了所有的资源之后,才能进行这项工作。所有操作都必须在锁过期之前完成。

考虑使用比通常更宽容的重试逻辑来最小化触发补偿事务的失败。如果实现最终一致性的操作中的某个步骤失败,请尝试将失败作为临时异常处理,并重复该步骤。只有在步骤重复失败或不可恢复时才停止操作并启动补偿事务。

实现补偿事务的许多挑战与实现最终一致性的挑战相同。

有关更多信息,请参见数据一致性入门中实现最终一致性的考虑部分。

核心思想

补偿交易,其核心思想是:针对每个操作,都要注册一个与其对应的补偿操作。一般来说操作本身和其补偿(撤销)操作会在一个事务里完成。

当其后续操作失败后,需要按相反顺序完成前面注册的所有撤销操作。

优缺点

跟2PC比,他的核心价值应该是少了锁资源的代价。流程也相对简单一点。

但实际操作中,补偿操作不太好定义,其中间状态处理也会比较棘手。

实例

比如A:-100(补偿为A:+100),
B:+100。
那么如果B:+100失败后就需要执行A:+100。

参考资料

https://docs.microsoft.com/en-us/azure/architecture/patterns/compensating-transaction