补偿事务模式
使用由一系列步骤组成的最终一致性操作时,补偿事务模式可能很有用。 具体而言,如果一个或多个步骤失败,可使用补偿事务模式撤消步骤执行的工作。 遵循最终一致性模型的操作常见于实现复杂业务流程和工作流的云托管应用程序中。
上下文和问题
云中运行的应用程序经常修改数据。 该数据有时分布在不同的地理位置的各种数据源中。 为避免争用和提高分布式环境中的性能,应用程序不应提供事务强一致性。 相反,应用程序应实现最终一致性。 在最终一致性模型中,典型的业务操作包含一系列单独的步骤。 当操作执行这些步骤时,系统状态的总体视图可能不一致。 但当操作完成且所有步骤都已运行后,系统应会再次变得一致。 数据一致性入门提供了有关分布式事务缩放性不佳的原因的信息。 此资源还列出了最终一致性模型的原则。 最终一致性模型中的一个难题是如何处理失败步骤。 失败后,可能需要撤消该操作中先前步骤已完成的所有工作。 然而,不能总是回滚数据,因为应用程序的其他并发实例可能已更改了数据。 即使在并发实例未更改数据的情况下,撤消步骤也可能比还原原始状态更为复杂。 可能需要应用多种业务特定规则。 有关示例,请参阅本文后面的示例部分所述的旅游网站。 如果实现最终一致性的操作跨多个异类数据存储,则撤销操作中的步骤需要依次访问每个数据存储。 要防止系统保持不一致,必须以可靠方式撤销每个数据存储中执行的工作。 受实现最终一致性的操作影响的数据并不总是保留在数据库中。 例如,考虑面向服务的体系结构 (SOA) 环境。 SOA 操作可调用服务中的操作,并导致该服务的状态发生更改。 要撤消操作,还必须撤消此状态更改。 此过程可能涉及到再次调用服务和再执行一个撤销第一次操作效果的操作。
解决方案
解决方案是实现补偿事务。 补偿事务中的步骤可撤销原始操作中步骤的效果。 一种直观的方法是将当前状态替换为系统在该操作开始时所处的状态。 但补偿事务不能总是采用这种方法,因为该方法可能会覆盖应用程序的其他并发实例所做的更改。 相反,补偿事务必须是一个考虑并发实例执行的所有工作的智能过程。 此过程通常特定于应用程序,由原始操作执行的工作的性质驱动。 常见方法是使用工作流来实现需要补偿的最终一致操作。 原始操作进行期间,系统会记录每个步骤的相关信息,包括如何撤销相应步骤执行的工作。 如果操作在任何时刻失败,则工作流会回退已完成的步骤。 在每个步骤中,工作流会撤销该步骤所执行的工作。 两个要点是:
- 补偿事务可能并不一定要按照与原始操作完全相反的顺序撤消工作。
- 可能会并行执行某些撤销步骤。
这种方法类似于 Clemens Vasters 博客中所述的 Sagas 策略。 补偿事务本身是一个最终一致性操作,因此也可能会失败。 系统应能够在失败时恢复补偿事务,然后继续。 该事务可能需要重复执行失败的步骤,因此补偿事务中的步骤应定义为幂等命令。 有关详细信息,请参阅 Jonathan Oliver 博客中的 Idempotency Patterns(幂等模式)。 在某些情况下,手动干预可能是从失败的步骤中恢复的唯一方法。 这类情况下,系统应会发出警报,并会提供尽可能多的有关失败原因的信息。
问题和注意事项
在决定如何实现此模式时,请考虑以下几点:
- 确定实现最终一致性的操作中的步骤何时失败并非易事。 步骤可能不会立即失败。 但是,可能会受到阻止。 你可能需要实现超时机制。
- 通用化补偿逻辑并非易事。 补偿事务特定于应用程序。 它依赖于具有足够信息的应用程序,从而能够撤消失败的操作中每个步骤的效果。
- 应将补偿事务中的步骤定义为幂等命令。 如果这样做,补偿事务本身失败时可重复这些步骤。
- 处理这些步骤的基础结构必须满足以下条件:
- 它在原始操作和补偿事务中具有复原能力。
- 它不会丢失补偿失败步骤所需的信息。
- 它以可靠方式监视补偿逻辑的进度。
- 补偿事务不一定会将系统数据返回到原始操作开始时的状态。 相反,事务会补偿操作在失败之前成功完成的工作。
- 补偿事务中步骤的顺序不一定与原始操作中步骤的顺序完全相反。 例如,一个数据存储可能比另一个数据存储对不一致性更加敏感。 补偿事务中撤销对此存储的更改的步骤应该会首先发生。
- 某些度量值有助于增加整个活动成功的可能性。 具体而言,可对完成操作所需的每个资源采用一个基于超时的短期锁。 还可以提前获取这些资源。 然后,仅在获取所有资源后执行工作。 在锁过期之前完成所有操作。
- 包容性更强的重试逻辑有助于避免会触发补偿事务的失败。 如果实现最终一致性的操作中的某个步骤失败,请尝试将该失败作为暂时异常处理,然后重复该步骤。 仅在步骤反复失败或不可恢复时才停止操作并启动补偿事务。
- 实现补偿事务时,将面临许多与实现最终一致性时相同的难题。 有关详细信息,请参阅数据一致性入门中的“实现最终一致性注意事项”部分。
何时使用此模式
仅对失败时必须撤销的操作使用此模式。 如果可能,请设计相关解决方案来避免需要补偿事务所带来的麻烦。
示例
客户使用旅游网站预订行程。 单个行程可能包含一系列航班和酒店。 一位先从西雅图到伦敦再到巴黎的客户在创建行程时,可能会执行以下步骤:
- 订购从西雅图到伦敦的 F1 航班机票。
- 订购从伦敦到巴黎的 F2 航班机票。
- 订购从巴黎到西雅图的 F3 航班机票。
- 预定伦敦 H1 酒店房间。
- 预定巴黎 H2 酒店房间。
虽然每个步骤为单独操作,但这些步骤会构成一个最终一致操作。 除执行这些步骤外,系统还必须记录撤消每个步骤的对立操作。 客户取消行程时需要此信息。 之后,执行对立操作所需的步骤可作为补偿事务运行。 补偿事务中的步骤可能与原始步骤完全相反。 此外,补偿事务中每个步骤中的逻辑都必须考虑特定于业务的规则。 例如,如果取消航班预订,客户可能无法获得全额退款。 下图显示了预订旅行行程的长时间运行事务中的步骤。 还可查看撤消事务的补偿事务步骤。 备注 可以并行执行补偿事务中的步骤,具体取决于每个步骤的补偿逻辑的设计方式。 在许多业务解决方案中,单个步骤失败并不始终要求通过使用补偿事务回滚系统。 例如,考虑旅游网站场景。 假设客户预订 F1、F2 和 F3 航班,但无法预订 H1 酒店的房间。 建议为客户提供该市另一家酒店的房间,而不是取消航班。 客户仍然可以决定取消。 在这种情况下,补偿事务将运行并撤消对 F1、F2 和 F3 航班的预订。 但做出这一决定的应该是客户,而不是系统。
后续步骤
- Data Consistency Primer(数据一致性入门)。 补偿事务模式通常用于撤消实现最终一致性模型的操作。 该入门指导提供了有关最终一致性优点和不足的信息。
- 幂等模式。 在补偿事务中,建议使用幂等命令。 此博客文章介绍了实现幂等性时要考虑的因素。
相关资源
- 计划程序代理监督程序模式。 本文介绍如何实现可复原的系统,这些系统执行使用分布式服务和资源的业务操作。 在这些系统中,有时需要使用补偿事务来撤消操作执行的工作。
- 重试模式。 补偿事务可能需要大量的计算。 可使用重试模式来实现重试失败操作的有效策略,尽可能少地使用补偿事务。
- Saga 分布式事务模式。 本文介绍如何在分布式事务方案中使用 Saga 模式管理微服务之间的数据一致性。 Saga 模式通过补偿事务处理故障恢复。
- 管道和筛选器模式。 本文介绍管道和筛选器模式,可使用该模式将复杂的处理任务分解为一系列可重用元素。 可将管道和筛选器模式与补偿事务模式结合使用,作为实现分布式事务的另一种方法。
- 自愈设计。 本指南介绍如何设计自愈应用程序。 可使用补偿事务作为自愈方法的一部分。