刚性事务:XA模型、XA接口规范、XA实现
XA模型或者 X/Open DTP模型
X/OPEN是一个组织.X/Open国际联盟有限公司是一个欧洲基金会,它的建立是为了向UNIX环境提供标准。它主要的目标是促进对UNIX语言、接口、网络和应用的开放式系统协议的制定。它还促进在不同的UNIX环境之间的应用程序的互操作性,以及支持对电气电子工程师协会(IEEE)对UNIX的可移植操作系统接口(POSIX)规范。
X/Open DTP(Distributed Transaction Process) 是一个分布式事务模型。这个模型主要使用了两段提交(2PC - Two-Phase-Commit)来保证分布式事务的完整性。
在X/Open DTP(Distributed Transaction Process) 模型里面,有三个角色:
AP: Application,应用程序。也就是业务层。哪些操作属于一个事务,就是AP定义的。
TM: Transaction Manager,事务管理器。接收AP的事务请求,对全局事务进行管理,管理事务分支状态,协调RM的处理,通知RM哪些操作属于哪些全局事务以及事务分支等等。这个也是整个事务调度模型的核心部分。
RM:Resource Manager,资源管理器。一般是数据库,也可以是其他的资源管理器,如消息队列(如JMS数据源),文件系统等。
XA把参与事务的角色分成AP,RM,TM。
AP,即应用,也就是我们的业务服务。
RM指的是资源管理器,即DB,MQ等。
TM则是事务管理器。
其中,AP自己操作TM,当需要事务时,AP向TM请求发起事务,TM负责整个事务的提交,回滚等。
XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接口。XA接口是双向的系统接口,在事务管理器(Transaction Manager)以及一个或多个资源管理器(Resource Manager)之间形成通信桥梁。
XA规范的核心作用和 XA 规范的本质是定义了 TM 和 RM 之间的通信接口,让分布式环境下的 “跨资源事务” 有了标准化的协调机制。它是实现 2PC(两阶段提交) 的基础(很多 2PC 方案都是基于 XA 规范实现的),通过 “中心化协调” 保证事务的原子性和一致性。
详细讲一下上图的角色间的协作流程
步骤 1:AP 使用多个 RM 的资源
业务服务(AP)需要操作多个资源(如同时操作 “订单数据库” 和 “库存数据库”),这些资源由各自的 RM 管理。
步骤 2:AP 通过 TX 接口定义事务边界
AP 向 TM 发起事务请求,定义 “事务从哪里开始、到哪里结束”(即事务的边界)。
步骤 3:TM 与 RMs 交换事务信息
TM 作为协调者,与每个 RM 通信,收集它们的操作状态(如 “订单库已准备提交”“库存库执行失败”),最终决定全局事务是 “提交” 还是 “回滚”,并通知所有 RM 执行对应操作。
为什么需要TM呢?
分布式系统中,多节点无法 “自发” 达成一致(理论上两台机器无法保证绝对同步),因此必须引入 单点协调者(TM)来统一控制事务流程 ——TM 负责收集所有 RM 的状态,最终决策事务的提交 / 回滚,避免 “部分资源提交、部分回滚” 的不一致情况。
XA规范
什么是XA
- XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。
- XA 规范 描述了全局的事务管理器与局部的资源管理器之间的接口。 XA规范 的目的是允许的多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使 ACID 属性跨越应用程序而保持有效。
- XA 规范 使用两阶段提交(2PC,Two-Phase Commit)协议来保证所有资源同时提交或回滚任何特定的事务。
- XA 规范 在上世纪 90 年代初就被提出。目前,几乎所有主流的数据库都对 XA 规范 提供了支持。
这台头疼了,最简单的来说:
XA 规范由 X/Open 组织(后并入 The Open Group)提出的分布式事务处理规范,旨在解决跨多个资源的分布式事务一致性问题,它规范了 TM 与 RM 之间的通信接口,在 TM 与多个RM之间形成一个双向通信桥梁,从而在多个数据库资源下保证ACID四个特性。
XA 规范的核心是XA是保证所有资源的操作要么全成功,要么全失败,它是数据库的分布式事务,拥有强一致性,在整个过程中,数据一张锁住状态,即从 prepare 到 commit、rollback 的整个过程中,TM一直把持着数据库的锁,如果有其他人要修改数据库的该条数据,就必须等待锁的释放,存在长事务风险。
它是实现两阶段提交(2PC)等强一致性分布式事务的核心理论基础。
以下的函数使事务管理器可以对资源管理器进行的操作:
xa_open,xa_close:建立和关闭与资源管理器的连接。xa_start,xa_end:开始和结束一个本地事务。xa_prepare,xa_commit,xa_rollback:预提交、提交和回滚一个本地事务。xa_recover:回滚一个已进行预提交的事务。ax_reg,ax_unreg:允许一个资源管理器在一个TMS(TRANSACTION MANAGER SERVER)中动态注册或撤消注册。ax_开头的函数使资源管理器可以动态地在事务管理器中进行注册,并可以对XID(TRANSACTION IDS)进行操作。
XA协议的各个阶段的处理流程如下
这张图展示了 XA 协议的两阶段提交(2PC)流程
准备阶段(Vote Phase)—— 确保所有资源 “可提交”
这一阶段的核心是TM 协调所有 RM 执行本地操作但不提交,并收集它们的 “可提交状态”。
AP 发起全局事务
业务应用(AP)向事务管理器(TM)发起 “全局事务请求”,标志着分布式事务的开始。
TM 与 RM 初始化分支事务
xa_reg():TM 向 RM “注册分支事务”,将当前全局事务与 RM 的本地资源绑定(每个 RM 的操作都是全局事务的一个 “分支”)。xa_open():TM 向 RM 发起 “打开连接” 请求,建立与资源(如数据库)的通信通道。xa_start():TM 向 RM 发送 “标识分支事务开始” 的指令,RM 开始记录本地事务的日志(如 undo log、redo log)。
AP 执行数据变更操作
AP 直接向 RM 发起 “变更数据操作”(如数据库的增删改),RM 在本地执行这些操作,但不提交(此时数据处于 “临时生效” 状态)。
TM 标记分支事务结束并发起准备请求
xa_end():TM 向 RM 发送 “标识分支事务结束” 的指令,RM 停止接收新的操作请求,准备进入 “提交 / 回滚决策” 阶段。xa_prepare():TM 向 RM 发送 “准备提交事务” 的指令,RM 执行本地事务的 “预提交校验”(如检查数据完整性、资源锁状态),并向 TM 反馈 “可提交(Ready)” 或 “不可提交(Abort)”。
提交 / 回滚阶段(Commit/Rollback Phase)—— 全局决策与执行
这一阶段的核心是TM 根据所有 RM 的准备状态,决定全局事务 “提交” 或 “回滚”,并通知所有 RM 执行最终操作。
若所有 RM 反馈 “可提交”:执行提交流程
xa_commit():TM 向所有 RM 发送 “提交事务” 的指令,RM 执行本地事务的最终提交(如数据库执行commit,消息队列确认消息发送),数据从 “临时生效” 变为 “永久生效”。xa_close():TM 向 RM 发送 “关闭连接” 的指令,释放与资源的通信通道。xa_unreg():RM 向 TM 反馈 “退出全局事务”,标志着整个分布式事务的提交流程完成。
若任一 RM 反馈 “不可提交”:执行回滚流程
(图中未展示回滚分支,但逻辑是:TM 向所有 RM 发送
xa_rollback()指令,RM 通过 undo log 等机制回滚本地操作,恢复数据到事务前状态;之后执行xa_close()和xa_unreg(),终止事务。)
可以看出,XA 协议的本质是 “先统一询问,再统一执行”:
XA协议的实现仅与上述的流程有部分区别
XA协议的实现
2PC/3PC协议
两阶段提交(2PC)协议是XA规范定义的 数据一致性协议。
三阶段提交(3PC)协议对 2PC协议的一种扩展。
Seata
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式
在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。商业化产品GTS 先后在阿里云、金融云进行售卖
而且Seata中的 Seata AT 模式是增强型2pc模式
AT 模式: 两阶段提交协议2PC的演变,没有一直锁表
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源
- 二阶段:提交异步化,非常快速地完成。或回滚通过一阶段的回滚日志进行反向补偿
JTA规范
作为 Java 平台上事务规范 JTA(Java Transaction API)也定义了对XA事务的支持
在 Java 技术栈中,XA 规范的典型落地是 JTA(Java Transaction
API),它是 XA 规范的 Java 实现。在 Spring 应用中,可通过
@Transactional 注解结合 Atomikos 实现 XA
分布式事务,让跨库操作自动参与全局事务的两阶段提交。
实际上,JTA是基于XA架构上建模的,在JTA中,事务管理器抽象为
Javax.transaction.TransactionManager
接口,并通过底层事务服务(即JTS)实现。像很多其他的 Java
规范一样,JTA仅仅定义了接口,具体的实现则是由供应商(如J2EE厂商)负责提供,目前JTA的实现主要由以下几种:
- J2EE容器所提供的JTA实现(JBoss)
- 独立的JTA实现:如JOTM,Atomikos.
这些实现可以应用在那些不使用J2EE应用服务器的环境里用以提供分布事事务保证。如Tomcat,Jetty以及普通的java应用。
JTS规范
事务是编程中必不可少的一项内容,基于此,为了规范事务开发,Java增加了关于事务的规范,即JTA和JTS
JTA定义了一套接口,其中约定了几种主要的角色:TransactionManager、UserTransaction、Transaction、XAResource,并定义了这些角色之间需要遵守的规范,如Transaction的委托给TransactionManager等。
JTS也是一组规范,上面提到JTA中需要角色之间的交互,那应该如何交互?JTS就是约定了交互细节的规范。
JTS(Java Transaction Service,Java 事务服务)是Java 平台上用于处理分布式事务的底层规范,是 JTA(Java Transaction API)的底层实现,它遵循 CORBA 的 “对象事务服务(OTS)” 标准,通过IIOP 协议(Internet Inter-ORB Protocol)实现分布式节点间的事务通信,最终目标是在 Java EE 环境中支持强一致性的分布式事务(即刚性事务)。
在 WebSphere、JBoss 等 Java EE 应用服务器中,JTS 是分布式事务的
“默认支撑”。例如,当应用通过 JTA
的UserTransaction发起跨库事务时,应用服务器的 JTS
实现会自动协调多个数据库的 2PC 流程,保证事务一致性。
总体上来说,JTA更多的是从框架的角度来约定程序角色的接口,而JTS则是从具体实现的角度来约定程序角色之间的接口,两者各司其职。
Atomikos分布式事务实现
Atomikos公司旗下有两款著名的分布事务产品:
- TransactionEssentials:开源的免费产品
- ExtremeTransactions:商业版,需要收费
这两个产品的关系如下图所示:
可以看到,在开源版本中支持JTA/XA、JDBC、JMS的事务。
Spring事务管理器的顶级抽象是PlatformTransactionManager接口,其提供了个重要的实现类:
- DataSourceTransactionManager:用于实现本地事务
- JTATransactionManager:用于实现分布式事务
显然,在这里,我们需要配置的是JTATransactionManager。
LCN
TX-LCN ,5.0以后由于框架兼容了LCN(2pc)、TCC、TXC 三种事务模式,为了区分LCN模式,特此将LCN分布式事务改名为TX-LCN分布式事务框架。
TX-LCN定位于一款事务协调性框架,框架其本身并不生产事务,而是本地事务的协调者,从而达到事务一致性的效果。
TX-LCN 主要有两个模块,Tx-Client(TC) ,Tx-Manager™.
- TM (Tx-Manager):是独立的服务,是分布式事务的控制方,协调分布式事务的提交,回滚
- TC(Tx-Client):由业务系统集成,事务发起方、参与方都由TxClient端来控制
标准 XA 模型 2PC
2PC的两个(三个)阶段
2PC即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase),2是指两个阶段,P是指准备阶段,C是指提交阶段。
但是如果阶段一超时或者出现异常,2PC的阶段二就变成了中断事务
和上面讲的 XA 规范的一般情况很类似,二阶段提交是一种强一致性设计,2PC 引入一个事务协调者的角色来协调管理各参与者(也可称之为各本地资源)的提交和回滚,二阶段分别指的是准备(投票)和提交两个阶段。
我们来详细分析一下 2PC 的各个阶段
我们继续拆解每一步的具体动作:
阶段一:准备阶段(提交事务的请求):
事务管理器给每个参与者发送Prepare消息,每个数据库参与者在本地执行事务,并写本地的Undo/Redo日志,此时事务没有提交。
这步的目标是让所有参与者完成本地操作并锁定资源,但不提交事务,确保后续要么能统一提交,要么能统一回滚,避免 “部分执行”。
详细的步骤如下:
- TM协调者会向所有参与者(RM1、RM2)发送 “准备请求”,并携带全局事务 ID(标识同一事务)。
- RM参与者会 执行本地业务操作(RM1 扣减库存、RM2 创建订单),记录 undo log(用于回滚)和 redo log(用于提交),然后锁定操作涉及的资源(如库存记录行锁、订单表行锁),避免其他事务修改。
- RM参与者在做好本地该做的事情之后,会向协调者反馈状态:
- 若操作成功且资源锁定完成,返回 “同意(Yes/Vote Commit)”。反馈 “同意” 则保持临时状态
- 若操作失败(如库存不足、SQL 错误),返回 “拒绝(No/Vote Abort)”。反馈 “拒绝” 则通过 undo log 回滚本地操作,释放资源。
- 最后协调者收集所有参与者的反馈,进入 “决策阶段”:
- 若全部同意,则进入 “提交阶段”;
- 若任一拒绝,则进入 “回滚阶段”。
阶段二:执行事务的提交
这步会根据准备阶段结果,统一执行最终操作,基于准备阶段的 “全同意” 或 “有拒绝” 结果,向所有参与者下达统一指令,确保状态一致。
如果事务管理器收到了参与者的执行失败或者超时消息时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据事务管理器的指令执行提交或者回滚操作,并释放事务处理过程中使用的锁资源。注意:必须在最后阶段释放锁资源。
协调者在阶段二决定是否最终执行事务提交操作。这一阶段包含两种情形:
- 可以执行事务的提交
- 所有参与者reply Yes,那么执行事务提交。
- 发送提交请求。协调者向所有参与者发送Commit请求;并记录 “全局事务已决定提交” 的日志
- 事务提交。参与者收到Commit请求后,会正式执行事务提交操作,并在完成提交操作之后,释放在整个事务执行期间占用的资源;
- 反馈事务提交结果。参与者在完成事务提交后,写协调者发送Ack消息确认;
- 完成事务。收到所有参与者的 “提交成功” 的ACK反馈后,标记全局事务 “完成(Completed)”,事务结束。
- 所有参与者reply Yes,那么执行事务提交。
- 需要中断事务的提交:
- 任一参与者reply No 或者等待超时,进入中断事务阶段,开始进行事务回滚
- 协调者向所有参与者发送 “回滚请求”,并记录 “全局事务已决定回滚” 的日志。
- 参与者接收 “回滚请求”,通过 undo log 执行回滚(恢复到事务前的原始数据)。并在回滚结束后释放该事务所占用的系统资源;
- 反馈回滚结果。参与者在完成回滚操作后,向协调者发送Ack消息;
- 中断事务。协调者收到所有参与者的 “回滚成功” 反馈后,标记全局事务 “终止(Terminated)”,事务结束。
- 考虑一个问题,上述回滚失败了,会发生什么?
- 任一参与者reply No 或者等待超时,进入中断事务阶段,开始进行事务回滚
- 可以执行事务的提交
所以事情没有这么简单,实际分布式环境中,网络超时、节点宕机是常态,2PC 通过 “日志 + 重试” 应对部分异常,核心逻辑如下:
- 协调者发送指令超时:
- 若准备阶段超时,协调者默认视为 “参与者拒绝”,触发回滚;
- 若提交 / 回滚阶段超时,协调者会重复发送指令(直到收到反馈),因为参与者一旦接收指令,会确保执行(提交 / 回滚是幂等操作)。
- 参与者宕机:
- 若准备阶段宕机,协调者超时后触发回滚;
- 若提交 / 回滚阶段宕机,重启后参与者会通过本地日志(记录了 “已接收提交 / 回滚请求”)补全操作,再反馈给协调者。
- 协调者宕机:
- 若准备阶段宕机,参与者会阻塞等待(不知道该提交还是回滚),需协调者重启后读取日志恢复状态;
- 若提交 / 回滚阶段宕机,参与者已收到指令,会继续执行并等待协调者重启后确认,不影响最终一致性。
可以看到,2PC经历了至少 2 轮网络通信(准备→执行),且参与者在准备阶段会长期持有锁,并发高时会导致大量请求阻塞。而且若协调者在准备阶段宕机,所有参与者会陷入 “阻塞状态”,直到协调者恢复,可用性低。
还存在我说的那种极端场景,也就是若协调者向部分参与者发送 “提交” 后宕机,未收到指令的参与者会阻塞,导致 “部分提交、部分未提交”,需人工介入修复。
2PC 性能太差
2PC的特点
2PC 方案比较适合单体应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。
上面我们也分析了,来回问来回等,自然不适合高并发
2PC 方案实际很少用,一般来说某个系统内部如果出现跨多个库的这么一个操作,是不合规的。我可以给大家介绍一下, 现在微服务,一个大的系统分成几百个服务,几十个服务。一般来说,我们的规定和规范,是要求每个服务只能操作自己对应的一个数据库。
如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务架构的规范,你随便交叉胡乱访问,几百个服务的话,全体乱套,这样的一套服务是没法管理的,没法治理的,可能会出现数据被别人改错,自己的库被别人写挂等情况。
所以如果你要操作别人的服务的库,你必须是通过调用别的服务的接口来实现,绝对不允许交叉访问别人的数据库。
2PC具有明显的优缺点
优点主要体现在实现原理比较简单;而且数据一致性强
缺点比较多:
- 2PC的提交在执行过程中,所有参与事务操作的逻辑都处于阻塞状态,也就是说,各个参与者都在等待其他参与者响应,无法进行其他操作;
- 协调者是个单点,一旦出现问题,其他参与者将无法释放事务资源,也无法完成事务操作;
- 数据不一致。当执行事务提交过程中,如果协调者向所有参与者发送Commit请求后,发生局部网络异常或者协调者在尚未发送完Commit请求,即出现崩溃,最终导致只有部分参与者收到、执行请求。于是整个系统将会出现数据不一致的情形;
- 而且2PC没有完善的容错机制,当参与者出现故障时,协调者无法快速得知这一失败,只能严格依赖超时设置来决定是否进一步的执行提交还是中断事务。
实际上分布式事务是一件非常复杂的事情,两阶段提交只是通过增加了事务协调者(Coordinator)的角色来通过2个阶段的处理流程来解决分布式系统中一个事务需要跨多个服务节点的数据一致性问题。但是从异常情况上考虑,这个流程也并不是那么的无懈可击。
上面我们说到的问题就是假设如果在第二个阶段中 Coordinator 在接收到 Partcipant 的 “Vote_Request” 后挂掉了或者网络出现了异常,那么此时 Partcipant 节点就会一直处于本地事务挂起的状态,从而长时间地占用资源。当然这种情况只会出现在极端情况下,然而作为一套健壮的软件系统而言,异常 Case 的处理才是真正考验方案正确性的地方。
事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,会导致参与者收不到提交或回滚的通知,从而导致参与者节点始终处于事务无法完成的中间状态。
而且如果在第二个阶段,发生了局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就会导致节点间数据的不一致问题。
2PC的改进(存疑)模型—3PC
3PC的三个阶段
针对2PC的缺点,研究者提出了3PC,即Three-Phase Commit。
作为2PC的改进版,3PC将原有的两阶段过程,重新划分为CanCommit、PreCommit和do Commit三个阶段。
也就是通过拆分准备阶段为 “CanCommit” 和 “PreCommit”,引入超时机制,减少单点故障导致的全局阻塞。
3PC 的诞生直接针对 2PC 的两大痛点:
单点故障阻塞:2PC 中协调者(Coordinator)若在 “准备阶段” 后崩溃,所有参与者(Participant)会一直阻塞等待指令。
3PC 通过超时机制让参与者在超时后自主决策(如默认放弃),避免永久阻塞。
状态不确定性:2PC 中参与者仅 “准备” 或 “提交” 两种状态,中间状态模糊。
3PC 新增 “预提交” 状态,细化状态流转,降低一致性判断难度。
3PC 将一致性流程拆分为三个阶段,每个阶段有明确的角色职责(协调者与所有参与者),且均引入超时机制。
我们继续拆解每一阶段的流程:
阶段一:CanCommit(能否提交)
- 协调者向所有参与者发送
CanCommit请求,询问 “是否可以执行事务并准备提交”,随后等待响应。 - 参与者收到请求后,仅判断自身是否具备执行事务的条件(如资源充足、无冲突),不实际执行事务。
- 若可以,返回
Yes响应,并进入 “等待 PreCommit” 状态; - 若不可以,直接返回
No响应。
- 若可以,返回
- 和 2PC
一样,只要有一个参与者返回
No,协调者立即终止事务,向所有参与者发送Abort(中止)指令。
- 协调者向所有参与者发送
阶段二:PreCommit(预提交)
在本阶段,协调者会根据上一阶段的反馈情况来决定是否可以执行事务的PreCommit操作。有以下两种可能:
执行事务预提交
协调者发送
PreCommit请求,指示参与者执行事务(如写入数据、记录日志),并等待确认。参与者收到
PreCommit请求后,执行事务但不提交,将Undo和Redo日志写入本机事务日志,然后返回Ack(确认)响应,进入 “等待 DoCommit” 状态。超时处理,若协调者超时未收到部分参与者的
Ack,或自身崩溃,会向所有参与者发送Abort指令;参与者若超时未收到DoCommit或Abort,默认执行Abort。
事务中断:
任意一个参与者向协调者发送No响应,或者等待超时,协调者在没有得到所有参与者响应时,即可以中断事务
- 发送中断请求。 协调者向所有参与者发送Abort请求;
- 中断事务。无论是收到协调者的Abort请求,还是等待协调者请求过程中出现超时,参与者都会中断事务;
阶段三:doCommit
在这个阶段,会真正的进行事务提交,同样存在两种可能。
执行提交:
此阶段是事务的最终确认,仅在 “所有参与者均返回
Ack” 时执行。- 假如协调者收到了所有参与者的Ack响应,那么将从预提交转换到提交状态,并向所有参与者发送doCommit请求,指示参与者最终提交事务,随后等待完成响应。
- 参与者收到doCommit请求后,执行最终提交(如释放锁定资源、持久化事务结果),删除
“预提交日志”,并在完成提交操作后释放占用资源,返回
Commit Done响应,向协调者发送Ack消息; - 完成事务。协调者接收到所有参与者的Ack消息后,完成事务。
- 异常处理:若参与者超时未收到
DoCommit,会主动向协调者发送 “查询请求”;若协调者已崩溃,参与者默认执行Abort(避免数据不一致)。
中断事务:
在该阶段,假设正常状态的协调者接收到任一个参与者发送的No响应,或在超时时间内,仍旧没收到反馈消息,就会中断事务:
- 发送中断请求。协调者向所有的参与者发送abort请求;
- 事务回滚。参与者收到abort请求后,会利用阶段二中的Undo消息执行事务回滚,并在完成回滚后释放占用资源;
- 反馈事务回滚结果。参与者在完成回滚后向协调者发送Ack消息;
- 中端事务。协调者接收到所有参与者反馈的Ack消息后,完成事务中断。
尽管优化了 2PC,但 3PC 并非完美,仍存在以下问题:
- 极端网络分区下的一致性风险:若协调者在发送
DoCommit后崩溃,部分参与者收到并提交,部分未收到且超时中止,会导致数据不一致(这是分布式协议的固有挑战,需结合其他机制弥补)。 - 性能开销更高:多一轮网络通信(3 轮请求 - 响应),相比 2PC 增加了延迟,不适用于对性能要求高的场景(如高频交易)。
- 实现复杂度高:状态流转更细(参与者需维护 “等待 PreCommit”“等待 DoCommit” 等状态),代码实现和故障恢复逻辑更复杂。
2PC与3PC的区别
3PC有效降低了2PC带来的参与者阻塞范围,并且能够在出现单点故障后继续达成一致;
但3PC带来了新的问题
- 极端网络分区下的一致性风险:若协调者在发送
DoCommit后崩溃,部分参与者收到并提交,部分未收到且超时中止,会导致数据不一致(这是分布式协议的固有挑战,需结合其他机制弥补)。或者在参与者收到preCommit消息后,如果网络出现分区,协调者和参与者无法进行后续的通信,这种情况下,参与者在等待超时后,依旧会执行事务提交,这样会导致数据的不一致。 - 性能开销更高:多一轮网络通信(3 轮请求 - 响应),相比 2PC 增加了延迟,不适用于对性能要求高的场景(如高频交易)。
- 实现复杂度高:状态流转更细(参与者需维护 “等待 PreCommit”“等待 DoCommit” 等状态),代码实现和故障恢复逻辑更复杂。
而 3PC 三阶段提交协议中,在协调者和参与者中都引入 超时机制,并且把两阶段提交协议的第一个阶段拆分成了两步:询问,然后再锁资源,最后真正提交。三阶段提交的三个阶段分别为:can_commit,pre_commit,do_commit。
在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者abort请求时,会在等待超时之后,继续进行事务的提交。(其实这个应该是基于概率来决定的,当进入第三阶段时,说明参与者在第二阶段已经收到了PreCommit请求,那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前,收到所有参与者的CanCommit响应都是Yes。(一旦参与者收到了PreCommit,意味他知道大家其实都同意修改了)所以,一句话概括就是,当进入第三阶段时, 由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大。 )))
而且相对于2PC,3PC主要解决的单点故障问题,并减少阻塞, 因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。
但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的 abort 响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit 操作。这样就和其他接到 abort 命令并执行回滚的参与者之间存在数据不一致的情况。
3PC相对于2PC而言到底优化了什么地方呢?
相比较2PC而言,3PC对于协调者(Coordinator)和参与者(Partcipant)都设置了超时时间,而2PC只有协调者才拥有超时机制。这解决了一个什么问题呢?
主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
另外,通过CanCommit、PreCommit、DoCommit三个阶段的设计,相较于2PC而言,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的。
以上就是3PC相对于2PC的一个提高(相对缓解了2PC中的前两个问题),但是3PC依然没有完全解决数据不一致的问题。假如在 DoCommit 过程,参与者A无法接收协调者的通信,那么参与者A会自动提交,但是提交失败了,其他参与者成功了,此时数据就会不一致。
所以,3PC 因性能和一致性的权衡,实际应用较少,更多是作为分布式一致性协议的理论基础
XA规范普遍存在的问题
XA规范在1994年就出现了,至今没有大规模流行起来,必然有他一定的缺陷:
- 数据锁定:数据在事务未结束前,为了保障一致性,根据数据隔离级别进行锁定。
- 协议阻塞:本地事务在全局事务 没 commit 或 callback前都是阻塞等待的。
- 性能损耗高:主要体现在事务协调增加的RT成本,并发事务数据使用锁进行竞争阻塞。
XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。但是,XA也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景。XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换回导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。
其实也并非不用,例如在IBM大型机上基于CICS很多跨资源是基于XA协议实现的分布式事务,事实上XA也算分布式事务处理的规范了,但在为什么互联网中很少使用,究其原因有以下几个:
- 性能(阻塞性协议,增加响应时间、锁时间、死锁);
- 数据库支持完善度(MySQL 5.7之前都有缺陷);
- 协调者依赖独立的J2EE中间件(早期重量级Weblogic、Jboss、后期轻量级Atomikos、Narayana和Bitronix);
- 运维复杂,DBA缺少这方面经验;
- 并不是所有资源都支持XA协议;
准确讲XA是一个规范、协议,它只是定义了一系列的接口,只是目前大多数实现XA的都是数据库或者MQ,所以提起XA往往多指基于资源层的底层分布式事务解决方案。其实现在也有些数据分片框架或者中间件也支持XA协议,毕竟它的兼容性、普遍性更好。







