目录

前言

传统的分布式事务管理方法对于现代应用程序来说不是一个好的选择,跨服务的操作必须使用所谓的Saga(一种消息驱动的本地事务序列)来维护数据一致性,而不是ACID事务(原子性、一致性、隔离性和持久性)。

Saga的一个挑战在于只满足ACD(原子性、一致性和持久性)特性,而缺乏ACID事务的隔离性。因此应用程序必须使用所谓的对策(countermeasure),找到办法来防止或减少由于缺少隔离而导致的并发异常

这是一本关于微服务架构设计方面的书,这是本人阅读的学习笔记。以下对一些符号做些说明:

()为补充,一般是书本里的内容;

[]符号为笔者笔注;


1. 微服务架构下的事务管理

微服务架构下的事务往往需要横跨多个服务,每个服务都有属于自己的私有数据库。在这种情况下,应用程序必须使用一些更为高级的事务管理机制来管理事务。

1.1 分布式事务的挑战

在多个服务、数据库和消息代理之间为此数据一致性的传统方法是采用分布式事务。

分布式事务管理的事实标准是XA标准:

  • XA采用两阶段提交来保证事务中所有参与方同时完成提交,或者失败时同时回滚;
  • 应用程序中的整个技术栈需要满足XA标准(包括数据库、消息代理、数据库驱动、消息API等);

其存在的挑战有:

  • 许多新技术,包括NoSQL数据库(如MongoDB和Cassandra),不支持XA标准的分布式事务;
  • 一些流行的消息代理(如RabbitMQ和Apache Kafka)不支持分布式事务;
  • 分布式事务本质都是同步进程间通信,会降低分布式系统的可用性;

1.2 一个Saga的示例

Sage模式:通过使用异步消息来协调一系列本地事务,从而维护多个服务之间的数据一致性。



当本地事务完成时,服务会发布消息。然后,此消息将触发Saga中的下一个步骤。

1.3 Saga使用补偿事务来回滚所作出的改变

Saga无法自动回滚事务,因为每个步骤都会将其更改提交到本地数据库。



需要注意不是所有步骤都需要事务补偿,如只读步骤、当某步骤之后的操作总会成功时等。

2. Saga的协调模式

Saga的实现包含协调Saga步骤的逻辑。

2.1 两种Saga协调模式

  • 协同式(choreography):把Saga的决策和执行顺序逻辑分布在Sage的每一个参与方中,它们通过交换事件的方式来进行沟通;
  • 编排式(orchestration):【推荐】把Saga的决策和执行顺序逻辑集中在一个Saga编排器类中。Saga编排器发出命令式消息给各个Saga参与方,指定这些参与方服务完成具体操作(本地事务);

2.2 实现协同式的Create Order Saga

参与方通过交换事件进行沟通,每个参与方从Order Service开始,更新其数据库并发布触发下一个参与方事件。

  • 当第5步失败时,创建的事件名称为:Credit card authorization failed



图解

  1. Order Service创建一个处于APPROVAL_PENDING状态的Order并发布OrderCreated事件;
  2. Consumer Service消费OrderCreated事件,验证消费者是否可以下订单,并发布ConsumerVerified事件;
  3. Kitchen Service消费OrderCreated事件,验证Order,创建一个处于CREATE_PENDING状态的后厨工单Ticket,并发布TicketCreated事件;
  4. Accounting Service消费OrderCreated事件并创建一个处于PENDING状态的CreditCardAuthorization;
  5. Accounting Service消费TicketCreatedConsumerVerified事件,向消费者的信用卡收费,并发布CreditCardAuthorized事件;
    • 如果失败,则发布CreditCardAuthorizationFailed事件;
  6. Kitchen Service消费CreditCardAuthorized事件,将Ticket的状态更改为AWAITING_ACCEPTANCE;
    • 如果失败,则消费CreditCardAuthorizationFailed事件,将Ticket的状态更改为REJECTED;
  7. Order Service接收CreditCardAuthorized事件,将Order的状态改为APPROVED,并发布OrderApproved事件;
    • 如果失败,则消费CreditCardAuthorizationFailed事件,将Order的状态更改为REJECTED;

