大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺
1. 分布式事务
在前面文章《分布式事务》中介绍了几种分布式事务,其中Saga介绍了相关的概念,接下来介绍Saga使用案例,案例来源《微服务架构设计模式》。
2. 案例需求分析
2.1 一个成功的订单创建流程
实现餐馆系统中的创建订单createOrder()操作。这个操作必须验证消费者是否满足下订单的相关条件、验证订单内容、完成消费者的信用卡授权,以及在数据库中创建Order。一个成功的订单创建流程:
- 创建一个待处理订单;
- 验证订单消费者可以下单;
- 创建后厨工单;
- 对消费者提供的信用卡进行授权操作;
- 更新后厨工单状态为“接受”状态;
- 更新订单状态为“通过”。
在单体应用中,这样的操作是相对直观和容易实现的。在验证中需要的所有数据都可以从数据库中直接读取,此外,可以使用一个ACID类事务来保证数据的一致性。
在微服务架构下实现同样的操作则颇有难度。如图一所示,所需要的验证数据散布在不同的服务中。createOrder()操作必须访问多个服务(包括:ConsumerService,KitchenService和AccountingService)来获得它所需要的验证数据。
createOrder()操作涉及三个服务:
- 订单服务:Order Service。功能:创建订单。
- 消费者服务:Consumer Service。功能:验证当前订单中的消费者是否可下单。
- 后厨服务:Kitchen Server。功能:验证订单内容,创建后厨工单Ticket。
- 账户服务:Account Service。功能:对消费者提供的信用卡授权。
(图一)
订单创建时序图:
(图二)
一个订单创建操作依赖三个服务原因是,实例中对餐饮服务进行了微服务话拆分,服务拆分使用了“领域驱动设计模式”,详情见《微服务架构设计模式》。备注:“领域驱动设计模”适合在复杂的业务模型使用。
2.2 存在的挑战
订单创建操作中涉及的三个服务,每个服务都有自己的私有数据库,怎样保障多数据库环境下的数据一致性?我们选择使用Saga模式来维护数据一致性。
3. 使用Saga模式维护数据一致性
Saga是一种在微服务架构中维护数据一致性的机制,它可以避免分布式事务所带来的问题。
- 一系列操作:一个Saga表示需要更新多个服务中数据的一个系统操作。
- 一连串本地事务:Saga由一连串的本地事务组成。每一个本地事务负责更新它所在服务的私有数据库,这些操作依赖于ACID 事务框架和函数库。
- 使用补偿事务:由于Saga缺少ACID事务的隔离性,因此必须使用补偿事务回滚Saga。
- 依次触发执行:系统操作启动了Saga的第一步。完成本地事务会触发下一个本地事务的执行。
4. 使用Saga实现订单创建实例
使用Saga实现前文中案例createOrder()操作。Saga事务操作流程如下图:
(图三)
这个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参与方之间的松散耦合,还可以保证Saga完成。因为如果消息的接收方暂时不可用,则消息代理会缓存消息,直到消息可以被投递为止。
- Saga挑战:一个挑战是Saga之间缺乏隔离。另一个挑战是在发生错误时的回滚更改。
5. Saga补偿事务
5.1 需要编写补偿事务原因
在传统ACID事务中,可以通过回滚事务,数据库可以撤销(回滚)目前为止所做的所有更改。
Saga中每个步骤将更改提交到本地数据库中,因此无法自动回滚。必须编写补偿事务。
5.2 编写补偿事务流程
- 假设一个Saga的第n+1个事务失败了。必须撤销前n个事务的影响。
- 每个步骤Ti都有一个相应的补偿事务Ci,它可以撤销Ti的影响。
- 要撤销前n个步骤的影响,Saga必须以相反的顺序执行每个Ci。步骤顺序为Ti…Tn, C…Ci。
如图四所示。在这个例子中,Tn+1失败,这需要撤销步骤T1…Tn。
(图四)
Create Order Saga。这个Saga可能导致执行失败的原因:
- 消费者的信息无效或者不允许他下单。
- 餐馆信息无效或餐馆无法接受订单。
- 消费者的信用卡验证失败。
6. Saga 的协调模式
Saga协调模式简介:
- Saga的实现包含协调Saga步骤的逻辑。
- 当通过系统命令启动Saga时,协调逻辑必须选择并通知第一个Saga参与方执行本地事务。
- 一旦该事务完成, Saga协调选择并调用下一个Saga参与方。这个过程一直持续到Saga执行完所有步骤。
- 如果任何本地事务失败,则Saga必须以相反的顺序执行补偿事务。
构建Saga的协调逻辑的两种方法:
- 协同式(choreography):把Saga的决策和执行顺序逻辑分布在Saga的每一个参与方中,它们通过交换事件的方式来进行沟通。
- 编排式(orchestration):把Saga的决策和执行顺序逻辑集中在一个Saga编排器类中。Saga编排器发出命令式消息给各个Saga参与方,指示这些参与方服务完成具体操作,(本地事务)。
6.1 协同式 Saga
协同式 Saga没有中央协调器,Saga参与方订阅彼此的事件并做出相应的响应。
6.1.1 实现协同式的 Create Order 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状态的CreditCardAuthorizationo
- Accounting Service 消费 TicketCreated 和 ConsumerVerified 事件,向消费者的信用卡收费,并发布CreditCardAuthorized事件。
- Kitchen Service 消费 CreditCardAuthorized 事件并将Ticket 的状态更改为AWAITING_ACCEPTANCE。
- Order Service接收 CreditCardAuthorized事件,将Order的状态更改为APPROVED,并发布 OrderApproved 事件。
(图五)
6.1.2 协同式补偿事务
按照前文介绍,Create 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。
(图六)
6.1.3 可靠事件通信
服务间通信需要考虑两个问题:
- 事务性消息:确保Saga参与方将更新其本地数据库和发布事件作为数据库事务的一部分。为了保证事务性消息,数据库更新和事件发布必须是原子的。
- 事件与数据映射:Saga参与方必须能够将接收到的每个事件映射到自己的数据上。解决方案是让Saga参与方发布包含相关性ID的事件,该相关性ID使其他参与方能够执行数据的操作。
6.1.4 协同式Saga的好处和弊端
基于协同式的Saga好处:
- 简单:服务在创建、更新或删除业务对象时发布事件。
- 松耦合:参与方订阅事件并且彼此之间不会因此而产生耦合。
基于协同式的Saga弊端:
- 更难理解:与编排式不同,代码中没有一个单一地方定义了Saga。相反,协调式Saga的逻辑分布在每个服务的实现中。因此,开发人员有时很难理解特定的Saga是如何工作的。
- 服务之间的循环依赖关系: Saga参与方订阅彼此的事件,这通常会导致循环依赖关系。虽然这并不一定是个问题,但循环依赖性被认为是一种不好的设计风格。
- 紧耦合的风险:每个Saga参与方都需要订阅所有影响它们的事件。例如,AccountingService 必须订阅所有可能导致消费者信用卡被扣款或退款的事件。因此,存在一种风险,即Accounting Service的内部代码需要与Order Service实现的订单生命周期代码保持同步更新。
6.2 编排式 Saga
当使用编排式Saga时,开发人员定义一个编排器类,这个类的唯一职责就是告诉Saga的参与方该做什么事情。
- Saga编排器使用命令/异步响应方式与Saga的参与方服务通信。
- 为了完成Saga中的一个环节,编排器对某个参与方发出一个命令式的消息,告诉这个参与方该做什么操作。
- 当参与方服务完成操作后,会给编排器发送一个答复消息。编排器处理这个消息,并决定Saga的下一步操作是什么。
6.2.1 实现协同式的 Create Order Saga
图七显示了Create Order Saga的基于编排式的设计。该Saga由CreateOrderSaga类编排,该类使用异步请求/响应调用Saga参与方。该类跟踪流程并向Saga参与方发送命令式消息。CreateOrderSaga类从其回复通道读取回复消息,然后确定Saga中的下一步(如果有的话)。
备注:基于编排式的Saga模型建模为状态机模式。
Order Service 首先创建(实例化)一个 Order对象和一个Create Order Saga编排器对象。一切正常情况下的流程如下所示:
- Saga 编排器向 Consumer Service 发送 verify Consumer 命令。
- Consumer Service 回复 Consumer Verified 消息。
- Saga 编排器向 kitchen Service 发送 Create Ticket 命令。
- Kitchen Service 回复 Ticket Created 消息。
- Saga 编排器向 Accounting Service 发送 Authorize Card 消息。
- Accounting Service 使用Card Authorized 消息回复。
- Saga 编排器向 kitchen Service 发送 Approve Ticket 命令。
- Saga 编排器向 Order Service 发送 Approve Order 命令。
(图七)
注意:在最后一步中,Saga编排器会向Order Service发送命令式消息,即使它是Order Service的一个组件。原则上,Create Order Saga可以通过直接更新Order来批准订单。但为了保持一致性, Saga将Order Service视为另一个参与方。
6.2.2 把 Saga 编排器视为一个状态机
图七描述了编排式Saga,一个Saga可能有多个场景。将Saga建模为状态机非常有用,因为它描述了所有可能的场景。
状态机由一组状态和一组由事件触发的状态之间的转换组成。每个转换都可以有一个动作。《状态模式(State)》
图八显示了Create Order Saga的状态机模型。此状态机由多个状态组成,包括以下内容:
(图八)
状态机还定义其他状态转换。例如:
- 状态机从Creating Ticket状态转换为Authorizing Card 或 Rejected Order 状态。
- 当收到成功回复Create Ticket命令时,它将转换到Authorizing Card状态
- 如果Kitchen Service无法创建Ticket,则状态机将转换为Order Rejected状态。
6.2.3 编排式 Saga 的好处和弊端
编排式的 Saga 好处:
- 更简单的依赖关系:编排的一个好处是它不会引入循环依赖关系。
- 较少的耦合:每个服务实现供编排器调用的API,因此它不需要知道Saga参与方发布的事件。
- 改善关注点隔离,简化业务逻辑: Saga的协调逻辑本地化在Saga编排器中。领域对象更简单,并且不需要了解它们参与的Saga。
编排式的 Saga 弊端:
- 在编排器中存在集中过多业务逻辑的风险。
建议在架构中使用编排式Saga。为Saga实现协调逻辑只是你需要解决的设计问题之一。另外,处理缺乏隔离的问题也许是你在使用Saga时面临的最大挑战。
7. 隔离问题
ACID 中的I代表隔离(isolation)。ACID 事务的隔离属性可确保同时执行多个事务的结果与顺序执行它们的结果相同。使用Saga的挑战在于它们缺乏ACID事务的隔离属性。这是因为一旦该事务提交,每个Saga的本地事务所做的更新都会立即被其他Sagas看到。此行为可能导致两个问题。
- 其他Saga可以在执行时更改该Saga所访问的数据。
- 其他Saga可以在Saga完成更新之前读取其数据,导致数据不一致。
Saga只满足ACD三个属性:
- 原子性:Saga实现确保执行所有事务或撤销所有更改。
- 一致性:服务内的参照完整性(referential integrity)由本地数据库处理。服务之间的参照完整性由服务处理。
- 持久性:由本地数据库处理。
7.1 缺乏隔离导致的问题
缺乏隔离可能导致以下三种异常:
- 丢失更新:一个Saga没有读取更新,而是直接覆盖了另一个Saga所做的更改。
- 脏读:一个事务或一个Saga读取了尚未完成的Saga所做的更新。
- 模糊或不可重复读:一个Saga的两个不同步骤读取相同的数据却获得了不同的结果,因为另一个Saga已经进行了更新。
这三个异常都可能发生,其中前两个是最常见和最具挑战性的。
7.1.1 丢失更新
当一个Saga覆盖另一个Saga所做的更新时,就会发生丢失更新异常。例如,下面场景:
- Create Order Saga 的第一步创建了 Order。
- 当该Saga正在执行时,另外一个Cancel Order Saga取消了这个Order。
- Create Order Saga 的最后一步批准 Order。
在这种情况下, Create Order Saga 忽略 Cancel Order Saga所做的更新并覆盖它。
7.1.2 脏读
当一个Saga试图访问正被另一个Saga更新的数据时,就会发生脏读。例如,我们假设某个版本的FTGO应用保存了消费者的可用额度。在此应用程序中。取消订单的Saga包含以下事务:
- Consumer Service:增加可用额度。
- Order Service:将Order 状态更改为已取消。
- Delivery Service:取消送货。
看一个场景: Cancel Order Saga 和 Create Order Saga 交错执行,并且由于无法执行取消操作,Cancel Order Saga被回滚。调用Consumer Service的事务的可能顺序如下所示:
- Cancel Order Saga:增加可用额度。
- Create Order Saga:减少可用额度。
- Cancel Order Saga:减少可用额度的补偿事务。
在这种情况下, Create Order Saga对可用额度发生了脏读,使消费者下的订单能够超过其信用额度。
7.2 Saga模式下实现隔离对策
7.2.1 对策介绍
Saga事务模型是ACD,它缺乏隔离可能导致异常,从而导致应用程序行为错误。
解决方法,使用对策防止一个或多个异常或最小化它们对业务的影响。对策有:
- 语义锁:应用程序级的锁。
- 交换式更新:把更新操作设计成可以按任何顺序执行。
- 悲观视图:重新排序Saga的步骤,以最大限度地降低业务风险。
- 重读值:通过重写数据来防止脏写,以在覆盖数据之前验证它是否保持不变。
- 版本文件:将更新记录下来,以便可以对它们重新排序。
- 业务风险评级(by value):使用每个请求的业务风险来动态选择并发机制。
Saga 包含三种类型的事物:
- 可补偿性事务:可以使用补偿事务回滚的事务。
- 关键性事务:Saga执行过程的关键点。如果关键性事务成功,则Saga将一直运行到完成。关键性事务不见得是一个可补偿性事务,或者可重复性事务。但是它可以是最后一个可补偿的事务或第一个可重复的事务。
- 可重复性事务:在关键性事务之后的事务,保证成功。
(图九)
7.2.2 对策:语义锁
使用语义锁对策时, Saga的可补偿性事务会在其创建或更新的任何记录中设置标志。
- 该标志表示该记录未提交且可能发生更改。
- 该标志可以是阻止其他事务访问记录的锁,也可以是指示其他事务应该谨慎地处理该记录的一个警告。
- 这个标志会被一个可重复的事务清除,这表示Saga成功完成;或通过补偿事务清除,这表示Saga发生了回滚。
7.2.3 对策:交互式更新
一个简单的对策是将更新操作设计为可交换的。如果可以按任何顺序执行,则操作是可交换的(commutative)。账户的debit()和credit ()操作是可交换的(如果忽略透支支票)。这种对策很有用,因为它可以避免更新的丢失。
Debit:借,Credit:贷。会计科目的借方登记资产的增加,贷方登记资产的减少。
例如,考虑一个场景,在一个可补偿性事务记入(或贷记)一个账户之后,需要回滚一个Saga。补偿事务可以简单地贷记(或借记)账户以撤销更新。这就不可能覆盖其他Saga的更新。
7.2.4 对策:悲观视图
处理缺乏隔离性的另一种方法是悲观视图(Pessimistic View)。它重新排序Saga的步骤,以最大限度地降低由于脏读而导致的业务风险。
例如,考虑先前用于描述脏读异常的场景。在这种情况下,Create Order Saga执行了可用信用额度的脏读,并创建了超过消费者信用额度的订单。为了降低发生这种情况的风险,此对策将重新排序Cancel Order Saga。
- Order Service:将Order 状态更改为已取消。
- Delivery Service:取消送货。
- Customer Service:增加可用信用额度。
在这个重新排序的Saga版本中,在可重复性事务中增加了可用的信用额度,消除了脏读的可能性。
7.2.5 对策:重读值
重读值对策可防止丢失更新。使用此计数器的Saga在更新之前重新读取记录,验证它是否未更改,然后更新记录。如果记录已更改,则Saga将中止并可能重新启动。此对策是乐观脱机锁模式的一种形式(https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html)。
7.2.5 对策:版本文件
版本文件对策之所以如此命名,是因为它记录了对数据执行的操作,以便可以对它们进行重新排序。这是将不可交换操作转换为可交换操作的一种方法。要了解此对策的工作原理,请考虑Create Order Saga与Cancel Order Saga同时执行的场景。除非Saga使用语义锁对策,否则Cancel Order Saga可能会在Create Order Saga授权信用卡之前取消消费者信用卡的授权。
Accounting Service 处理这些无序请求的一种方法是在操作到达时记录操作,然后以正确的顺序执行操作。在这种情况下,它将首先记录Cancel Authorization请求。然后,当Accounting Service收到后续的Authorize Card请求时,它会注意到它已经收到Cancel Authorization请求并跳过授权信用卡。
7.2.5 对策:业务风险评级
最终的对策是基于价值(业务风险)对策。这是一种基于业务风险选择并发机制的策略。使用此对策的应用程序使用每个请求的属性来决定使用Saga和分布式事务。它使用Saga执行低风险请求,可能会应用前几节中描述的对策。但它使用分布式事务来执行高风险请求(例如涉及大量资金)。此对策使应用程序能够动态地对业务风险、可用性和可伸缩性进行权衡。
7.2.6 小结
在应用程序中实现Saga时,可能需要使用一种或多种这样的对策。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/193405.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...