分布式事务最经典的七种解决方案

2023-05-16

随着业务的快速发展、业务复杂度越来越高,几乎每个公司的系统都会从单体走向分布式,特别是转向微服务架构。随之而来就必然遇到分布式事务这个难题,这篇文章总结了分布式事务最经典的解决方案,分享给大家。

基础理论

在讲解具体方案之前,我们先了解一下分布式事务所涉及到的基础理论知识。

我们拿转账作为例子,A需要转100元给B,那么需要给A的余额-100元,给B的余额+100元,整个转账要保证,A-100和B+100同时成功,或者同时失败。看看在各种场景下,是如何解决这个问题的。

事务

把多条语句作为一个整体进行操作的功能,被称为数据库事务。数据库事务可以确保该事务范围内的所有操作都可以全部成功或者全部失败。

事务具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特性。

  • Atomicity(原子性):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复到事务开始前的状态,就像这个事务从来没有执行过一样。
  • Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。完整性包括外键约束、应用定义的等约束不会被破坏。
  • Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
  • Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

分布式事务

银行跨行转账业务是一个典型分布式事务场景,假设A需要跨行转账给B,那么就涉及两个银行的数据,无法通过一个数据库的本地事务保证转账的ACID,只能够通过分布式事务来解决。

分布式事务就是指事务的发起者、资源及资源管理器和事务协调者分别位于分布式系统的不同节点之上。在上述转账的业务中,用户A-100操作和用户B+100操作不是位于同一个节点上。本质上来说,分布式事务就是为了保证在分布式场景下,数据操作的正确执行。

分布式事务在分布式环境下,为了满足可用性、性能与降级服务的需要,降低一致性与隔离性的要求,一方面遵循 BASE 理论(BASE相关理论,涉及内容非常多,感兴趣的同学,可以参考BASE理论):

  • 基本业务可用性(Basic Availability)
  • 柔性状态(Soft state)
  • 最终一致性(Eventual consistency)

同样的,分布式事务也部分遵循 ACID 规范:

  • 原子性:严格遵循
  • 一致性:事务完成后的一致性严格遵循;事务中的一致性可适当放宽
  • 隔离性:并行事务间不可影响;事务中间结果可见性允许安全放宽
  • 持久性:严格遵循

分布式事务的解决方案

两阶段提交/XA

XA是由X/Open组织提出的分布式事务的规范,XA规范主要定义了(全局)事务管理器(TM)和(局部)资源管理器(RM)之间的接口。本地的数据库如mysql在XA中扮演的是RM角色

XA一共分为两阶段:

第一阶段(prepare):即所有的参与者RM准备执行事务并锁住需要的资源。参与者ready时,向TM报告已准备就绪。
第二阶段 (commit/rollback):当事务管理者(TM)确认所有参与者(RM)都ready后,向所有参与者发送commit命令。

目前主流的数据库基本都支持XA事务,包括mysql、oracle、sqlserver、postgre

XA 事务由一个或多个资源管理器(RM)、一个事务管理器(TM)和一个应用程序(ApplicationProgram)组成。

把上面的转账作为例子,一个成功完成的XA事务时序图如下:

如果有任何一个参与者prepare失败,那么TM会通知所有完成prepare的参与者进行回滚。

XA事务的特点是:

简单易理解,开发较容易
对资源进行了长时间的锁定,并发度低

如果读者想要进一步研究XA,go语言可参考DTM,java语言可参考seata

SAGA

Saga是这一篇数据库论文saga提到的一个方案。其核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。

把上面的转账作为例子,一个成功完成的SAGA事务时序图如下:

SAGA事务的特点:

并发度高,不用像XA事务那样长期锁定资源
需要定义正常操作以及补偿操作,开发量比XA大
一致性较弱,对于转账,可能发生A用户已扣款,最后转账又失败的情况

论文里面的SAGA内容较多,包括两种恢复策略,包括分支事务并发执行,我们这里的讨论,仅包括最简单的SAGA

SAGA适用的场景较多,长事务适用,对中间结果不敏感的业务场景适用

