首页 > 数据库 > 数据库开发技术 > 把脉分布式事务的模型、协议和方案

大家好,我是石页兄;编程界的一名小学生
既然选择远方,便只顾风雨兼程
欢迎关注微信公众号「架构染色」&& 加群交流
在当前的技术发展阶段,不同的业务场景对一致性、可靠性、易用性、性能等要求不同,应用架构可根据实际场景的需求,灵活选择合适的分布式事务解决方案。
行业中把分布式事务解决方案分为刚性事务方案和柔性事务方案这两大类;就刚性事务这个范畴,The Open Group 的组织提出的 X/Open Distributed Transaction Processing (DTP) Model ,已经成为事实上的事务模型组件的行为标准,解决了不少分布式事务的问题。但随着技术以及业务场景的演进发展,其缺点和局限性也越来越明显,伴随着 BASE 理论的提出,诸多柔性事务方案(如可靠消息、最大努力通知、TCC、AT、Saga)诞生,满足了各类业务场景的差异化需求。掌握各种方案的特点,方可灵活、准确的应用。
在开始介绍 DTP 模型之前,需要先了解 两阶段提交协议。两阶段提交协议(The two-phase commit protocol)简称 2PC,2PC 是为了要保证分布式事务的原子性,此协议中有【协调者】和【参与者】两个角色,整个处理过程包含两个阶段:托票阶段 和 提交阶段:
依据第 1 阶段返回的结果,决定事务最终是提交(Commit)还是放弃(Rollback)。
如果所有的参与节点都回复 Yes,那么协调者在第 2 阶段发出提交(Commit)请求。
如果任一参与节点回复“No”,那么协调者在第 2 阶段发出回滚(Rollback)请求。
整个过程要么成功,要么失败回滚,对外部来说,该事务的状态变化是原子的。要达到这个效果,对 2 个阶段的处理是有约束的:
X/Open 组织定义的分布式事务的处理(DTP)模型由以下几个元素组成:
在 DTP 中,基本组成需要涵盖 AP、TM、RMs(不需要 CRM、CP 也是可以的),如下图所示
XA 规范是 X/Open 组织提出的分布式事务处理 (DTP)的接口规范(所以也常看到被叫做 XA 接口 ),描述了 RM 要支持事务性访问所必需做的事情,XA 接口是在 TM 与 RM 之间提供双向通信。它是 TM、RM 这两个 DTP 软件组件之间的系统级别接口,而不是应用程序开发者对其进行编码的普通应用程序接口。
重要提示:为什么有的资料把 XA 叫做 2PC ,把 XA 和 2PC 描述为同一个事物。DTP、XA、2PC 之间是什么关系?可以这么理解,DTP 模型定义 TM 和 RM 之间通讯的接口规范叫 XA,XA 规范使用 2PC 来保证事务中的所有资源全部提交或全部回滚。
DTP 模型定义了 TM 和 RM 之间通讯的 XA 规范,而 DB 在 DTP 模型中担任 RM 的角色。为了让上层应用可以便捷的使用分布式事务,许多关系型 DB 都实现了 X/Open 的 XA 规范。MySQl 从 5.x 开始支持 XA 规范,但仅 innodb 存储引擎支持 。
这里列举出 MySQL XA 的命令集合,先有个初步认识;看完后边状态流转和示例后可加深印象。
| 命令 | 解释 |
|---|---|
| XA START xid | 开启一个事务,并将事务置于 ACTIVE 状态,此后执行的 SQL 语句都将置于该事务中 |
| XA END xid | 将事务置于 IDLE 状态,表示事务内的 SQL 操作完成 |
| XA PREPARE xid | 实现事务提交的准备工作,事务状态置于 PREPARED 状态。事务如果无法完成提交前的准备操作,该语句会执行失败 |
| XA COMMIT xid | 事务最终提交,完成持久化 |
| XA ROLLBACK xid | 事务回滚终止 |
| XA RECOVER | 查看 MySQL 中存在的 PREPARED 状态的 xa 事务 |
XA 事务可以一步提交(非本篇关注点),本篇重点是两阶段提交。对于两阶段提交来说,要在 prepare 之后等其他 RM 反馈结果。状态转换流程如下图所示:
XA 事务开启时多了一个全局事务号,结束时多了一个 end 动作 和 prepare 动作。
XA START, 开启一个分布式事务,需要指定分布式事务号。
XA END,在内部仅是一个状态变化,声明当前 XA 事务结束,不允许追加新的 sql 语句,无其它作用,业界有人提出 XA 事务框架去掉这一步,减少一次网络交互,提高性能。
XA PREPARE,MySQL 5.7 版本此指令才写 binlog 和 redo log,预提交事务,并将分布式事务信息保存到全局内存结构,让其它连接可以查询、回滚、提交。
XA COMMIT,真正提交事务,修改事务状态,释放锁资源。如果实例上 XA PREPARE 已经成功,那么它的 XA COMMIT 一定能成功。
// 第一阶段
// 1.1 通过 XA START 和 XA END 来包裹 用户业务SQL
mysql> XA START 'transfer_money';
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE account SET money = money -100 where id = 1 ;
Query OK, 1 row affected (0.00 sec)
mysql> XA END 'transfer_money';
Query OK, 0 rows affected (0.00 sec)
// 1.2 通过 XA PREPARE 通知 RM 一阶段就绪
mysql> XA PREPARE 'transfer_money';
Query OK, 0 rows affected (0.00 sec)
// 第二阶段,通过 XA COMMIT 完成二阶段的提交
mysql>
mysql> XA COMMIT 'transfer_money';
Query OK, 0 rows affected (0.00 sec)
XA 事务 prepare 成功后,若不提交就相当于是长事务了,要尽快提交,否则会带来很多问题。但 DB 或应用出错导致 XA 事务未能全部提交的情况也无法避免,这些残存 XA 事务可人工处理,执行 xa recover 查看未提交 XA 事务,选择对应的进行 rollback 或 commit。想了解 MySQL XA 的更多信息可查看MySQL 分布式事务的“路”与“坑”
java 项目中若使用 MySQL XA 方案,有 JTA(Atomikos 框架提供支持) 和 Seata 的 XA 模式,这两种实现方式。
JTA(Atomikos)
JTA(Java Transaction API)是 java 根据 XA 规范提供的事务处理标准,目的是统一一套 API 简化学习,JTA 和它的同胞 JTS(Java Transaction Service),为 J2EE 平台提供了分布式事务服务
JTA 的目标是屏蔽底层事务资源,使应用可以透明的方式参入到事务处理中(Seata 的也类似)
非常有名的分布式事务开源框架 atomikos 提供了开源免费的 JTA/XA 实现(也有 TCC 机制的商业付费版实现)。接入简单,只需引入对应 jar 包,无需额外的服务。但 JTA 方案仅适用于单体架构多数据源时实现分布式事务
Seata XA 模式
2PC 是 XA 规范用于在全局事务中协调多个资源的机制。2PC 遵循 OSI(Open System Interconnection,开放系统互联)/DTP 标准,但实际它比标准本身还要早若干年出现。
网络中关于 2PC 的描述有的是仅指 2PC 算法本身,而有的则用 2PC 来代指 XA;依笔者目前有限的认知来看,网络资料中大多关于 2PC 缺点的描述,是针对 XA(即强一致方案下的 2PC);因为目前其他主流的如 TCC、AT 等模式也属于是 2PC 的变种,但其出现是为了解决了 XA 已知的诸多问题,在阅读资料时需根据上下文来辨识。
业务无侵入:XA 模式将是业务无侵入的,不给应用设计和开发带来额外负担
数据库的支持广泛:XA 协议被主流关系型数据库广泛支持,不需要额外的适配即可使用
数据锁定
协议阻塞
性能差
除此之外,网络上常见的同步阻塞、单点问题、脑裂以及数据一致性问题等问题在《深入理解分布式系统的 2PC 和 3PC》和《关于分布式事务、两阶段提交协议、三阶提交协议》 这两篇文章中有比较详尽的分析。
3 阶段提交在维基百科的描述是这样:
三阶段提交(英语:Three-phase commit),也叫三阶段提交协议(英语:Three-phase commit protocol),是在电脑网络及数据库的范畴下,令一个分布式系统内的所有节点能够执行事务的提交的一种分布式算法。三阶段提交是为了解决两阶段提交协议的缺点而设计的。
与两阶段提交不同的是,三阶段提交是一种“非阻塞”的协议。三阶段提交在两阶段提交的第一阶段与第二阶段之间插入了一个准备阶段,令原先在两阶段提交中,参与者在投票之后,由于协调者发生崩溃或错误,而导致参与者处于无法知晓是否提交或者中止的“不确定状态”所产生的可能相当长的延时的问题[1]得以解决。
简单来说就是把 2PC中的 第 1 个阶段拆分成先询问所有参与者是否可以执行事务-得到一致的同意回复后才执行预提交并锁资源,最后真正提交。
协调者向各参与者发送 CanCommit 的请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应
参与者收到 CanCommit 请求后,正常情况下,如果自身认为可以顺利执行事务,那么会反馈 Yes 响应,并进入预备状态,否则反馈 No。
这个环节根据阶段 1 的参与者的反馈不同,而执行不同的逻辑:
1)如果任意一个参与者在阶段 1 向协调者反馈了 No 响应,或者协调者等待参与者反馈超时,那么就会中断事务,中断执行逻辑如下:
2)另一种情况,如果协调者接收到各参与者反馈都是 Yes,那么才执行事务预提交,执行逻辑如下:
协调者向各参与者发送 preCommit 请求,并进入 prepared 阶段
各参与者接收到 preCommit 请求后执行事务操作,并将 Undo 和 Redo 信息记录到事务日记中,但事务不提交;
如果各参与者都成功执行了事务操作,那么反馈给协调者 Ack 响应,同时等待最终指令,提交 commit 或者终止 abort
这个环节根据阶段 2 参与者不同反馈,而执行不同的逻辑:
1)假设协调者正常工作,并且有任意一个参与者在阶段 2 反馈 No,或者在等待参与者的反馈超时后,都会主动中断事务
协调者向所有参与者节点发送 abort 请求
参与者接收到 abort 请求后,利用 undo 日志执行事务回滚,并在完成事务回滚后释放占用的资源后,向协调者发送 ack 信息,反馈事务回滚结果
协调者接收到所有参与者反馈的 ACK 消息之后,完成事务的中断
2)假设协调者正常工作,接收到了所有参与者的 ack 响应,那么它将从预提交阶段进入提交状态
协调者向所有参与者发送 doCommit 请求
参与者收到 doCommit 请求后,正式提交事务,并在完成事务提交后释放占用的资源,向协调者发送 ACK 信息,反馈事务提交结果
协调者接收到所有参与者 ack 信息,整个事务完成
3PC 的核心理念是:在询问的时候并不锁定资源,除非所有人都同意了,才开始锁资源。怎么展开来理解呢?
在锁定资源之前通过询问的方式执行一次资源锁定前的预检,可以减少非必要资源锁定
通过两次确认来保证在进入最后提交阶段时各参与节点的状态是一致的
避免无限等待,同时在协调者和参与者中都引入超时机制
在参与者完成 PreCommit 后,即使遇到与协调者交互超时的问题,仍可自主的继续 Commit。因为完成 PreCommit 后也意味着大家其实都在 CanCommit 阶段同意修改了,此时采取 Commit 极大的概率不会错的。而 2 阶段提交中参与者在 1 阶段后是不知所措的(不知道其他参与者怎么反馈,不知道协调者的决议是什么),而且不知所错的状态可能持续相当长的时间;可查看 3PC 维基百科的举例说明。
在协调者和参与者中都引入超时机制,超时后自醒,主动执行自认为合理的下一步逻辑(回滚或者提交),避免无上限的挂起:
1)canCommit 阶段
2)pre commit 阶段
3)do commit 阶段
2PC 和 3PC ,它们是在分布式数据库管理系统 (Distributed Database Management System,DDBMS) 中管理如何提交或中止数据库事务的最流行算法。2PC 协议是一个阻塞的两阶段提交协议,3PC 协议是一种非阻塞的三阶段提交协议。通过询问避免了资源的无效占用;协调者和参与者各自有超时自醒处理,避免长时间资源占用不释放,提高资源利用率。消除了 2PC 协议的阻塞问题,但是,3PC 协议在网络出现分段的情况下不会恢复。因此,Keidar 和 Dolev (1998) 建议使用增强型三阶段提交 (E3PC) 协议来消除此问题。E3PC 协议需要至少三个往返才能完成,这将有很长的延迟才能完成每笔交易。
所以,无论是 3PC 还是 E3PC,虽然解决了一些问题,但遗留问题以及复杂度的提升也导致性价比不高,估计在实际应用中的效果并不好,所以目前普遍使用的依然是 XA(2PC) ;对 3PC 的学习都是理论(缺产品),通过对 3PC 理论学习探讨,来理解经典 XA(2PC) 的优缺点,并作为后续学习柔性事务方案的理论借鉴。
补充:有些网络资料描述 XA 包括 2PC 和 3PC,但从维基百科的信息来看,对 XA 的描述中并未提及 3PC,且 3PC 的描述中也未看到 XA。而且 3PC 没有让人熟悉的产品实现,后续内容就不再与其他可实施落地的方案一起讨论了。
XA(2PC)由数据库做为参与者管理事务资源,提供分布式事务的处理支持,所以可以保障从任意视角对数据的访问都能有效隔离,满足全局数据一致性,被称作刚性事务。
刚性事务属于 CAP 理论中的 CP 组合,会有性能上限,无法满足高并发场景的需求。基于 BASE 理论,柔性事务方案被提出用于保证事务数据的最终一致性。柔性事务本质是对 XA 协议的妥协,它通过降低强一致性要求,从而降低数据库资源锁定时间,提升可用性,允许有中间状态,要求最终一致性,也就是 AP 组合。柔性事务分为通知型和补偿型:
通知型事务都是异步的,包含有:可靠消息、最大努力通知
补偿型事务都是同步的,包含有:AT、TCC、Saga
可靠消息方案是指当事务发起方(消息发送者)执行完成本地事务后并发出一条消息,事务参与方(消息接收者)一定能够接收消息并处理事务成功。
此方案强调的是一旦消息发给事务参与方,则最终事务要达到一致。这其中有两个关键问题:
参与方必须能够从 MQ 接收到消息,如果接收消息失败可以重复接收消息。
参与方要解决消息重复消费的问题(消费处理的幂等性)。
可靠消息方案适合执行周期长且实时性要求不高的场景。引入消息机制后,原先同步的事务操作变为基于消息写/读的异步操作,避免了同步阻塞,也实现了服务间的解耦。一般有基于本地消息表和基于消息中间件这两种实现式。
1)基于本地消息表
本地消息表核心思路是:将分布式事务拆分成本地事务进行处理
优点:从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于消息中间件,弱化了对 MQ 中间件特性的依赖,方案轻量,容易实现。
缺点:需设计 DB 消息表,同时还需要一个后台任务,不断扫描本地消息。导致消息的处理和业务逻辑耦合额外增加业务方的负担。
2)基于 MQ 事务消息
为了能解决本地消息表方案的这个缺点,同时又不和业务耦合,RocketMQ 提供了“事务消息”的能力,可以理解为将本地消息表移动到了 MQ 内部。
事务消息发送步骤如下:
发送方将半事务消息发送至 RocketMQ 服务端
服务端将消息持久化成功之后,向发送方返回 ACK 确认消息已经发送成功,此时消息为半事务消息
发送方开始执行本地事务逻辑
发送方根据本地事务执行结果向服务端提交二次确认(Commit 或是 Rollback),服务端收到 Commit 状态则将半事务消息标记为可投递,订阅方最终将收到该消息;服务端收到 Rollback 状态则删除半事务消息,订阅方将不会接受该消息
事务消息回查步骤如下:
在断网或者是应用重启的特殊情况下,上述发送步骤的步骤 4 提交的二次确认最终未到达服务端,经过固定时间后服务端将对该消息发起消息回查
发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果
发送方根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照发送步骤的步骤 4 对半事务消息进行操作
最大努力通知型的目标是 事务发起方尽量将业务处理结果通知到参与方。适用于一些最终一致性时间敏感度低,且参与方的处理结果不影响发起方的处理结果的这类通知类的业务场景。如短信供应商的回执通知:
最大努力通知型的实现方案,一般符合以下特点:
消息重复通知
在业务活动发起方完成业务处理之后,向参与方发送消息,参与方可能没有接收到通知,此时要发起方有一定的机制对消息重复通知(通常是发起方调用参与方的 http 接口,而且会协商一个 N 次 通知的上限)
定期校对
事务发起方提供消息校对的接口,如果事务参与方没有接收到发起方发送的消息,可以调用事务发起方提供的接口主动获取消息
1)可靠性的保障方不同
可靠消息方案中,发起方需要保证将消息发出去,并且将消息发到参与方,消息的可靠性关键由发起方来保证。
最大努力通知方案中,发起方尽最大努力将业务处理结果通知给参与方,但参与者是可能接收不到消息,此时需要接收通知方主动调用发起通知方的接口查询业务处理结果,消息通知的可靠性关键在参与方。
2)两者的业务应用场景不同
可靠消息方案 关注的是整体业务处理的事务一致,以异步的方式完成整个业务处理,通常是内部系统之间的调用
最大努力通知 关注的是业务处理后的通知事务,即将业务处理结果可靠的通知出去
3)技术解决方向不同
可靠消息方案 要解决消息从发出到接收的一致性,即消息发出并且被接收到
最大努力通知方案 无法保证消息从发出到接收的一致性,只提供消息接收的可靠性机制。可靠机制是,尽最大努力将消息通知给参与方,当消息无法被参与方接收时,由参与方主动查询消息(业务处理结果)
AT、TCC、Saga 都是 补偿型的事务模式,应用层面的来提供分布式事务能力,不用依赖数据库对协议的支持,完全剥离了分布式事务方案对数据库在协议支持上的要求(留意下图中 RM 已从 DB 层移至应用层)。
AT、TCC、Saga 也属两阶段处理。有资料中描述 AT、TCC、Saga 属于 2PC 变种,也有一些文章中用 2PC 来代指 XA,在阅读资料时需根据上下文来辨识。关于这几种补偿型事务方案的介绍,后续笔者会用更多独立的篇章来分别描述。
AT 和 TCC 模式可先从笔者以往的文章《用 Seata 搞定分布式事务的规范化建设-赋能产研|提质增效》做个初步的了解。
我是石页兄,如果这篇文章对您有帮助,或者有所启发的话,欢迎关注笔者的微信公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。
另外技术群中也有许多热心的大佬时常交流互助;可扫描下方笔者微信二维码,加 V 备注“加群”,期待并欢迎您的加入。
X/Open 分布式事务处理模型
Mysql 数据库分布式事务 XA 详解
DTP 模型之一:(XA 协议之一)XA 协议、二阶段 2PC、三阶段 3PC 提交
分布式事务如何实现?深入解读 Seata 的 XA 模式
XA 事务水很深,小伙子我怕你把握不住!
分布式事务解决方案,中间件 Seata 的设计原理详解
微服务架构下数据一致性解决方案实践
分布式架构设计篇(七)-刚性事务总结和柔性事务概述
七种常见分布式事务详解
终于有人把“分布式事务”说清楚了!
5.0 柔性事务:最大努力通知
原文作者:架构染色;如有侵权,请联系管理员删除。