Saga 分布式事务模式
Saga 设计模式是一种在分布式事务场景中跨微服务管理数据一致性的方法。 Saga 是一系列事务,用于更新每项服务并发布消息或事件来触发下一个事务步骤。 如果某个步骤失败,则 Saga 将执行补偿事务,以抵消上一个事务的影响。
上下文和问题
事务是单个逻辑或工作单元,有时由多个操作组成。 在事务中,事件是实体发生的状态更改,命令封装执行操作或触发后续事件所需的所有信息。 事务必须为原子级的、一致的、孤立的和持久的 (ACID)。 单个服务中的事务是 ACID 的,但跨服务数据一致性需要跨服务事务管理策略。 在多服务体系结构中:
- 原子性是一组不可分割且无法简化的操作,必须全部发生或不发生。
- 一致性意味着事务仅将数据从一个有效状态引入另一个有效状态。
- 孤立性确保并发事务生成的数据状态与按顺序执行的事务生成的数据状态相同。
- 持续性确保即使在发生系统故障或停电的情况下,已提交的事务仍保持提交状态。
每项微服务一个数据库模型为微服务体系结构提供了许多好处。 通过封装域数据,每项服务都可以使用其最佳数据存储类型和架构,根据需要缩放其自己的数据存储,并使其免受其他服务故障的影响。 但是,确保特定服务的数据库之间的数据一致性带来了挑战。 分布式事务(如两阶段提交 (2PC) 协议)要求事务中的所有参与者在事务继续之前提交或回滚。 但是,某些参与者实现(如 NoSQL 数据库和消息中转站)不支持此模型。 另一个分布式事务限制是进程间通信 (IPC) 同步性和可用性。 操作系统提供的 IPC 允许单独的进程共享数据。 若要提交分布式事务,所有参与的服务都必须可用,这可能会降低整个系统的可用性。 具有 IPC 或事务限制的体系结构实现适合 Saga 模式。
解决方案
Saga 模式使用一系列本地事务来提供事务管理。 本地事务是由 Saga 参与者执行的原子工作。 每个本地事务都会更新数据库,并发布消息或事件来触发 Saga 中的下一个本地事务。 如果本地事务失败,则 Saga 将执行一系列补偿事务,以撤消上一个本地事务所做的更改。 在 Saga 模式中:
- 可补偿事务是可以通过处理另一个具有相反效果的事务来撤销的事务。
- 透视事务是 Saga 中的 go/no-go 点。 如果透视事务提交,则 Saga 将运行到完成为止。 透视事务可以是既不可补偿也不可重试的事务,也可以是 Saga 中的最后一个可补偿事务或第一个可重试事务。
- 可重试事务是透视事务之后且保证成功的事务。
有两种常见的 Saga 实现方法,即协调和编排。 每个方法都有自己的一组挑战和技术来协调工作流。
协调
协调是协调 Saga 的一种方法,参与者在没有集中控制点的情况下交换事件。 通过协调,每个本地事务都会发布在其他服务中触发本地事务的域事件。
好处
- 适用于只需很少参与者且不需要协调逻辑的简单工作流。
- 不需要额外的服务实现和维护。
- 不会引入单一故障点,因为责任在各个 Saga 参与者之间进行分配。
缺点
- 添加新步骤时,工作流可能会变得混乱,因为很难跟踪哪些 Saga 参与者侦听哪些命令。
- 由于 Saga 参与者必须使用彼此的命令,因此他们之间存在循环依赖的风险。
- 集成测试很困难,因为必须运行所有服务才能模拟事务。
资源协调
编排是协调 Saga 的一种方法,在此方法中,中央控制器告诉 Saga 参与者要执行的本地事务。 Saga 编排器处理所有事务,并告知参与者根据事件执行哪项操作。 编排器执行 Saga 请求、存储和解释每个任务的状态,并通过补偿事务处理故障恢复。
好处
- 适用于涉及许多参与者或随时间推移增加了新参与者的复杂工作流。
- 适用于可控制流程中的每个参与者和活动流的情况。
- 不会引入循环依赖项,因为编排器单方面依赖于 Saga 参与者。
- Saga 参与者无需了解其他参与者的命令。 明确的关注点分离可简化业务逻辑。
缺点
- 其他设计复杂性需要协调逻辑的实现。
- 还有一个额外的故障点,因为编排器管理完整的工作流。
问题和注意事项
在实现 Saga 模式时,请考虑以下几点:
- Saga 模式最初可能具有挑战性,因为它需要一种新的思维方式来思考如何协调事务和维护跨多项微服务的业务流程的数据一致性。
- Saga 模式的调试尤其困难,随着参与者的增加,其复杂性也会增加。
- 无法回滚数据,因为 Saga 参与者将更改提交到其本地数据库。
- 实现必须能够处理一组潜在的暂时性故障,并提供幂等性来减少副作用并确保数据一致性。 幂等性意味着相同的操作可以重复多次,而不会改变初始结果。 有关详细信息,请参阅有关在处理消息和更新状态时确保幂等性的指南。
- 最好实现可观测性来监视和跟踪 Saga 工作流。
- 缺乏参与者数据隔离会带来持续性挑战。 Saga 的实现必须包括减少异常的对策。
如果没有适当的度量值,可能会发生以下异常:
- 更新丢失:一个 Saga 未读取另一个 Saga 所做的更改,而是直接写入。
- 脏读:一个事务或 Saga 读取某个 Saga 所做的但尚未完成的更新。
- 模糊/不可重复读:不同的 Saga 步骤读取不同的数据,因为数据更新发生在读取之间。
用于减少或防止异常的建议对策包括:
- 语义锁定:一种应用程序级锁定,其中 Saga 的可补偿事务使用信号灯来指示更新正在进行中。
- 交换式更新:可按任何顺序执行并产生相同的结果。
- 悲观视图:可以在一个 Saga 读取脏数据的同时,另一个 Saga 运行可补偿事务来回滚操作。 悲观视图对 Saga 重新排序,以便基础数据在可重试事务中更新,从而避免发生脏读。
- 重新读取值:验证数据是否不变,然后更新记录。 如果记录已更改,则步骤将中止,并且 Saga 可能会重启。
- 版本文件:在操作到达时将它们记在一条记录中,然后按正确的顺序执行这些操作。
- 基于价值:使用每个请求的业务风险来动态选择并发机制。 低风险请求偏向于 Sagas,而高风险请求偏向于分布式事务。
何时使用此模式
在以下情况下,请使用 Saga 模式:
- 需确保分布式系统中的数据一致性,而无需紧密耦合。
- 需要在序列中的某个操作失败时进行回滚或补偿。
Saga 模式不太适合用于:
- 紧密耦合事务。
- 补偿早期参与者中发生的事务。
- 循环依赖项。
示例
无服务器上的编排式 Saga 是一种使用编排方法的 Saga 实现参考,用于模拟具有成功和失败工作流的转账方案。
后续步骤
- 分布式数据
- Richardson, Chris. 2018: Microservices Patterns. Manning Publications.
相关资源
实现此模式时,以下模式也可能有用:
- 协调让每个系统组件都参与有关业务事务工作流的决策过程,而不是只依赖于控制中心点。
- 补偿事务撤消由一系列步骤执行的工作,并在一个或多个步骤失败时最终定义一致的操作。 实现复杂业务流程和工作流的云托管应用程序通常遵循此最终一致性模型。
- 重试在应用程序尝试连接到服务或网络资源时,使应用程序能够通过以透明方式重试失败的操作来处理暂时性故障。 重试可提高应用程序的稳定性。
- 断路器在连接到远程服务或资源时处理故障,此类故障所需恢复时间不定。 断路器可提高应用程序的稳定性和复原能力。
- 运行状况终结点监视在应用程序中实施可让外部工具通过公开终结点定期访问的功能检查。 运行状况终结点监视有助于验证应用程序和服务的运行是否正常。