如果读者想要进一步研究SAGA,go语言可参考DTM,java语言可参考seata

TCC

关于 TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。

TCC分为3个阶段

Try 阶段:尝试执行,完成所有业务检查(一致性), 预留必须业务资源(准隔离性)
Confirm 阶段:确认执行真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源,Confirm 操作要求具备幂等设计,Confirm 失败后需要进行重试。
Cancel 阶段:取消执行,释放 Try 阶段预留的业务资源。Cancel 阶段的异常和 Confirm 阶段异常处理方案基本上一致,要求满足幂等设计。

把上面的转账作为例子,通常会在Try里面冻结金额,但不扣款,Confirm里面扣款,Cancel里面解冻金额,一个成功完成的TCC事务时序图如下:

TCC特点如下:

并发度较高,无长期资源锁定。
开发量较大,需要提供Try/Confirm/Cancel接口。
一致性较好,不会发生SAGA已扣款最后又转账失败的情况

TCC适用于订单类业务,对中间状态有约束的业务

如果读者想要进一步研究TCC,go语言可参考DTM,java语言可参考seata

本地消息表

本地消息表这个方案最初是 ebay 架构师 Dan Pritchett 在 2008 年发表给 ACM 的文章。设计核心是将需要分布式处理的任务通过消息的方式来异步确保执行。

大致流程如下:

写本地消息和业务操作放在一个事务里,保证了业务和发消息的原子性,要么他们全都成功,要么全都失败。

容错机制:

扣减余额事务 失败时,事务直接回滚,无后续步骤
轮序生产消息失败, 增加余额事务失败都会进行重试

本地消息表的特点:

长事务仅需要分拆成多个任务,使用简单
生产者需要额外的创建消息表
每个本地消息表都需要进行轮询
消费者的逻辑如果无法通过重试成功,那么还需要更多的机制,来回滚操作

适用于可异步执行的业务,且后续操作无需回滚的业务

事务消息

在上述的本地消息表方案中,生产者需要额外创建消息表,还需要对本地消息表进行轮询,业务负担较重。阿里开源的RocketMQ 4.3之后的版本正式支持事务消息,该事务消息本质上是把本地消息表放到RocketMQ上,解决生产端的消息发送与本地事务执行的原子性问题。

事务消息发送及提交:

  • 发送消息(half消息)
  • 服务端存储消息,并响应消息的写入结果
  • 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)
  • 根据本地事务状态执行Commit或者Rollback(Commit操作发布消息,消息对消费者可见)

正常发送的流程图如下:

补偿流程:

  • 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”
  • Producer收到回查消息,返回消息对应的本地事务的状态,为Commit或者Rollback

事务消息方案与本地消息表机制非常类似,区别主要在于原先相关的本地表操作替换成了一个反查接口

事务消息特点如下:

长事务仅需要分拆成多个任务,并提供一个反查接口,使用简单
消费者的逻辑如果无法通过重试成功,那么还需要更多的机制,来回滚操作

适用于可异步执行的业务,且后续操作无需回滚的业务

如果读者想要进一步研究事务消息,可参考rocketmq,为了方便大家学习事务消息,dtm也提供了简单实现

最大努力通知

发起通知方通过一定的机制最大努力将业务处理结果通知到接收方。具体包括:

有一定的消息重复通知机制。因为接收通知方可能没有接收到通知,此时要有一定的机制对消息重复通知。
消息校对机制。如果尽最大努力也没有通知到接收方,或者接收方消费消息后要再次消费,此时可由接收方主动向通知方查询消息信息来满足需求。

前面介绍的的本地消息表和事务消息都属于可靠消息,与这里介绍的最大努力通知有什么不同?

可靠消息一致性,发起通知方需要保证将消息发出去,并且将消息发到接收通知方,消息的可靠性关键由发起通知方来保证

最大努力通知,发起通知方尽最大的努力将业务处理结果通知为接收通知方,但是可能消息接收不到,此时需要接收通知方主动调用发起通知方的接口查询业务处理结果,通知的可靠性关键在接收通知方

解决方案上,最大努力通知需要:

