07.《深入理解分布式事务》第七章 XA 强一致性分布式事务原理
07.《深入理解分布式事务》第七章 XA 强一致性分布式事务原理
《深入理解分布式事务》第七章 XA 强一致性分布式事务原理
文章目录
一、X/Open DTP 模型与 XA 规范
X/Open DTP 模型是 X/Open 组织定义的分布式事务标准规范,这个规范定义了分布式事务处理的一套规范和 API,具体的实现由各厂商负责。本节对 X/Open DTP 模型与 XA 规范进行简单介绍
1.DTP 模型
DTP 模型主要定义了 3 个核心组件,分别是应用程序、资源管理器和事务管理器,三者之间的关系如下图所示:
- 应用程序用于定义事务边界,即定义事务的开始和结束,并且在事务边界内对资源进行操作
- 资源管理器也称为事务参与者,如数据库、文件系统等,并提供访问资源的方式
- 事务管理器也称为事务协调者,负责分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚等操作
2.XA 规范
下面简要介绍 XA 规范:
- xa_start:负责开启或恢复一个事务分支,并且管理 XID 到调用线程
- xa_end:负责取消当前线程与事务分支的关联
- xa_prepare:负责询问资源管理器是否准备好提交事务分支
- xa_commit:负责通知资源管理器提交事务分支
- xa_rollback:负责通知资源管理器回滚事务分支
- xa_revocer:负责列出需要恢复的 XA 事务分支
3.JTA 规范
JTA(Java Transaction API)为 J2EE 平台提供了分布式事务服务的能力。JTA 规范是 XA 规范的 Java 版,即把 XA 规范中规定的 DTP 模型交互接口抽象成 Java 接口中的方法,并规定每个方法要实现什么样的功能,其架构如下图所示:
JTA 定义的接口如下:
- javax.transaction.TransactionManager:事务管理器,负责事务的 begin、commit、rollback 等命令
- javax.transaction.UserTransaction:用于声明一个分布式事务
- javax.transaction.TransactionSynchronizationRegistry:事务同步注册
- javax.transaction.xa.XAResource:定义资源管理器提供给事务管理器操作的接口
- javax.transaction.xa.Xid:事务 XID 接口
事务管理器提供者:实现 UserTranssaction、TransactionManager、Transaction、TransactionSynchronizationRegistry、Synchronization、XID 接口,通过与 XAResource 接口交互来实现分布式事务
资源管理器提供者:XAResource 接口需要由资源管理器实现,该接口中定义了一些方法,这些方法会被事务管理器调用
- start 方法:开启事务分支,对应 XA 底层实现是 XA START
- end 方法:结束事务分支,对应 XA 底层实现是 XA END
- prepare 方法:准备提交分支事务,对应 XA 底层实现是 XA PREPARE
- commit 方法:提交分支事务,对应 XA 底层实现是 XA COMMIT
- rollback 方法:回滚分支事务,对应 XA 底层实现是 XA ROLLBACK
- recover 方法:列出所有处于 PREPARED 状态的事务分支,对应 XA 底层实现是 XA RECOVER
4.XA 二阶段提交
一阶段:执行 XA PREPARE 语句。事务管理器通知各个资源管理器准备提交它们的事务分支。资源管理器受到通知后执行 XA PREPARE 语句
二阶段:执行 XA COMMIT/ROLLBACK 语句。事务管理器根据各个资源管理器的 XA PREPARE 语句执行结果,决定是提交事务还是回滚事务。如果所有的资源管理器都预提交成功,那么事务管理器通知所有的资源管理器执行 XA 提交操作;如果有资源管理器的 XA PREPARE 语句执行失败,则由事务管理器通知所有资源管理器执行 XA 回滚操作
二、MySQL 对 XA 规范的支持
MySQL 从 5.0.3 版本开始支持 XA 分布式事务,且只有 InnoDB 存储引擎支持。MySQL Connector/J 从 5.0.0 版本开始直接提供对 XA 的支持。需要注意的是,在 DTP 模型中,MySQL 属于资源管理器,而一个完整的分布式事务中一般会存在多个资源管理器,由事务管理器来统一协调。因此,这里所说的 MySQL 对 XA 分布式事务的支持,一般指的是单台 MySQL 实例如何执行自己的事务分支
1.MySQL XA 事务的语法
- XA {START|BEGIN} xid [JOIN|RESUME]:开启 XA 事务,注意如果使用的是 XA START,那么不支持 [JOIN|RESUME] 语句
- XA END xid [SUSPEND [FOR MIGRATE]]:结束一个 XA 事务,不支持 [SUSPEND [FOR MIGRATE]] 语句
- XA PREPARE xid:准备提交 XA 事务(如果使用了一阶段提交,该过程可以省略)
- XA COMMIT xid [ONE PHASE]:提交 XA 事务
- XA ROLLBACK xid:回滚 XA 事务
- XA RECOVER [CONVERT XID]:列出所有处于 Prepare 阶段的 XA 事务
2.MySQL XID 详解
在 MySQL 的事务语法中的最后都会跟上 XID,MySQL 作为事务分支标识符是在 XA 规范中定义的。XID 的结构描述如下:
#define XIDDATASIZE 128
#define MAXGTRIDSSIZE 64
#define MAXBQUALSIZE 64
struct xid_t {
long formatID;
long gtrid_length;
long bqual_length;
char data[XIDDATASIZE];
};
typedef struct xid_t XID;
extern int ax_reg(int, XID *, long);
extern int ax_unreg(int, long);
我们可以看到,在 XID 中有 4 个字段,下面分别解释各字段的含义:
- formatID:记录 gtrid、bqual 的格式,类似 Memcached 中 flags 字段的作用。XA 规范中通过一个结构体约定了 XID 的组成部分,但没有规定 data 中存储的 gtrid、bqual 的内容应该是什么格式
- gtrid_length:全局事务标识符(Global Transaction Identifier),最大不能超过 64 字节
- bequal_length:分支限定符(Branch Qualifier),最大不能超过 64 字节
- data:XID 的值,即 gtrid 和 bqual 拼接后的内容。在 XID 的结构体中,没有 gtrid 和 bqual,只有 gtrid_length、bqual_length。由于二者的内容都存储在 data 中,因此我们可以根据 data 反推出 gtrid 和 bqual。举例来说,假设 gtrid 为 abc,bqual 为 def,那么 gtrid_length = 3,bqual_length = 3,data = abcdef。反推的时候,从 data[0] 到 data[gtrid_length - 1] 之间的部分就是 gtrid 的值,从 data[gtrid_length] 到 data[gtrid_length + bqual_length - 1] 部分就是 bqual 的值
3.MySQL XA 事务的状态
MySQL XA 事务状态是正确执行 XA 事务的关键。每次执行 MySQL 的 XA 事务语句都会修改 XA 事务的状态,进而执行不同的 XA 语句。XA 事务状态流程如下图所示:
- 在 XA START 和 XA END 之间执行的是业务 SQL 语句,无论是否执行成功, 都应该执行 XA END
- 在 IDLE 状态下的事务可以直接执行 XA COMMIT,这里我们可以这样理解,当只有一个资源管理器的时候,可以直接退化成一阶段提交
- 只有状态为 Failed 的时候,才能执行 XA ROLLBACK 进行 XA 事务回滚
- XA 事务和非 XA 事务(即本地事务)是互斥的。例如,已经执行了 XA START 命令来开启一个 XA 事务,则本地事务不会被启动,直到 XA 事务被提交或回滚为止。相反的,如果已经使用 START TRANSACTION 启动了一个本地事务,则 XA 语句不能被使用,直到该事务被提交或回滚为止
4.MySQL XA 的问题
MySQL 低于 5.7 版本会出现的问题
- 已经预提交的事务,在客户端退出或者服务宕机的时候,二阶段提交的事务会被回滚
- 在服务器故障重启提交后,相应的 BinLog 会丢失
MySQL 5.6 版本在客户端退出的时候,会自动回滚已经准备好的事务,这是因为,对于处于 Prepare 状态的事务,MySQL 是不会记录 BinLog 的(官方解释为减少 fsync,起到优化的作用),Prepare 状态以前的操作信息都保存在连接的 IO_CACHE 中,如果此时客户端退出了,那么 BinLog 信息会被丢弃,重启后会丢失数据
MySQL 高版本的优化
事务在 Prepare 阶段就完成了写 BinLog 的操作(通过新增一种名为 XA_prepare_log_event 的 event 类型来实现)
三、XA 规范的问题思考
XA 二阶段规范虽然提供了分布式事务的解决思路与方案,但是自身也存在很多问题
1.XA 规范的缺陷
下面使用两个 MySQL 实例,完成一次 XA 分布式事务,如下图所示:
XA 规范中每个分支事务的执行都是同步的,并且只会存在一个事务协调者,由于网络的不稳定性,可能会出现数据不一致的问题。总体来说,XA 分布式事务会存在如下几个问题
同步阻塞
全局事务内部包含多个独立的事务分支,这些事务分支要么都成功,要么都失败。各个事务分支的 ACID 特性共同构成了全局事务的 ACID 特性,即单个事务分支支持的 ACID 特性被提升到分布式事务的范畴。即使在非分布式事务中,如果对读操作很敏感,我们也需要将事务隔离级别设置为串行化。而分布式事务更是如此,可重复读隔离级别不足以保证分布式事务的一致性。如果我们使用 MySQL 来支持 XA 分布式事务,那么最好将事务隔离级别设置为串行化。串行化是 4 个事务隔离级别中最高的级别,也是执行效率最低的级别
单点故障
一旦协调者事务管理器发生故障,参与者资源管理器会一直阻塞下去。尤其在两阶段提交的第二个阶段,如果协调者发生故障,那么所有的参与者都将处于锁定事务资源的状态中,无法继续完成事务操作(如果是协调者宕机,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
数据不一致
在 Commit 阶段,当协调者向参与者发送 commit 请求后,发生了局部网络异常或者在发送 commit 请求的过程中,协调者发生了故障,会导致只有一部份参与者接收到了 commit 请求。而这部分参与者接到 commit 请求之后就会执行 commit 操作,但是其他部分未接到 commit 请求的参与者无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象
2.解决 XA 数据不一致的问题
日志存储
记录 XA 事务在每个流程中的执行状态,是解决 XA 数据不一致问题的关键。至于日志应该存储在哪里,使用什么存储,则根据具体需求确定,一般推荐采用中心化的存储方式,比如数据库。下表是一个简单的事务日志的数据结构:
自定义事务回复
事务恢复,首先通过 XA recovery 命令从资源管理器中获取需要被恢复的事务记录,然后根据 XID 匹配应用程序中存储的日志,根据事务状态进行提交或回滚,大体流程如下图所示:
4.解决事务管理器的单点故障问题
解决事务管理器的单点故障问题,我们一般会想到集群部署和注册中心。实际上,注册中心检测服务是否可用也是需要时间的。目前业界大致有两种解决方式,一种是去中心化部署(事务管理器嵌套在业务系统中),一种是中心化部署,如下图所示:
- 去中心化部署:事务管理器嵌套在应用程序里面,不再单独部署。集群模式中事务角色由应用程序来解决
- 中心化部署:事务管理器单独部署,然后与应用程序进行远程通信。集群模式中事务角色依赖其自身解决
四、主流的解决方案
XA 规范虽已提出多年,但在开源社区中,完整的解决方案并不是很多,下面给大家简单介绍几个目前开源社区中主流的 XA 分布式事务解决方案
Atomikos 解决方案
Atomikos 由免费的社区版本和收费的商业版本
- 官网地址:https://www.atomikos.com
- 社区版源码地址:https://github.com/atomikos/transactions-essentials
- 社区版与商业版对比:https://www.atomikos.com/Main/CompareSubscriptions?done_form=1
Hmily 解决方案
Hmily 是国内 Dromara 开源社区提供的一站式分布式事务解决方案
- 官网地址:https://dromara.org
- 项目源码地址:https://github.com/dromara/hmily
Narayana 解决方案
Narayana 是 Jboos 团队提供的 XA 分布式事务解决方案
- 官网地址:http://narayana.io
- 项目源码地址:https://github.com/jbosstm/narayana
来源:https://blog.csdn.net/qq_45593575/article/details/122476673