2.3 协同式Sage服务间通信相关的问题

在实现基于协同的Saga时,需要考虑一些与服务间通信相关的问题:

  • 确保Saga参与方将更新其本地数据库和发布事件作为数据库事务的一部分;

    • 例如:在Create Order Saga中,Kitchen Service接受CreditCarAuthorized事件,创建Ticket,发布到TicketCreated事件;数据库的更新和事件发布必须是原子的;
    • 解决:Saga参与方必须使用事务性消息;
  • 确保Saga参与方必须能够将接受到的每个事件映射到自己的数据上;
    • 例如:当Order Service收到CreditCardAuthorized事件时,它必须能够查找相应的Order;
    • 解决:让Saga参与方发布包含相关性ID的事件;

2.4 协同式Sage的优缺点

好处

  • 简单:服务在创建、更新和删除业务对象时发布事件;
  • 松耦合:参与方订阅事件并且彼此之间不会因此而产生耦合;

弊端

  • 更难理解:协调式Saga的逻辑分布在每个服务的实现中;开发人员有时很难理解特定Saga是如何工作;
  • 服务之间的循环依赖关系:Saga参与方订阅彼此事件,通常会导致循环依赖关系;
  • 紧耦合的风险:每个Saga参与方都需要订阅所有影响它们的事件;

2.5 实现编排式的Create Order Saga

开发人员定义一个编排器类,该类唯一的职责是告诉Saga的参与方该做什么事清。Saga编排器使用命令 / 异步响应方式与Saga参与方服务通信。

基于编排式是Saga的每个步骤都包括一个更新数据库和发布消息的服务。

图解

Order Service首先创建(实例化)一个Order对象和一个Create Order Saga编排器对象,一切正常后流程如下:

  1. Saga编排器向Consumer Service发送Verify Consumer命令;
  2. Consumer Service回复Consumer Verified消息;
  3. Saga编排器向Kitchen Service发送Create Ticket命令;
  4. Kitchen Service回复Ticket Created消息;
  5. Saga编排器向Accounting Service发送Authorize Card消息;
  6. Accounting Service使用Card Authorized消息回复;
  7. Saga编排器向Kitchen Service发送Approve Ticket命令;
  8. Saga编排器向Order Service发送Approve Ordere命令(命令式消息);

2.6 把Saga编排器视为一个状态机

状态机是由一组状态和一组由事件触发的状态之间的转换组成。每个转换都可以有一个动作,对Saga来说动作就是对某个参与方对调用。

将Saga建模成状态机非常有用,因为它描述了所有可能的场景(可能成功也可能失败)。

  • Verifying Consumer:初始状态。当处于此状态时,该Saga正在等待Consumer Service验证消费者是否可以下订单;
  • Creating Ticket:该Saga正在等待对Create Ticket命令的回复;
  • Authorizing Card:等待Authorizing Service授权消费者的信用卡;
  • Order Approved:最终状态,表示该Saga已成功完成;
  • Order Rejected:最终状态,表示Order被其中一个参与方拒绝;

2.7 编排式Saga的优缺点

好处

  • 更简单的依赖:不会引入循环依赖关系;
  • 较少的耦合:每个服务实现供编排器调用的API,因此它不需要知道Saga参与方发布的事件;
  • 改善关注点隔离,简化业务逻辑:Saga的协调逻辑本地化在Saga编排器中;领域对象更简单,并且不需要了解它们参与的Saga;

弊端

  • 在编排器中存在集中过多业务逻辑的风险

    • 解决办法:可以通过设计只负责排序的编排器来避免此问题,并且不包含任何其他业务逻辑;

3. 解决隔离问题

ACID事务的隔离性可确保同时执行多个事务的结果与顺序执行它们的结果相同。而Saga只满足ACD(原子性、一致性、持久性),不满足隔离性。

3.1 Saga只满足ACD

  • 原子性:Saga实现确保执行所有事务或撤销所有更改;
  • 一致性:服务内的参照完整性(referential integrity)由本地数据库处理;服务之间的参照完整性由服务处理;
  • 持久性:由本地数据库处理;

