《微服务架构设计模式》第四章 使用Saga管理事务

news2024/9/30 15:21:48

内容总结自《微服务架构设计模式》

使用Saga管理事务

    • 一、XA解决方案存在问题
    • 二、使用Saga管理事务
      • Saga是什么
      • 补偿事务是什么
      • Saga协调模式
        • 协同式Saga
        • 编排式Saga
      • 隔离性
      • Saga结构
    • 三、总结

一、XA解决方案存在问题

在多个服务、数据库和消息代理之间维持数据一致性的传统方式是采用分布式事务。分布式事务管理的事实标准是X/Open Distributed Transaction Processing (DTP) Model。XA采用了两阶段提交(two phase commit,2PC)来保证事务中的所有参与方同时完成提交,或者在失败时同时回滚。应用程序的整个技术栈需要满足XA标准,包括符合XA要求的数据库、消息代理、数据库驱动、消息API,以及用来传播XA全局事务ID的进程间通信机制。市面上绝大多数的SQL数据库和一部分消息代理满足XA标准。例如,JaveEE应用程序可以使用JTA来完成分布式事务。

分布式事务并不像听起来这么简单,而是有着许多问题。其中一个问题是,许多新技术,包括NoSQL数据库(例如 MongoDB和 Cassandra),并不支持XA标准的分布式事务。同样,一些流行的消息代理如RabbitMQ和Apache Kafka也不支持分布式事务。因此,如果你坚持在微服务架构中使用分布式事务,那么不得不放弃使用这些流行的数据库或消息代理。



二、使用Saga管理事务

Saga是什么

传统的分布式事务管理方法对于现代应用程序来说不是一个好的选择。跨服务的操作必须使用所谓的Saga(一种消息驱动的本地事务序列)来维护数据一致性,而不是ACID事务。Saga的一个挑战在于只满足ACD(原子性、一致性和持久性)特性,而缺乏传统ACID事务的隔离性。因此,应用程序必须使用所谓的对策(countermeasure),找到办法来防止或减少由于缺乏隔离而导致的并发异常。

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


补偿事务是什么

传统ACID事务的一个重要特性是:如果业务逻辑检测到违反业务规则,可以轻松回滚事务。通过执行ROLLBACK语句,数据库可以撤销(回滚)目前为止所做的所有更改。遗憾的是,Saga无法自动回滚,因为每个步骤都会将其更改提交到本地数据库。这意味着,如果一个流程有六个步骤,如果第四个步失败,则应用程序必须明确撤销前三个步骤所做的更改。即必须编写所谓的补偿事务

假设一个Saga的第n+1个事务失败了。必须撤销前n个事务的影响。从概念上讲,每个步骤T,都有一个相应的补偿事务C,它可以撤销T的影响。要撤销前n个步骤的影响,Saga必须以相反的顺序执行每个C。步骤顺序如图

请添加图片描述


Saga协调模式

Saga的实现包含协调Saga步骤的逻辑。当通过系统命令启动Saga时,协调逻辑必须选择并通知第一个Saga参与方执行本地事务。一旦该事务完成,Saga协调选择并调用下一个Saga参与方。这个过程一直持续到Saga执行完所有步骤。如果任何本地事务失败,则 Saga必须以相反的顺序执行补偿事务。以下几种不同的方法可用来构建Saga的协调逻辑。

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

协同式Saga

使用协同时,没有一个中央协调器会告诉Saga参与方该做什么。相反,Saga参与方订阅彼此的事件并做出相应的响应。为了展示基于协同的Saga如何运作。

好处:

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

弊端:

  • 更难理解:与编排式不同,代码中没有一个单一地方定义了Saga。相反,协调式Saga的逻辑分布在每个服务的实现中。因此,开发人员有时很难理解特定的Saga是如何工作的。
  • 服务之间的循环依赖关系:Saga参与方订阅彼此的事件,这通常会导致循环依赖关系,比如A服务->B服务->A服务。虽然这并不一定是个问题,但循环依赖性被认为是一种不好的设计风格。
  • 紧耦合的风险:每个Saga参与方都需要订阅所有影响它们的事件。

编排式Saga

编排式是实现Saga的另外一种方式。当使用编排式Saga时,开发人员定义一个编排器类,这个类的唯一职责就是告诉Saga的参与方该做什么事情。Saga编排器使用命令/异步响应方式与Saga的参与方服务通信。为了完成Saga中的一个环节,编排器对某个参与方发出一个命令式的消息,告诉这个参与方该做什么操作。当参与方服务完成操作后,会给编排器发送一个答复消息。编排器处理这个消息,并决定Saga 的下一步操作是什么。

基于编排的Saga的每个步骤都包括一个更新数据库和发布消息的服务。一个 Saga参与方通过更新其数据库并发送回复消息,作为对编排器的响应。

好处:

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