提供接口,让接受通知放能够通过接口查询业务处理结果
消息队列ACK机制,消息队列按照间隔1min、5min、10min、30min、1h、2h、5h、10h的方式,逐步拉大通知间隔 ,直到达到通知要求的时间窗口上限。之后不再通知

最大努力通知适用于业务通知类型,例如微信交易的结果,就是通过最大努力通知方式通知各个商户,既有回调通知,也有交易查询接口

AT事务模式

这是阿里开源项目seata中的一种事务模式,在蚂蚁金服也被称为FMT。优点是该事务模式使用方式,类似XA模式,业务无需编写各类补偿操作,回滚由框架自动完成,缺点也类似AT,存在较长时间的锁,不满足高并发的场景。有兴趣的同学可以参考seata-AT

分布式事务中的网络异常

在分布式事务的各个环节都有可能出现网络以及业务故障等问题,这些问题需要分布式事务的业务方做到防空回滚,幂等,防悬挂三个特性,下面以TCC事务说明这些异常情况:

空回滚:

  在没有调用 TCC 资源 Try 方法的情况下,调用了二阶段的 Cancel 方法,Cancel 方法需要识别出这是一个空回滚,然后直接返回成功。

  出现原因是当一个分支事务所在服务宕机或网络异常,分支事务调用记录为失败,这个时候其实是没有执行Try阶段,当故障恢复后,分布式事务进行回滚则会调用二阶段的Cancel方法,从而形成空回滚。

幂等:

  由于任何一个请求都可能出现网络异常,出现重复请求,所以所有的分布式事务分支,都需要保证幂等性

悬挂:

  悬挂就是对于一个分布式事务,其二阶段 Cancel 接口比 Try 接口先执行。

  出现原因是在 RPC 调用分支事务try时,先注册分支事务,再执行RPC调用,如果此时 RPC 调用的网络发生拥堵,RPC 超时以后,TM就会通知RM回滚该分布式事务,可能回滚完成后,RPC 请求才到达参与者真正执行。

下面看一个网络异常的时序图,更好的理解上述几种问题

  1. 业务处理请求4的时候,Cancel在Try之前执行,需要处理空回滚
  2. 业务处理请求6的时候,Cancel重复执行,需要幂等
  3. 业务处理请求8的时候,Try在Cancel后执行,需要处理悬挂

面对上述复杂的网络异常情况,目前看到各家建议的方案都是业务方通过唯一键,去查询相关联的操作是否已完成,如果已完成则直接返回成功。相关的判断逻辑较复杂,易出错,业务负担重。

在项目dtm中,出现了一种子事务屏障技术,使用该技术,能够达到这个效果,看示意图:

所有这些请求,到了子事务屏障后:不正常的请求,会被过滤;正常请求,通过屏障。开发者使用子事务屏障之后,前面所说的各种异常全部被妥善处理,业务开发人员只需要关注实际的业务逻辑,负担大大降低。

子事务屏障提供了方法ThroughBarrierCall,方法的原型为:

func ThroughBarrierCall(db *sql.DB, transInfo *TransInfo, busiCall BusiFunc)

业务开发人员,在busiCall里面编写自己的相关逻辑,调用该函数。ThroughBarrierCall保证,在空回滚、悬挂等场景下,busiCall不会被调用;在业务被重复调用时,有幂等控制,保证只被提交一次。

子事务屏障会管理TCC、SAGA、XA、事务消息等,也可以扩展到其他领域

子事务屏障技术,为DTM首创,它的意义在于设计简单易实现的算法,提供了简单易用的接口,在首创,它的意义在于设计简单易实现的算法,提供了简单易用的接口,在这两项的帮助下,开发人员彻底的从网络异常的处理中解放出来。

该技术目前需要搭配DTM事务管理器,目前SDK已经提供给go语言的开发者。其他语言的sdk正在规划中。对于其他的分布式事务框架,只要提供了合适的分布式事务信息,能够按照上述原理,快速实现该技术。

总结

本文介绍了分布式事务的一些基础理论,并对常用的分布式事务方案进行了讲解,在文章的后半部分还给出了事务异常的原因、分类以及优雅的解决方案。