3.2 缺乏隔离导致的问题

缺乏隔离将导致以下三个异常。

  • 丢失更新:一个Saga没有读取更新,而是直接覆盖了另一个Saga所做的更改;
  • 脏读:一个事务或一个Saga读取了尚未完成的Saga所做的更新;
  • 模糊或不可重复读:一个Saga的两个不同步骤读取相同的数据却获得了不同的结果,因为另一个Saga已经进行了更新;

3.3 Saga的结构模型术语

一个Saga包含三个类型的事务。

  • 可补偿性事务:可以使用补偿事务回滚的事务;
  • 关键性事务:Saga执行过程的关键节点。如果关键性事务成功,则Saga将一直运行到完成。关键性事务不见得是一个可补偿性事务,或者可重复性事务。但是它可以是最后一个可补偿的事务或第一个可重复的事务;
  • 可重复性事务:在关键性事务之后的事务,保证成功;

3.4 解决隔离问题的对策

  • 语义锁:应用程序级的锁;

    • Saga的可补偿性事务会在其创建或更新的任何记录中设置标志,该标志表示该记录未提交且可能发生失败;
    • 如:在可补偿性事务执行时给操作对象添加上*_PENDING状态,以告诉该对象的其他Saga,该对象当前正处于一个Saga的处理过程中;
  • 交换式更新:把更新操作设计成可以按任何顺序执行;
    • 将更新操作设计为可交换的;
    • 如:账户的debit()credit()操作是可交换的;
  • 悲观视图:重新排序Saga的步骤,以最大限度地降低业务风险;
    • 重新排序Saga的步骤,以最大限度地降低由于脏读而导致的业务风险;
  • 重读值:通过重写数据来防止脏写,以在覆盖数据之前验证它是否保持不变;
    • 使用此对策的Saga在更新之前重新读取记录,验证它是否未更改,然后根性记录;如果记录已更改,则Saga将中止并可能重新启动。此对策是乐观脱机锁模式的一种形式;
    • 如:可以用来处理Order在批准过程中被取消的情况;
  • 版本文件:将更新记录下来,以便可以对它们重新排序;
    • 记录对数据执行的操作,以便可以对它们进行重新排序;
    • 如:当Accounting Service先收到Cancel Authorization请求,再收到Authorize Card请求时,它会注意到它已经收到Cancel Authorization请求并跳过授权信用卡;
  • 业务风险评级(by value):使用每个请求的业务风险来动态选择并发机制;
    • 使用此对策的应用程序使用每个请求的属性来决定使用Saga和分布式事务;

4. Order Service和Create Order Saga的设计

示例:使用语义锁对策的Create Order Saga的详细设计和实现。

4.1 Order Service的设计及其Saga

  • 服务的业务逻辑由传统的业务逻辑类组成,与传统业务一样,业务核心由OrderServiceOrderOrderRepository类实现;
  • 还有一些Saga编排器类,包括CreateOrderSaga类,它可以编排Create Order Saga;
  • Order Service即是一个Saga编排器,也是一个Saga参与方;Order Service参与它自己的Saga,它有一个OrderCommandHandlers适配器类,该适配器类通过调用Order Service来处理命令消息;

4.2 OrderService类

一个由服务的API层调用的领域服务,负责创建和管理订单。

OrderService的UML类图



OrderService类及其createOrder()方法

4.3 Create Order Saga的实现

使用Eventuate Tram Saga框架编写,它提供了一种特定于领域的语言(DSL),用于定义Saga的状态。

OrderService的Saga UML类图

  • CreateOrderSaga:定义Saga状态机的单例类;它调用CreateOrderSagaState来创建命令式消息,并使用Saga参与方代理类(如KitchenServiceProxy)指定的消息通道将它们发送给参与方;
  • CreateOrderSagaState:一个Saga的持久化状态,用于创建命令式消息;
  • Saga参与方的代理类(如KitchenServiceProxy):每个代理类定义一个Saga参与方的消息API,它由命令通道、命令式消息和回复类型组成。