弊端

  • 在编排器中存在集中过多业务逻辑的风险,为Saga实现协调逻辑只是你需要解决的设计问题之一
  • 处理缺乏隔离的问题也许是你在使用Saga时面临的最大挑战。

隔离性

缺乏隔离可能导致数据库异常( anomaly)。异常是一个数据库术语,指多个事务以某种方式(往往是并行)读取或写入数据产生的结果,与多个事务按顺序执行时的结果不同。当异常发生时,并行执行多个Saga的结果与串行执行的结果不同。

从表面上看,缺乏隔离听起来是个大问题。但实际上,开发人员往往在减少隔离性和获得更高性能之间权衡。关系型数据库允许你为每个事务指定隔离级别。默认的隔离级别通常是一个比完全隔离要弱一些的隔离级别,也称为可序列化事务。现实世界中的数据库事务通常与教科书中的ACID事务定义不同。

缺乏隔离性会导致的问题:

  1. 丢失更新
  2. 脏读
  3. 模糊或不可重复读

解决策略:

  1. 语义锁:应用程序级的锁。
  2. 交换式更新:把更新操作设计成可以按任何顺序执行。
  3. 悲观视图:重新排序Saga的步骤,以最大限度地降低业务风险。
  4. 重读值:通过重写数据来防止脏写,以在覆盖数据之前验证它是否保持不变。版本文件:将更新记录下来,以便可以对它们重新排序。
  5. 业务风险评级(by value):使用每个请求的业务风险来动态选择并发机制。

Saga结构

Saga模型中,一个Saga包含三种类型的事务

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

请添加图片描述


三、总结

  • 某些系统操作需要更新分散在多个服务中的数据。传统的基于XA/2PC的分布式事务不适合现代应用。更好的方法是使用Saga模式。Saga是使用消息机制协调的一组本地事务序列。每个本地事务都在单个服务中更新数据。由于每个本地事务都会提交更改,因此如果由于违反业务规则而导致Saga必须回滚,则必须执行补偿事务以显式撤销更改

  • 可以使用协同或编排来协调Saga的步骤。在基于协同的Saga 中,本地事务发布触发其他参与方执行本地事务的事件**。在基于编排的Saga中,集中式Saga编排器向参与方发送命令式消息,告诉它们执行本地事务。可以通过将Saga编排器建模为状态机来简化开发和测试。简单的Saga可以使用协同式,但编排式通常是复杂Saga的更好选择。**

  • 设计基于Saga 的业务逻辑可能具有挑战性,因为与ACID事务不同,Saga不是彼此孤立的。你必须经常使用各种对策,即防止ACD事务模型引起的并发异常的设计策略。应用甚至可能需要使用锁来简化业务逻辑,即使这会导致死锁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/711181.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小黑重庆归来,眼睛复查顺利,见到了三年没见的线上同门的leetcode之旅:剑指 Offer II 015. 字符串中的所有变位词

小黑代码1:滑动窗口 class Solution:def findAnagrams(self, s: str, p: str) -> List[int]:# 字符串长度n_s len(s)n_p len(p)if n_s < n_p:return []# 差值数组arr [0] * 26# 初始窗口for i in range(n_p):arr[ord(p[i])-97] - 1arr[ord(s[i])-97] 1# 计算初始窗口…

Web服务器群集:Tomcat配置https证书

目录 一、理论 1.SSL 2.HTTPS协议和HTTP协议的区别 3.https证书配置 4.tomcat强制使用https 二、实验 1.https证书配置过程 2.tomcat强制使用https 三、总结 一、理论 1.SSL &#xff08;1&#xff09;概念 SSL是网络加密传输协议&#xff0c;是支持在网络服务器(主…

chatgpt赋能python:烧录代码过程是怎样的

烧录代码过程是怎样的 烧录代码是将编写好的程序代码烧录进内置闪存器件&#xff08;Flash&#xff09;或外部存储器&#xff08;SD卡、EEPROM等&#xff09;中的过程。本文将介绍烧录代码的具体过程和常用工具&#xff0c;以及一些注意事项。 烧录代码的步骤 步骤一&#x…

分布式负载均衡 Ribbon

一、Ribbon简介 是Netfix发布的负载均衡&#xff0c;Eureka一般配合Ribbon进行使用&#xff0c;基于HTTP和TCP的客户端负载均衡工具。 只有负载均衡的能力&#xff0c;不具有发送请求的能力&#xff0c;要配合服务通信组件。 RestTemplate 针对各种类型的 HTTP 请求都提供了相…

青少年机器人技术一级考试备考重点(一):机器人常识

随着机器人技术的飞速发展&#xff0c;越来越多的青少年开始关注并参与其中。青少年机器人技术考试作为一项评估学生机器人技术水平的重要考试&#xff0c;备受广大青少年和家长的关注。为了更好地备战青少年机器人技术一级考试&#xff0c;了解考试的学习要点和备考重点是非常…