分布式事务本身是一个技术难题,业务中具体使用哪种方案还是需要根据自身业务特点自行选择。现在有了dtm,能够极大简化分布式事务的开发,大大降低了分布式事务的门槛。文末放上链接,欢迎大家star、提issue指教

https://github.com/yedf/dtm

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

分布式事务最经典的七种解决方案 的相关文章

  • 浏览器中Local Storage和Session Storage的区别

    Local Storage xff1a 数量空间没有限制 xff0c 只要硬盘空间较大 xff0c 可以一直存 Session Storage xff1a 是一个会话存储对象 xff0c 在Session Storage中保存的数据只能在同
  • win10 MongoDB启动失败 Error: couldn't connect to server 127.0.0.1:27017, connection attempt faile

    1 我的MongoDB安装路径 2 创建配置文件mongo conf xff0c 文件内容如下 xff1a 数据库路径 dbpath 61 d MongoDB Server 3 4 data 日志输出文件路径 logpath 61 d Mo
  • "docker build"requires exactly 1 argument

    例如运行 xff1a docker build t xc govern center 1 0 SNAPSHOT 后面的这个 前面是有个空格的
  • PyCharm 快捷键

    ctrl 43 alt 43 s xff1a 打开软件设置 ctrl 43 d xff1a 复制当前行代码 shift 43 alt 43 上 下 xff1a 将当前行代码上移或下移 ctrl 43 shift 43 f10 xff1a 运
  • Python进阶

    1 Ubuntu操作系统 Linux主要目录 xff1a xff1a 根目录 bin xff1a 可执行二进制文件的目录 ect xff1a 系统配置文件存放的目录 home xff1a 用户家目录 2 linux命令 查看目录命令 命令说
  • Python+tkinter应用程序设置背景图片

    功能描述 xff1a 设计tkinter应用程序 xff0c 为窗口和组件设置背景图片 参考代码 xff1a 运行效果 xff1a 董付国老师Python系列图书 友情提示 xff1a 不建议购买太多 xff0c 最好先通过京东 当当 天猫
  • git学习

    1 git和svn的区别 svn是集中式版本控制系统 xff0c 版本库是集中放在中央服务器的 xff0c 而工作的时候 xff0c 用的都是自己的电脑 xff0c 所以首先要从中央服务器得到最新的版本 xff0c 然后工作 xff0c 完
  • git常用命令

    1 git下载完后 xff0c 必须要设置签名 xff0c 否则无法提交代码 2 C Users 12131 gitconfig 文件里可以查看到设置的信息 3 签名的作用 xff1a 区分不同操作者身份 xff0c 用户的签名信息在每一个
  • GitHub上 README 增加图片标签

    hey Guys 你可能遇到的GitHub上好的项目都有一个非常棒的README xff0c 其中不乏用到一些非常好看的标签 比如下面这样 xff1a walle fastjson 那我们怎样自己添加一个高大上图片标签呢 xff1f 比如我
  • 树莓派上使用 Gstreamer做视频推流

    树莓派使用Gstreamer进行视频推流 最近在研究如何使用树莓派进行视频推流推送到云端 进行了各种比较尝试后 xff0c 认为使用Gstreamer比较好 xff0c 主要的一点就是想利用Gstreamer的硬件加速 在使用ffmpeg的
  • geoserver jms集群部署

    1 集群节点运行架构图 官网集群介绍 xff1a https docs geoserver org latest en user community jms cluster index html 节点之间通过jms消息通信 xff0c A节
  • linux smb配置 不修改git权限

    root 64 localhost cat etc samba smb conf map archive 61 no map hidden 61 no map read only 61 no map system 61 no store d
  • luogu P1185 绘制二叉树

    题目大意 绘制一棵给定的二叉树 解题思路 模拟即可 代码比较乱 include lt cstdio gt include lt cstdlib gt include lt cstring gt const int MAXN 61 12 第i
  • shell 自动化运维

    1 shell 基础知识 1 shell是一个程序 xff0c 文件路径 xff1a bin bash xff0c 是一个命令解释器 xff0c 所有的linux命令都由它来执行 打开终端 xff0c 就进入了shell交互式命令 2 sh
  • 递归求鸭子数

    一个人赶着鸭子去每个村庄卖 xff0c 每经过一个村子卖去所赶鸭子的一半又一只 这样他经过了七个村子后还剩两只鸭子 xff0c 问他出发时共赶多少只鸭子 xff1f 经过每个村子卖出多少只鸭子 xff1f span style color
  • 【STM32】基于STM32F407寄存器方式点亮LED流水灯

    目录 一 STM32F4寄存器介绍二 通过寄存器方式点亮流水灯1 硬件设计2 软件设计3 烧录验证 三 原理阐述1 使能IO口时钟2 初始化IO口模式3 操作IO口 xff0c 输出高低电平 四 总结五 参考 本文使用 原子STM32F40
  • Python+tkinter+pillow实现屏幕任意区域截图

    基本思路 xff1a 首先获取并显示全屏幕截图 xff0c 然后在全屏幕截图上响应鼠标左键按下和抬起事件 xff0c 最后进行二次截图 import tkinter import tkinter filedialog import os f
  • iOS瀑布流

    WaterFallFlow 瀑布流Demo 使用UICollectionView实现瀑布流 自定义UICollectionViewLayout中的主要代码 xff1a YJWaterFlowLayout h中代码 span class hl
  • RedHat8 服务安装(编译、rpm、dnf)

    安装软件的三种方式 下载源码编译安装 可以实现自定义安装目录和参数调整可以指定版本安装在其他2个方法无法安装的情况下可以编译安装 xff08 解决兼容性 xff09 需要自己解决依赖关系 下载rpm包安装 需要下载对应系统的rpm包 xff
  • OpenStack octavia LB负载均衡基础

    octavia octavia 作为openstack的负载均衡方案 xff0c 实现4层和7层的负载 xff0c 自Pike版本替换了neutron自带的LB方案 xff08 Neutorn LBaaS xff09 基本对象 loadba