4.4 CreateOrderSaga编排器

CreateOrderSaga类实现了2.6点的状态机;它使用Eventuate Tram Saga框架提供的DSL来定义Create Order Saga的步骤;核心代码为Saga的定义,如下:

4.5 CreateOrderSagaState类

CreateOrderSagaState类表示Saga实例状态;此类由OrderService创建,并由Eventuate Tram Saga框架持久化保存在数据库中;其职责为创建发送给Saga参与方的消息;

4.6 KitchenServiceProxy类

代理类不是必要的,使用代理类的好处有两个:代理类定义静态类型端点,这减少了Saga向服务发送错误消息的可能性;代理类是一个定义良好的调用服务的API,使服务代码更易于理解和测试;

4.7 Eventuate Tram Saga框架

Eventuate Tram Saga是一个用于编写Saga编排器和Saga参与方的框架;它使用Eventuate Tram的事务性消息能力。

Eventuate Tram Saga框架

OrderService创建Create Order Saga实例时的事件序列

当SagaManager收到来自Saga参与方的回复消息时的事件序列

4.8 OrderCommandHandlers类

Order Service参与其自己的Saga;OrderCommandHandlers类定义了这些Saga发起命令式消息的处理程序方法。

OrderCommandHandlers UML类图

Order Service的命令处理程序



4.9 OrderServiceConfiguration类

Order Service使用Spring框架;OrderServiceConfiguration是一个@Configuration类,使用Spring @Bean实例化并组装在一起。

5. 本章小结

  • 某些系统操作需要更新分散在多个服务中的数据。传统的基于XA / 2PC的分布式事务不适合现代应用。更好的方法是使用Saga模式。Saga是使用消息机制协调的一组本地事务序列。每个本地事务都在单个服务中更新数据。由于每个本地事务都会提交更改,因此如果由于违反业务规则而导致Saga必须回滚,则必须执行补偿事务以显式撤销更改;
  • 可以使用协同或编排协调Saga的步骤。在基于协同的Saga中,本地事务发布触发其他参与方执行本地事务的事件。在基于编排的Saga中,集中式Saga编排器向参与方发送命令式消息,告诉它们执行本地事务。可以通过将Saga编排器建模为状态机来简化开发和测试,简单的Saga可以使用协同式,但编排式通常是复杂Saga的更好选择;
  • 设计基于Saga的业务逻辑可能具有挑战性,因为与ACID事务不同,Saga不是彼此孤立的。你必须经常使用各种对策,即防止ACD事务模型引起的并发异常的设计策略。应用甚至可能需要使用锁来简化逻辑,即使这样会导致死锁。

最后

新人制作,如有错误,欢迎指出,感激不尽!
欢迎关注公众号,会分享一些更日常的东西!
如需转载,请标注出处!

