https://dev.mysql.com/doc/refman/5.7/en/xa.html
全局事务涉及几个本身具有事务性的操作,但是所有操作必须作为一个组同时COMMIT或者ROLLBACK,从本质上讲,这将ACID属性扩展到上一个级别,以便可以将多个ACID事务作为具有ACID属性的全局操作的组成部分一起执行。
使用全局事务的应用程序涉及一个或多个资源管理器和一个事务管理器:
执行全局事务的过程使用两阶段提交(2PC)。这是在执行全局事务的分支所执行的动作之后发生的。
XA事务状态:
使用XA START启动一个XA事务,并把它的 ACTIVE状态。
对于ACTIVEXA事务,发出组成该事务的SQL语句,然后发出一条XA END语句。 XA END将交易置于 IDLE状态。
对于IDLEXA事务,可以发出一个XA PREPARE语句或一个XA COMMIT … ONE PHASE语句:
XA PREPARE将交易置于 PREPARED状态。此时的一条 XA RECOVER语句将事务的xid值包含在其输出中,因为 XA RECOVER列出了处于该PREPARED状态的所有XA事务。
XA COMMIT ... ONE PHASE准备并提交事务。xid未列出该 值, XA RECOVER因为事务终止。
于PREPAREDXA事务,您可以发出一条XA COMMIT语句来提交和终止该事务,或者 XA ROLLBACK回滚并终止该事务。
mysql> XA START 'xatest';
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO mytable (i) VALUES(10);
Query OK, 1 row affected (0.04 sec)
mysql> XA END 'xatest';
Query OK, 0 rows affected (0.00 sec)
mysql> XA PREPARE 'xatest';
Query OK, 0 rows affected (0.00 sec)
mysql> XA COMMIT 'xatest';
Query OK, 0 rows affected (0.00 sec)
两阶段提交又称2PC(two-phase commit protocol),2pc是一个非常经典的强一致、中心化的原子提交协议。这里所说的中心化是指协议中有两类节点:一个是中心化协调者节点(coordinator)和N个参与者节点(participant)。
事务询问:协调者 向所有的 参与者 发送事务预处理请求,称之为Prepare,并开始等待各 参与者 的响应。
执行本地事务:各个 参与者 节点执行本地事务操作,但在执行完成后并不会真正提交数据库本地事务,而是先向 协调者 报告说:“我这边可以处理了/我这边不能处理”。.
各参与者向协调者反馈事务询问的响应:如果 参与者 成功执行了事务操作,那么就反馈给协调者 Yes 响应,表示事务可以执行,如果没有 参与者 成功执行事务,那么就反馈给协调者 No 响应,表示事务不可以执行。
第一阶段执行完后,会有两种可能。1、所有都返回Yes. 2、有一个或者多个返回No。
所有的参与者反馈给协调者的信息都是Yes,那么就会执行事务提交,协调者 向 所有参与者 节点发出Commit请求.
事务提交,参与者 收到Commit请求之后,就会正式执行本地事务Commit操作,并在完成提交之后释放整个事务执行期间占用的事务资源。
发送回滚请求,协调者 向所有参与者节点发出 RoollBack 请求。
事务回滚,参与者 接收到RoollBack请求后,会回滚本地事务。
(1)协调者正常,参与者宕机
由于 协调者 无法收集到所有 参与者 的反馈,会陷入阻塞情况。
解决方案:引入超时机制,如果协调者在超过指定的时间还没有收到参与者的反馈,事务就失败,向所有节点发送终止事务请求。
(2)协调者宕机,参与者正常
无论处于哪个阶段,由于协调者宕机,无法发送提交请求,所有处于执行了操作但是未提交状态的参与者都会陷入阻塞情况.
解决方案:引入协调者备份,同时协调者需记录操作日志.当检测到协调者宕机一段时间后,协调者备份取代协调者,并读取操作日志,向所有参与者询问状态。
(3)协调者和参与者都宕机
a.发生在第一阶段: 因为第一阶段,所有参与者都没有真正执行commit,所以只需重新在剩余的参与者中重新选出一个协调者,新的协调者在重新执行第一阶段和第二阶段就可以了。
b.发生在第二阶段 并且 挂了的参与者在挂掉之前没有收到协调者的指令。也就是上面的第4步挂了,这是可能协调者还没有发送第4步就挂了。这种情形下,新的协调者重新执行第一阶段和第二阶段操作。
c.发生在第二阶段 并且 有部分参与者已经执行完commit操作。就好比这里订单服务A和支付服务B都收到协调者 发送的commit信息,开始真正执行本地事务commit,但突发情况,Acommit成功,B确挂了。这个时候目前来讲数据是不一致的。虽然这个时候可以再通过手段让他和协调者通信,再想办法把数据搞成一致的,但是,这段时间内他的数据状态已经是不一致的了! 2PC 无法解决这个问题。
三阶段提交又称3PC,其在两阶段提交的基础上增加了CanCommit阶段,并引入了超时机制。一旦事务参与者迟迟没有收到协调者的Commit请求,就会自动进行本地commit,这样相对有效地解决了协调者单点故障的问题。
发送中断请求:协调者向所有的参与者节点发送abort请求。
事务回滚:参与者接收到abort请求后,会利用其在阶段二中记录的Undo信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。
反馈事务回滚结果:参与者在完成事务回滚之后,向协调者发送Ack消息。
中断事务:协调者接收到所有参与者反馈的Ack消息后,中断事务。
进入第三阶段,无论是协调者和参与者故障,最终都会导致参与者无法及时接收到来自协调者的doCommit或是abort请求,针对于这样的异常情况,参与者都会在等待超时之后,继续进行事务提交。
相比较2PC而言,3PC对于2PC都设置了超时时间,而2PC只有协调者才拥有超时机制。
这个优化点,主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
另外,通过CanCommit、PreCommit、DoCommit三个阶段的设计,相较于2PC而言,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的。
以上就是3PC相对于2PC的一个提高(相对缓解了2PC中的前两个问题),但是3PC依然没有完全解决数据不一致的问题。
LCN模式是通过代理Connection的方式实现对本地事务的操作,然后在由TxManager统一协调控制事务。当本地事务提交回滚或者关闭连接时将会执行假操作,该代理的连接将由LCN连接池管理。
https://github.com/codingapi/tx-lcn/
https://www.codingapi.com/docs/txlcn-preface/
TX-LCN由两大模块组成, TxClient、TxManager,TxClient作为模块的依赖框架,提供TX-LCN的标准支持,TxManager作为分布式事务的控制方。事务发起方或者参与方都由TxClient端来控制。
pom.xml
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tm</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.codingapi.txlcn/txlcn-tc -->
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.codingapi.txlcn/txlcn-txmsg-netty -->
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
表结构
CREATE TABLE `t_tx_exception` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`group_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`unit_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`mod_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`transaction_state` tinyint(4) NULL DEFAULT NULL,
`registrar` tinyint(4) NULL DEFAULT NULL COMMENT '-1:未知;0:Manager 通知事务失败;1:client询问事务状态失败;2:事务发起方关闭事务组失败'
`ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0:待处理;1:已处理',
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 967 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE `t_logger` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`group_id` varchar(20) DEFAULT NULL,
`unit_id` varchar(20) DEFAULT NULL,
`tag` varchar(255) DEFAULT NULL,
`content` varchar(255) DEFAULT NULL,
`create_time` varchar(10) DEFAULT NULL,
`app_name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
TM属性配置
spring.application.name=xxxxx
server.port=7970
# JDBC 数据库配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://ip:3306/tx-manager?characterEncoding=UTF-8
spring.datasource.username=账号
spring.datasource.password=秘密
# redis 的设置信息. 线上请用Redis Cluster
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=xxxxx
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.use-generated-keys=true
# TM后台登陆密码
tx-lcn.manager.admin-key=admin
# TC连接IP, TM监听IP, 默认为 127.0.0.1
tx-lcn.manager.host=127.0.0.1
# TC连接端口,TM监听Socket端口
tx-lcn.manager.port=8070
# 心跳检测时间(ms)
#tx-lcn.manager.heart-time=15000
# 分布式事务执行总时间
#tx-lcn.manager.dtx-time=30000
#参数延迟删除时间单位ms
#tx-lcn.message.netty.attr-delay-time=10000
#tx-lcn.manager.concurrent-level=128
# 开启日志,默认为false
tx-lcn.logger.enabled=true
tx-lcn.logger.driver-class-name=${spring.datasource.driver-class-name}
tx-lcn.logger.jdbc-url=${spring.datasource.url}
tx-lcn.logger.username=${spring.datasource.username}
tx-lcn.logger.password=${spring.datasource.password}
TM启动类添加注解
@EnableTransactionManagerServer
TC属性配置
spring.application.name=spring-demo-d
server.port=12002
## 数据库配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/txlcn-demo?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=roo
mybatis.type-aliases-package=xxx.xxx.xxx
mybatis.mapper-locations=classpath:mapper/*.xml
## tx-manager 链接配置,集群逗号分隔
tx-lcn.client.manager-address=127.0.0.1:8070
TC 启动类添加注解
@EnableDistributedTransaction
业务层添加注解
@Transactional(rollbackFor = Exception.class)
@LcnTransaction
server.port=xxxx
tx-lcn.client.manager-address=127.0.0.1:xxxx,127.0.0.1:xxxx
tx-lcn.springcloud.loadbalance.enabled=true
TCC事务机制是通过对(由业务系统提供的)业务逻辑的调度来实现分布式事务。主要由三步操作,Try: 尝试执行业务、 Confirm:确认执行业务、 Cancel: 取消执行业务。
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
AT 模式 RM 驱动分支事务的行为分为以下两个阶段:
TCC问题
Saga 模式 RM 驱动分支事务的行为包含以下两个阶段:
1、TransactionStatus.CommitTransaction,提交事务,表示允许消费者消费(使用)这条消息
2、TransactionStatus.RollbackTransaction,回滚事务,表示消息将被删除,不允许使用
3、TransactionStatus.Unknown,中间状态,表示需要MQ向消息发送方进行检查以确定状态