随机推荐

  • DoraOS连接Proxmox VE搭建简单桌面云

    最近公司想换桌面云 xff0c 我就说想搭建一个Proxmox VE环境 xff0c 问我为什么要用Proxmox 简单说就是好用 xff0c 相对于Xendesktop和horizon xff0c 以及各种厂商的VDI解决方案 xff0c
  • DoraCloud for Proxmox桌面云上启用NVIDIA Tesla P4的vGPU功能

    Proxmox virtualization environment xff0c 简称PVE xff0c 是一个开源免费的基于linux的企业级虚拟化方案 xff0c 功能不输专业收费的VMware 简单的说 xff0c PVE是一个基于D
  • 【meta-learning】经典工作MAML和Reptile(demo理解meta-learning机制)

    Meta Learning MAML ICML 17 MAML和模型结构 任务无关 xff0c 唯一的要求只是模型有参数就可以 MAML产生一个权重的初始化 xff0c 其他模型再使用少量的样本就能在其基础上进行fine tuning 因此
  • 语音识别功能集成总结

    一 场景 业务需求 xff0c 需要集成语音识别功能 xff0c 供移动端使用 二 调研 经过初步了解 xff0c 决定集成国内主流的三家厂商 xff0c 科大讯飞 百度 腾讯 讯飞不用说 xff0c 在语音识别这块之前被评为全球最聪明的5
  • 技巧1 python|django接收参数数据类型转换(数组-列表,对象-字典,json)举例用法

    1 接收的参数是中文乱码 xff08 E7等 xff09 django 中 xff1a 引入quote 引入后红线处点击红色小灯 xff0c 点安装 span class token keyword from span urllib spa
  • WEEK4 二分与贪心

    DDL的恐惧 xff1a 题干 xff1a ZJM 有 n 个作业 xff0c 每个作业都有自己的 DDL xff0c 如果 ZJM 没有在 DDL 前做完这个作业 xff0c 那么老师会扣掉这个作业的全部平时分 所以 ZJM 想知道如何安
  • Ubuntu中的dpkg命令

    dpkg是Debian Package的缩写 xff0c 是专门为Debian开发的软件包管理系统 xff0c 方便安装 管理 更新 近来在安装chrome的时候 xff0c 一开始使用sudo apt get install命令 xff0
  • 分布式事务实战--go语言的saga事务

    我们团队在引入go语言做微服务的过程中 xff0c 遇见了分布式事务的强需求 我们的交易中心涉及大量的业务 xff0c 包括了商品 库存 各类营销活动 商品权限等等 xff0c 按照我们微服务的设计 xff0c 需要拆分到多个微服务 原先由
  • openstack安装故障排除经验

    1 OpenStack的部署过程遇到的问题可归纳总结为配置文件问题 配置步骤缺失 等 2 故障通常有以下几种情况 xff1a xff08 1 xff09 时间同步问题 xff0c 两 xff08 多 xff09 个节点间时间不同步 xff0
  • 【已解决】 Windows11安装WindowsSubsystemForAndroid时报错VClibs错误

    管理员终端输入以下代码安装 xff0c WindowsSubsystemForAndroid add Appxpackage 34 MicrosoftCorporationII WindowsSubsystemForAndroid 1 7
  • SQL解析引擎Apache Calcite

    1 什么是Apache Calcite Apache Calcite 是一款开源SQL解析工具 可以将各种SQL语句解析成抽象语法术AST Abstract Syntax Tree 之后通过操作AST就可以把SQL中所要表达的算法与关系体现
  • C# 使用Selenium

    一 介绍 xff1a Selenium 是一个用于Web应用程序测试的工具 Selenium测试直接运行在浏览器中 xff0c 就像真正的用户在操作一样 1 Selenium Webdriver xff08 也就是Selenium2 xff
  • CentOS 7关闭与启用图形化界面记录

    1 关闭图形界面命令 systemctl set default multi user target 2 查看启动模式 systemctl get default 显示 graphical targe 则代表是 图形化界面模式 显示 mul
  • ubuntu下conda虚拟环境的操作,cuda,cudnn版本的查询, pytorch的安装

    一 ubuntu下conda虚拟环境的操作 随着深度学习的发展 xff0c tensorflow keras pytorch等深度学习框架的兴起和发展 xff0c 或者多用户的使用情况 xff0c 使得在ubuntu下我们可能需要安装多个深
  • dos 设置环境变量

    1 查看环境变量 echo path 2 设置环境变量 set path 61 path C your path
  • H264--2--语法及结构

    名词解释 场和帧 xff1a 视频的一场或一帧可用来产生一个编码图像 在电视中 xff0c 为减少大面积闪烁现象 xff0c 把一帧分成两个隔行的场 片 xff1a 每个图象中 xff0c 若干宏块被排列成片的形式 片分为I片 B片 P片和
  • 强化练习6:判断一字符串是否为回文,是返回1,不是返回0,出错返回-1

    题目 xff1a 判断一字符串是否为回文 xff0c 是返回1 xff0c 不是返回0 xff0c 出错返回 1 程序如下 xff1a include lt stdio h gt int fun char p if p 61 61 NULL
  • debian 服务器安装图形界面

    本人由于习惯了Ubuntu的图形界面 xff0c 实际上 呢 xff0c 是被Windows给带坏了 虽然全 控制台 很流弊 xff0c 但看着还是很不舒服 xff0c 所以就想着 安装 一个图形界面 其实很简单的说 xff0c 就是几行命
  • 如何在Ubuntu 20.04 上安装 Xrdp 服务器(远程桌面)

    本文最先发布在 xff1a https www itcoder tech posts how to install xrdp on ubuntu 20 04 Xrdp 是一个微软远程桌面协议 xff08 RDP xff09 的开源实现 xf
  • 分布式事务最经典的七种解决方案

    随着业务的快速发展 业务复杂度越来越高 xff0c 几乎每个公司的系统都会从单体走向分布式 xff0c 特别是转向微服务架构 随之而来就必然遇到分布式事务这个难题 xff0c 这篇文章总结了分布式事务最经典的解决方案 xff0c 分享给大家