《微服务架构设计模式》读书笔记 | 第4章 使用Saga管理事务的更多相关文章

  1. 微服务架构(Microservice Architect Pattern)综述——什么是微服务架构(读书笔记)

    简单定义: 微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间相互协调,相互配合,为用户提供最终价值.每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制相互沟通(通 ...

  2. 腾讯T8纯手写66个微服务架构设计模式,全部学会真的“变强”了

    微服务的概念虽然直观易懂,但“细节是魔鬼”,微服务在实操落地的环节中存在诸多挑战.我们在为企业提供PaaS.人工智能.云原生平台等数字化转型解决方案时也发现,企业实现云原生,并充分利用PaaS能力的第 ...

  3. 部署:持续集成(CI)与持续交付(CD)——《微服务设计》读书笔记

        系列文章目录:     <微服务设计>读书笔记大纲 一.CI(Continuous Integration)简介  CI规则1:尽量频繁地把代码签入到分支中以进行集成 CI规则2: ...

  4. 同事跳槽阿里P7,甩我一份微服务架构设计模式文档,看完我也去

    给所有微服务架构开发者的忠告,我想对你们说: 第一,要记住微服务不是解决所有问题的万能“银弹”. 第二,编写整洁的代码和使用自动化测试至关重要,因为这是现代软件开发的基础. 第三,关注微服务的本质,即 ...

  5. 《微服务架构设计模式》读书笔记 | 第8章 外部API模式

    目录 前言 1. 外部API的设计难题 1.1 FTGO应用程序的服务及客户端 1.2 FTGO移动客户端API的设计难题 1.3 其他类型客户端API的设计难题与特点 2. API Gateway模 ...

  6. springcloud微服务架构搭建入门笔记

    注册管理服务器 应用入口配置 @SpringBootApplication @EnableEurekaServer public class GatewayApplication { public s ...

  7. .net架构设计读书笔记--第三章 第10节 命令职责分离(CQRS)简介(Introducing CQRS)

    一.分离查询命令 Separating commands from queries     早期的面向DDD设计方法的难点是如何设计一个类,这个类要包含域的方方面面.通常来说,任务软件系统方法调用可以 ...

  8. .net架构设计读书笔记--第三章 第9节 域模型实现(ImplementingDomain Model)

        我们长时间争论什么方案是实现域业务领域层架构的最佳方法.最后,我们用一个在线商店案例来说明,其中忽略了许多之前遇到的一些场景.在线商店对很多人来说更容易理解. 一.在线商店项目简介 1. 用例 ...

  9. .net架构设计读书笔记--第三章 第8节 域模型简介(Introducing Domain Model)

    一.数据--行为转变     很长的时间,典型的分析方法或多或少是以下两种,第一,收集需求并做一些分析,找出有关实体 (例如,客户. 订单. 产品) 和进程来实现. 第二,手持这种理解你尝试推断一个物 ...

随机推荐

  1. Linux搭建私有yum源

    一.前期准备 环境:CentOS 8.3 镜像: CentOS-7-x86_64-Everything-2009.iso CentOS-8.3.2011-x86_64-dvd1.iso 二.搭建步骤 ...

  2. linux 中只显示目录的几种方法

    ls 参数 -a 表示显示所有文件,包含隐藏文件-d 表示显示目录自身的属性,而不是目录中的内容-F 选项会在显示目录条目时,在目录后加一个/ ls -l total 8 drwxrwxr-x 2 r ...

  3. [zebra源码]分片语句ShardPreparedStatement执行过程

    主要过程包括: 分库分表的路由定位 sql语句的 ast 抽象语法树的解析 通过自定义 SQLASTVisitor (MySQLSelectASTVisitor) 遍历sql ast,解析出逻辑表名 ...

  4. 「CF521E」 Cycling City

    「CF521E」 Cycling City 传送门 首先你能发现这个东西一定是两个环的公共边. 最开始想的是什么如果一个点被访问过三次那它一定是公共边的某一端之类的东西,然后发现被仙人掌叉掉. 然后就 ...

  5. 「CF446C」 DZY Loves Fibonacci Numbers

    「CF446C」 DZY Loves Fibonacci Numbers 这里提供一种优美的根号分治做法. 首先,我们考虑一种不太一样的暴力.对于一个区间加斐波那契数的操作 \([a,b]\),以及一 ...

  6. Django基础013--redis开发

    1.redis配置 在settings.py中加入以下代码块,可支持多个redis的配置 1 CACHES = { 2 "default": { 3 "BACKEND&q ...

  7. asp网页防止乱码

    <%@LANGUAGE="VBSCRIPT" CODEPAGE="65001"%> <%Session.CodePage=65001%> ...

  8. python 并行

    网址:http://www.parallelpython.com/ 下载模块[根据使用环境选择相应的下载版本]下载pp-1.6.4.4 .zip,注意不要下载md5 解压缩上面下载的文件:pp-1.6 ...

  9. java测试银行系统源代码

    1 package Kaoshi; 2 3 /*信1705-3 20173442 田昕可*/ 4 import java.util.*; 5 import java.io.*; 6 7 class A ...

  10. js学习笔记之日期倒计时DOM操作

    1.访问html元素 getElementById() 方法  返回对拥有指定 id 的第一个对象的引用,只有dom对象有效 getElementsByName() 方法  返回指定名称的对象集合 g ...