微服务架构设计模式笔记--第四章 使用Saga管理事务
作者:互联网
微服务架构设计模式笔记--第四章 使用Saga管理事务
1. 微服务架构下的事务管理
ACID(原子性、一致性、隔离性和持久性)事务造成了一种错觉,让开发人员认为每个事务都具有对数据的独占访问权,这种错觉极大地简化了开发人员的工作。在微服务架构中,单个服务中的事务仍然可以使用ACID事务。然而,在对更新多个服务所拥有的数据的操作实现事务时,我们面临着新的挑战。
在只访问一个数据库的单体应用中,事务管理是简单明了的。一些较为复杂的单体应用可能会使用多个数据库和消息代理。更进一步,微服务架构下的事务往往需要横跨多个服务,每个服务都有属于自己的私有数据库。在这种情况下,应用程序必须使用些更为高级的事务管理机制来管理事务。正如你已经了解的,传统的分布式事务管理并不是现代应用程序的合适选择。取而代之的是,微服务应用程序需要使用Saga机制。
1.1 微服务架构对分布式事务的需求
因为每个服务都有自己的私有数据库,你需要一种机制来保障多数据库环境下的数据一致性。
1.2. 传统解决办法存在的问题
在多个服务、数据库和消息代理之间维持数据一致性的传统方式是采用分布式事务XA,采用了两阶段提交(2PC)来保证事务中的所有参与方同时完成提交,或者在失败时同时回滚。
存在的问题:
- 应用程序的整个技术栈需要同时满足XA标准
绝大多数的SQL数据库和一部分消息代理满足XA标准。许多新技术,包括NOSQL数据库,并不支持XA标准的分布式事务。一些流行的消息代理如 RabbitMQ和 Apache Kafka也不支持分布式事务。 - 它们本质上都是同步进程间通信,这会降低分布式系统的可用性
为了让一个分布式事务完成提交,所有参与事务的服务都必须可用。系统整体的可用性是所有事务参与方可用性的乘积。如果一个分布式事务包括两个可用性为99.5%的参与方,那么总的系统可用性可能就只有99%。 - 在XA性能不足
系统可用性不高,在高并发场景,经常需要进行同步锁操作。
1.3. 使用Saga模式维护数据一致性
Saga是一种在微服务架构中维护数据一致性的机制,它可以避免分布式事务所带来的问题。一个Saga表示需要更新多个服务中数据的一个系统操作。Saga由一连串的本地事务组成。每一个本地事务负责更新它所在服务的私有数据库,这些操作仍旧依赖于我们所熟悉的ACID事务框架和函数库。
示例Saga: Create Order Saga
本章中使用的示例Saga是 Create Order saga,如图所示。Order service使用此Saga实现 createOrder()操作。Saga的第一个本地事务由创建订单的外部请求启动。其他5个本地事务均由前一个完成触发。
这个Saga包含了以下几个本地事务:
- Order service:创建一个处于 APPROVAL PENDING状态的 Order。
- Consumer Service:验证当前订单中的消费者可以下单。
- Kitchen Service:验证订单内容,并创建一个后厨工单Ticket,状态为CREATE PENDING。
- Accounting service:对消费者提供的信用卡做授权操作。
- Kitchen Service:把后厨工单Ticket的状态改为AWAITING ACCEPTANCE。
- Order service:把Order的状态改为APPROVED。
当本地事务完成时,服务会发布消息。然后,此消息将触发Saga中的下一个步骤。使用消息不仅可以确保Saga参与方之间的松散耦合,还可以保证Saga完成。这是因为如果消息的接收方暂时不可用,则消息代理会缓存消息,直到消息可以被投递为止。
Saga使用补偿事务来回滚所做出的改变
传统ACID事务的一个重要特性是:如果业务逻辑检测到违反业务规则,可以轻松回滚事务。通过执行 ROLLBACK语句,数据库可以撤销(回滚)目前为止所做的所有更改。
Saga无法自动回滚,因为每个步骤都会将其更改提交到本地数据库。这意味着,如果Create Order saga的第4个步(信用卡授权)失败,则应用程序必须明确撤销前三个步骤所做的更改。你必须编写所谓的补偿事务。
Create Order saga的前3个步骤,它们被称为可补偿性事务,因为它们后面跟着的步骤可能失败;第4步被称为Saga的关键性事务,因为它后面跟着不可能失败的步骤;最后两个步骤被称为可重复性事务,因为它们总是会成功。
要了解如何使用补偿事务,请想象一下消费者信用卡授权失败的情况。在这种情况下,Saga执行以下本地事务:
- Order Service:创建一个处于APPROVAL PENDING状态的Order。
- Consumer service:验证消费者是否可以下订单。
- Kitchen Service:验证订单内容,并创建一个后厨工单 Ticket,状态为CREATE PENDING。
- Accounting service:授权消费者的信用卡,但失败了。
- Kitchen service:将后厨工单Ticket的状态更改为CREATE REJECTED。
- Order Service:将Order的状态更改为REJECTED。
第5步和第6步分别是用于取消由Kitchen Service和Order Service进行的更新的补偿事务。Saga的协调逻辑负责对正常事务和补偿事务的执行进行排序。
2. Saga的协调模式
Saga的实现包含协调Saga步骤的逻辑。当通过系统命令启动Saga时,协调逻辑必须选择并通知第一个Saga参与方执行本地事务。一旦该事务完成,Saga协调选择并调用下一个Saga参与方。这个过程一直持续到Saga执行完所有步骤。如果任何本地事务失败,则Saga必须以相反的顺序执行补偿事务。以下几种不同的方法可用来构建Saga的协调逻辑。
- 协同式:把Saga的决策和执行顺序逻辑分布在Saga的每一个参与方中,它们通过交换事件的方式来进行沟通。
- 编排式:把Saga的决策和执行顺序逻辑集中在一个Saga编排器类中。Saga编排器发出命令式消息给各个Saga参与方,指示这些参与方服务完成具体操作(本地事务)。
2.1 协同式Saga
实现Saga的一种方法是使用协同。使用协同时,没有一个中央协调器会告诉Saga参与方该做什么。相反,Saga参与方订阅彼此的事件并做出相应的响应。
图中显示了Create Order Saga的基于协同式版本的设计。参与方通过交换事件进行沟通。每个参与方从Order Service开始,更新其数据库并发布触发下一个参与方的事件。
Saga的正常工作路径如下所示:
- Order Service创建一个处于APPROVAL PENDING状态的Order并发布OrderCreated事件。
- Consumer service消费OrderCreated事件,验证消费者是否可以下订单,并发布 ConsumerVerified事件。
- Kitchen service消费OrderCreated事件,验证Order,创建一个处于CREATE PENDING状态的后厨工单Ticket,并发布 TicketCreated事件。
- Accounting Service消费OrderCreated事件并创建一个处于PENDING状态的CreditCardAuthorization。
- Accounting Service消费TicketCreated和ConsumerVerified事件,向消费者的信用卡收费,并发布 CreditCardAuthorized事件。
- Kitchen service消费CreditCardAuthorized事件并将 Ticket的状态更改为AWAITING ACCEPTANCE。
- Order Service接收 CreditCardAuthorized事件,将 Order的状态更改为APPROVED,并发布 OrderApproved事件。
Create Order Saga还必须处理Saga参与方拒绝 Order并发布某种失败事件的场景。例如,消费者信用卡的授权可能会失败。Saga必须执行补偿性事务来撤销已经完成的事务。图中显示了 Accounting service无法授权消费者信用卡时的事件流。
事件的顺序如下:
- Order Service创建一个处于APPROVAL PENDING状态的order并发布OrderCreated事件。
- Consumer Service消费 OrderCreated事件,验证消费者是否可以下订单,并发布ConsumerVerified事件。
- Kitchen Service消费 OrderCreated事件,验证Order,创建一个处于CREATE PENDING状态的后厨工单Ticket,并发布TicketCreated事件。
- Accounting Service消费OrderCreated事件并创建一个处于PENDING状态CreditCardAuthorization。
- Account Service消费TicketCreated和 ConsumerVerified事件,向消费者的信用卡扣款(失败了),并发布CreditCardAuthorizationFailed事件。
- Kitchen Service消费CreditCardAuthorizationFailed事件,然后把后厨工单Ticket的状态更改为REJECTED。
- Order Service消费CreditCardAuthorizationFailed事件,并将 order
的状态更改为REJECTED。
可靠的事件通信
在实现基于协同的Saga时,你必须考虑一些与服务间通信相关的问题。
- 确保Saga参与方将更新其本地数据库和发布事件作为数据库事务的一部分,数据库更新和事件发布必须是原子的。因此,为了可靠地进行通信,Saga参与方必须使用第3章中描述的事务性消息。
- 确保Saga参与方必须能够将接收到的每个事件映射到自己的数据上。例如,当Order Service收到CreditcardAuthorized事件时,它必须能够查找相应的 Order。解决方案是让Saga参与方发布包含相关性ID的事件,该相关性ID使其他参与方能够执行数据的操作。
协同式Saga的好处和弊端
基于协同式的Saga有以下几个好处:
- 简单:服务在创建、更新或删除业务对象时发布事件。
- 松耦合:参与方订阅事件并且彼此之间不会因此而产生耦合。
但它也有以下一些弊端:
- 更难理解:与编排式不同,代码中没有一个单一地方定义了Saga。相反,协调式Saga的逻辑分布在每个服务的实现中。因此,开发人员有时很难理解特定的Saga是如何工作的。
- 服务之间的循环依赖关系:Saga参与方订阅彼此的事件,这通常会导致循环依赖关系虽然这并不一定是个问题,但循环依赖性被认为是一种不好的设计风格。
- 紧耦合的风险:每个Saga参与方都需要订阅所有影响它们的事件。例如,Accounting Service必须订阅所有可能导致消费者信用卡被扣款或退款的事件。因此,存在一种风险,即Accounting Service的内部代码需要与 Order Service实现的订单生命周期代码保持同步更新。
2.2 编排式Saga
编排式是实现Saga的另外一种方式。当使用编排式Saga时,开发人员定义一个编排器类,这个类的唯一职责就是告诉Saga的参与方该做什么事情。Saga编排器使用命令/异步响应方式与Saga的参与方服务通信。为了完成Saga中的一个环节,编排器对某个参与方发出一个命令式的消息,告诉这个参与方该做什么操作。当参与方服务完成操作后,会给编排器发送一个答复消息。编排器处理这个消息,并决定Saga的下一步操作是什么。
实现编排式的 Create order saga
图中显示了Create Order saga的基于编排式的设计。该Saga由CreateOrderSaga类编排,该类使用异步请求/响应调用Saga参与方。该类跟踪流程并向Saga参与方发送命令式消息,例如Kitchen Service和Consumer Service。CreateOrderSaga类从其回复通道读取回复消息,然后确定Saga中的下一步(如果有的话)。Order Service首先创建(实例化)一个Order对象和一个Create Order Saga编排器对象。
一切正常情况下的流程如下所示:
- Saga编排器向Consumer Service发送VerifyConsumer命令。
- Consumer Service回复ConsumerVerified消息。
- Saga编排器向 Kitchen Service发送CreateTicket命令。
- Kitchen Service回复 TicketCreated消息。
- Saga编排器向Accounting Service发送AuthorizeCard消息。
- Accounting Service使用CardAuthorized消息回复。
- Saga编排器向Kitchen Service发送ApproveTicket命令。
- Saga编排器向Order Service发送ApproveOrder命令。
把Saga编排器视为一个状态机
状态机是建模Saga编排器的一个好方法。状态机由一组状态和一组由事件触发的状态之间的转换组成。每个转换都可以有一个动作,对Saga来说动作就是对某个参与方的调用。状态之间的转换由Saga参与方执行的本地事务完成触发。当前状态和本地事务的特定结果决定了状态转换以及执行的动作(如果有的话)。对状态机也有有效的测试策略。因此,使用状态机模型可以更轻松地设计、实现和测试Saga。
编排式Saga的好处和弊端
基于编排的Saga有以下好处
- 更简单的依赖关系:编排的一个好处是它不会引人循环依赖关系。Saga编排器调用Saga参与方,但参与方不会调用编排器。因此,编排器依赖于参与方,但反之则不然,因此没有循环依赖。
- 较少的耦合:每个服务实现供编排器调用的API,因此它不需要知道Saga参与方发布的事件。
- 改善关注点隔离,简化业务逻辑:Saga的协调逻辑本地化在Saga编排器中。领域对象更简单,并且不需要了解它们参与的Saga。业务更加简单。
编排式Saga也有一个弊端:
- 在编排器中存在集中过多业务逻辑的风险。这会导致这样的架构设计:智能编排器告诉哑服务要做什么操作。幸运的是,你可以通过设计只负责排序的编排器来避免此问题,并且不包含任何其他业务逻辑。
除了最简单的情况以外,建议在架构中使用编排式Saga。为Saga实现协调逻辑只是你需要解决的设计问题之一。
3. 解决隔离问题
ACID中的I代表隔离(isolation)。ACID事务的隔离属性可确保同时执行多个事务的结果与顺序执行它们的结果相同。数据库为每个ACID事务提供了具有对数据的独占访问权的错觉。隔离使得编写并发执行的业务逻辑变得更加容易。
使用Saga的挑战在于它们缺乏ACID事务的隔离属性。这是因为一旦该事务提交,每个Saga的本地事务所做的更新都会立即被其他 Sagas看到。此行为可能导致两个问题。首先,其他Saga可以在执行时更改该Saga所访问的数据。其他Saga可以在Saga完成更新之前读取其数据,因此可能会暴露不一致的数据。
事实上,你可以认为Saga只满足ACD三个属性:
- 原子性:Saga实现确保执行所有事务或撤销所有更改。
- 一致性:服务内的参照完整性由本地数据库处理。服务之间的参照完整性由服务处理。
- 持久性:由本地数据库处理。
3.1缺乏隔离导致的问题
缺乏隔离可能导致以下三种异常:
- 丢失更新:一个Saga没有读取更新,而是直接覆盖了另一个Saga所做的更改。
- 脏读:一个事务或一个Saga读取了尚未完成的Saga所做的更新。
- 模糊或不可重复读:一个Saga的两个不同步骤读取相同的数据却获得了不同的结果,因为另一个Saga已经进行了更新。
3.2 对策
- 语义锁:应用程序级的锁。
- 交换式更新:把更新操作设计成可以按任何顺序执行。
- 悲观视图:重新排序Saga的步骤,以最大限度地降低业务风险
- 重读值:通过重写数据来防止脏写,以在覆盖数据之前验证它是否保持不变。
- 版本文件:将更新记录下来,以便可以对它们重新排序。
- 业务风险评级:使用每个请求的业务风险来动态选择并发机制。
Saga的结构
在如图所示的模型中,一个Saga包含三种类型的事务:
- 可补偿性事务:可以使用补偿事务回滚的事务。
- 关键性事务:Saga执行过程的关键点。如果关键性事务成功,则Saga将一直运行到完成。关键性事务不见得是一个可补偿性事务,或者可重复性事务。但是它可以是最后一个可补偿的事务或第一个可重复的事务。
- 可重复性事务:在关键性事务之后的事务,保证成功。
可补偿性事务和可重复性事务之间的区别尤为重要。正如你将看到的,每种类型的事务在对策中扮演着不同的角色。当迁移到微服务时,有时单体必须参与Saga,如果单体只需要执行可重复性事务,那么它就会变得非常简单。
对策:语义锁
使用语义锁对策时,Saga的可补偿性事务会在其创建或更新的任何记录中设置标志。该标志表示该记录未提交且可能发生更改。该标志可以是阻止其他事务访问记录的锁,也可以是指示其他事务应该谨慎地处理该记录的一个警告。这个标志会被一个可重复的事务清除,这表示Saga成功完成;或通过补偿事务清除,这表示Saga发生了回滚。
对策:交换式更新
一个简单的对策是将更新操作设计为可交换的。如果可以按任何顺序执行,则操作是可交换的。这种对策很有用,因为它可以避免更新的丢失。例如,扣款动作,-100元,回滚动作+100元。
对策:悲观视图
处理缺乏隔离性的另一种方法是悲观视图。它重新排序Saga的步骤,以最大限度地降低由于脏读而导致的业务风险。例如,考虑先前用于描述脏读异常的场景。在这种情况下,Create Order Saga执行了可用信用额度的脏读,并创建了超过消费者信用额度的订单。为了降低发生这种情况的风险,此对策将重新排序 Cancel Order Saga。
- Order Service:将Order状态更改为已取消。
- Delivery Service:取消送货。
- Customer Service:增加可用信用额度。
在这个重新排序的Saga版本中,在可重复性事务中增加了可用的信用额度,消除了脏读的可能性。
对策:重读值
重读值对策可防止丢失更新。使用此计数器的Saga在更新之前重新读取记录,验证它是否未更改,然后更新记录。如果记录已更改,则Saga将中止并可能重新启动。此对策是乐观脱机锁模式的一种形式。
Create Order Saga可以使用此对策来处理Order在批准过程中被取消的情况。批准Order的事务验证Order是否保持不变,因为它是在Saga早期创建的。如果不变,事务将批准 Order。但是,如果Order已被取消,则事务将中止该Saga,从而触发其补偿事务执行。
对策:版本文件
版本文件对策之所以如此命名,是因为它记录了对数据执行的操作,以便可以对它们进行重新排序。这是将不可交换操作转换为可交换操作的一种方法。要了解此对策的工作原理,请考虑 Create Order Saga与 Cancel Order Saga同时执行的场景。除非Saga使用语义锁对策,否则 Cancel Order Saga可能会在 Create Order Saga授权信用卡之前取消消费者信用卡的授权。
Accounting Service处理这些无序请求的一种方法是在操作到达时记录操作,然后以正确的顺序执行操作。在这种情况下,它将首先记录 Cancel Authorization请求。然后,当 Accounting Service收到后续的 Authorize Card请求时,它会注意到它已经收到 Cancel Authorization请求并跳过授权信用卡。
对策:业务风险评级
最终的对策是基于价值(业务风险)对策。这是一种基于业务风险选择并发机制的策略。使用此对策的应用程序使用每个请求的属性来决定使用Saga和分布式事务。它使用Saga执行低风险请求,可能会应用前几节中描述的对策。但它使用分布式事务来执行高风险请求(例如涉及大量资金)。此对策使应用程序能够动态地对业务风险、可用性和可伸缩性进行权衡。
4. Eventuate Tram Saga框架
源码链接:https://github.com/eventuate-tram/eventuate-tram-sagas
如图所示,Eventuate Tram Saga是一个用于编写Saga编排器和Saga参与方的框架。它使用 Eventuate tram的事务性消息能力。
Saga Orchestration包是框架中最复杂的部分。它提供了 SimpleSaga,这是Saga的基本接口,以及一个创建和管理Saga实例的 SagaManager类。 SagaManager用于处理持久化Saga,发送它生成的命令式消息、订阅回复消息并调用Saga来处理回复。
如你所见,事务管理和业务逻辑设计的某些方面在微服务架构中是完全不同的。幸运的是,Saga编排器通常是非常简单的状态机,你可以使用Saga框架来简化代码。然而微服务架构中的事务管理肯定比单体架构更复杂。但这通常只是在享受微服务带来的巨大好处时付出的小代价。
标签:架构设计,事务,Service,--,参与方,Saga,编排,Order 来源: https://blog.csdn.net/ylnlp5602260/article/details/113809953