Saga 设计模式通过跨多个服务协调事务来帮助维护分布式系统中的数据一致性。 Saga 是一系列本地事务,其中每个服务执行其操作,并通过事件或消息启动下一步。 如果序列中的步骤失败,则 saga 执行补偿事务以撤消已完成的步骤,从而保持数据一致性。
上下文和问题
事务 表示可以包含多个操作的工作单元。 在事务中,事件 是指影响实体的状态更改。 命令 封装执行操作或触发后续事件所需的所有信息。
事务必须遵循原子性、一致性、隔离性和持久性(ACID)的原则。
- 原子性:所有操作都成功或无。
- 一致性:数据从一个有效状态转换到另一个状态。
- 隔离:并发事务产生与顺序事务相同的结果。
- 持久性:提交后,即使在失败中也会保留更改。
在单个服务中,事务遵循 ACID 原则,因为它们在单个数据库中运行。 但是,跨多个服务实现 ACID 符合性更为复杂。
微服务体系结构中的挑战
微服务体系结构通常将 专用数据库分配给每个微服务,这具有以下几个优势:
- 每个服务封装其自己的数据。
- 每个服务都可以根据其特定需求使用最合适的数据库技术和架构。
- 每个服务的数据库独立缩放。
- 一个服务中的故障与其他服务隔离。
尽管有这些优势,但此体系结构使跨服务数据一致性复杂化。 传统的数据库保证(如 ACID)不适用于多个独立托管数据存储。 由于这些限制,依赖于进程间通信(IPC)或传统事务模型的体系结构(如双阶段提交(2PC)协议通常更适合 Saga 模式。
溶液
Saga 模式通过将事务分解为一系列 本地事务来管理事务(见图 1)。
显示传奇概述的
图 1. 包含三个服务的传奇故事。
每个本地事务:
- 在单个服务中以原子方式完成其工作。
- 更新服务的数据库。
- 通过事件或消息启动下一个事务。
- 如果本地事务失败,则 saga 将执行一系列 补偿事务, 来扭转上述本地事务所做的更改。
Saga 模式中的关键概念
补偿事务:其他事务可以撤消或补偿相反效果的事务。 如果传奇中的步骤失败,补偿事务将撤消可补偿事务所做的更改。
透视事务:透视事务在传奇中充当“无返回点”。 透视事务成功后,可补偿事务(可以撤消)不再相关。 系统必须完成所有后续操作才能实现一致的最终状态。 透视事务可以属于不同的角色,具体取决于传奇流程:
不可逆(不可编译):无法撤消或重试。
可逆和提交的之间的边界:它可以是最后一个可撤消的(可补偿)事务,也可以是传奇中的第一个可重试操作。
可重试事务:这些事务遵循透视事务。 可重试的事务是幂等的,并确保 saga 可以达到其最终状态,即使发生临时故障也是如此。 它保证传奇最终实现一致状态。
Saga 实现方法
有两种常见的传奇实现方法:编舞 和 业务流程。 每个方法都有自己的一组挑战和技术来协调工作流。
编舞
在编舞中,服务在没有集中式控制器的情况下交换事件。 通过编舞,每个本地事务都会发布域事件,以在其他服务中触发本地事务(见图 2)。
图 2. 使用编舞的传奇。
编舞 的 |
编舞 的 |
---|---|
适用于具有少量服务的简单工作流,不需要协调逻辑。 | 添加新步骤时,工作流可能会变得混乱。 很难跟踪哪些传奇参与者侦听哪些命令。 |
协调不需要其他服务。 | 传奇参与者之间存在循环依赖关系的风险,因为它们必须相互使用命令。 |
不会引入单一故障点,因为职责分布在传奇参与者中。 | 集成测试很困难,因为所有服务都必须运行才能模拟事务。 |
配器
在业务流程中,集中式控制器(业务流程协调程序)处理所有事务,并告知参与者根据事件执行哪些操作。 业务流程协调程序执行传奇请求、存储和解释每个任务的状态,并使用补偿事务处理故障恢复(见图 3)。
图 3. 使用业务流程的传奇。
业务流程 的 |
业务流程 的 |
---|---|
更适合复杂的工作流或在添加新服务时使用。 | 其他设计复杂性需要协调逻辑的实现。 |
避免循环依赖项,因为业务流程协调程序管理流。 | 引入故障点,因为业务流程协调程序管理完整的工作流。 |
明确职责分离简化了服务逻辑。 |
问题和注意事项
实现 Saga 模式时,请考虑以下几点:
设计思维转变:采用 Saga 模式需要不同的思维模式,专注于协调事务并确保跨多个微服务的数据一致性。
调试传奇的复杂性:调试传奇可能比较复杂,尤其是在参与服务数量增加时。
不可逆的本地数据库更改:无法回滚数据,因为传奇参与者将更改提交到各自的数据库。
处理暂时性故障和幂等:系统必须有效处理暂时性故障,并确保重复相同操作不会改变结果。 有关详细信息,请参阅 幂等消息处理。
需要监视和跟踪传奇:监视和跟踪传奇故事的工作流对于维护运营监督至关重要。
补偿事务的限制:补偿事务可能并不总是成功,可能会使系统处于不一致状态。
传奇中潜在的数据异常
数据异常是跨多个服务执行传奇时可能发生的不一致。 由于每个服务都管理自己的数据(参与者数据),因此服务之间没有内置的隔离。 此设置可能会导致数据不一致或持续性问题,例如部分应用的更新或服务之间的冲突。 常见问题包括:
丢失的更新:当一个传奇修改数据而不考虑另一个传奇做出的更改时,会导致覆盖或丢失更新。
Dirty 读取:当传奇或事务读取另一个传奇修改但尚未完成的数据时。
模糊(不可重复)读取:当 saga 中的不同步骤读取不一致数据时,因为读取之间发生更新。
解决数据异常的策略
若要减少或防止这些异常,请考虑以下对策:
语义锁:使用应用程序级锁,其中 saga 的可补偿事务使用信号灯指示更新正在进行。
通勤更新:设计更新,使其可以按任意顺序应用,同时仍产生相同的结果,从而减少传奇之间的冲突。
悲观视图:对传奇的序列重新排序,以便数据更新在可重试的事务中发生,以消除脏读取。 否则,一个传奇可以读取脏数据(未提交的更改),而另一个传奇同时执行可补偿事务来回滚其更新。
重新读取值:在进行更新之前验证数据是否保持不变。 如果数据发生更改,请中止当前步骤并根据需要重启传奇。
版本文件:维护记录上所有操作的日志,并确保它们按正确的顺序执行,以防止冲突。
基于风险的并发(按值):根据潜在的业务风险动态选择适当的并发机制。 例如,对高风险更新使用 sagas,将分布式事务用于高风险更新。
何时使用此模式
需要以下情况时使用 Saga 模式:
- 确保分布式系统中的数据一致性,而无需紧密耦合。
- 如果序列中的某个操作失败,则回滚或补偿。
Saga 模式不太适合:
- 紧密耦合的事务。
- 补偿在早期参与者中发生的事务。
- 循环依赖项。
后续步骤
- 分布式数据
- 理查森,克里斯 2018:微服务模式。 曼宁出版物。
相关资源
实现此模式时,以下模式可能也很有用:
- 编舞 让系统的每个组件都参与有关业务事务工作流的决策过程,而不是依靠中心控制点。
- 补偿事务 一系列步骤执行的撤消工作,并在一个或多个步骤失败时最终定义一致的操作。 实现复杂业务流程和工作流的云托管应用程序通常遵循此 最终一致性模型。
- 重试 允许应用程序在尝试连接到服务或网络资源时处理暂时性故障,方法是透明地重试失败的操作。 重试可以提高应用程序的稳定性。
- 断路器 处理在连接到远程服务或资源时需要花费可变时间从中恢复的故障。 断路器可以提高应用程序的稳定性和复原能力。
- 运行状况终结点监视 在应用程序中实现功能检查,外部工具可以定期通过公开的终结点进行访问。 运行状况终结点监视可以帮助验证应用程序和服务是否正常运行。