LeetCode-67. 二进制求和

LeetCode-67. 二进制求和 1、题目描述2、解题思路3、代码实现4、解题记录 ) 1、题目描述 题目描述&#xff1a; 给你两个二进制字符串 a 和 b &#xff0c;以二进制字符串的形式返回它们的和。 示例1&#xff1a; 输入:a “11”, b “1” 输出&#xff1a;“100” 示例2&…

kafka实现消息接受和发送

1、首先引入依赖 <dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId> </dependency> <dependency><groupId>org.springframework.kafka</groupId><artifactId>spr…

第九十五天学习记录:C++核心:类和对象Ⅳ(五星重要)

C对象模型和this指针 成员变量和成员函数分开存储 在C中&#xff0c;类内的成员变量和成员函数分开存储只有非静态成员变量才属于类的对象上 #include<iostream> using namespace std;class Person {int m_A;//非静态成员变量 属于类的对象上static int m_B;//静态成…

Gradio库的Gallery模块介绍与select方法详解

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

Flask boostrap实现图片视频上传下载展示

Flask boostrap实现图片视频上传下载展示 1、展示效果2、前端代码3、后端代码 1、展示效果 项目目录结构 2、前端代码 html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title>&l…

Go 程序是怎样跑起来的

Go 程序是怎样跑起来的 引入 我们从一个 helloworld 的例子开始 package mainimport "fmt"func main() {fmt.Println("hello world") }用 vim 要打开&#xff0c;输入命令&#xff1a; :%!xxd下面是输出 00000000:7061 636b 6167 6520 6d61 696e 0a0a…

tomcat概述,优化,多实例部署

目录 一、概述 二、三个容器 1、Web 容器&#xff1a; 2、Servlet 容器&#xff1a; 3、JSP 容器&#xff1a; 三、Tomcat 功能组件结构 四、优化 1、启动速度优化 2、配置参数优化 五、多实例部署 一、概述 Tomcat 是 Java 语言开发的&#xff0c;Tomcat 服务器是一…

ubuntu下安装docker遇到的问题

如果你还没有安装虚拟机&#xff0c;推荐一篇关于安装Ubuntu的详细教程&#xff1a; VMware虚拟机安装Ubuntu20.04详细图文教程https://blog.csdn.net/weixin_41805734/article/details/120698714首先&#xff0c;安装docker的前提是虚拟机能够联网&#xff0c;如果能看到右上…

官方文档中docker安装php插件xdebug

docker安装php插件 直接上代码常见问题如果查看系统类型如何查看xdebug合适的版本安装异常提示Cannot find autoconf. Please check your autoconf installation and the$PHP_AUTOCONF environment variable. Then, rerun this script.configure: error: in /tmp/pear/temp/pea…

Zookeeper 分布式锁

优质博文&#xff1a;IT-BLOG-CN 一、简介 随着公司业务的发展&#xff0c;单机应用已经无法支撑现有的用户量&#xff0c;之前采用synchronized和Lock锁已经无法满足分布式系统的要求。我们应用程序目前都会运行120台&#xff0c;节假日会扩容至240台&#xff0c;属于多JVM环…

领域事件驱动(二)聚合与聚合根的了解

上一章对值对象以及实体进行了一些简单的讲解&#xff1a; 聚合 聚合&#xff1a;我们把一些关联性极强、生命周期一致的实体、值对象放到一个聚合里。 聚合有一个聚合根和上下文边界&#xff0c;这个边界根据业务单一职责和高内聚原则&#xff0c;定义了聚合内部应该包含哪…

U-Boot移植 - 2_环境搭建和u-boot烧录启动

文章目录 1. 编译环境搭建1.1 交叉编译器下载1.2 交叉编译器安装 2. 编译原厂uboot3. 烧录开发板3.1 烧录到SD卡3.2 启动开发板 1. 编译环境搭建 1.1 交叉编译器下载 嵌入式Linux开发&#xff0c;程序编译通常在电脑端的Linux&#xff08;如虚拟机中的Ubuntu)下进行编译&…

阿里云ECS部署

nginx 安装nginx # 查看dnf版本 dnf --version# 查找是否是否安装 dnf search nginx# 安装nginx dnf install nginx# 启动nginx systemctl start nginx# 查看nginx运行状态 systemctl status nginx# 相当于开机自启&#xff08;重启服务器&#xff0c;nginx自动启动&#xff…

GoLand下载、安装

一、Goland下载 官方最新版本下载地址&#xff1a; Download GoLand: A Go IDE with extended support for JavaScript, TypeScript, and databases 其他版本下载&#xff1a; Other Versions - GoLand 二、安装过程 1.下载好goland-2021.1.1安装包后&#xff0c;双击运行安装包…

【ARIMA-WOA-CNN-LSTM】合差分自回归移动平均方法-鲸鱼优化-卷积神经网络-长短期记忆神经网络研究(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…