A. 原来Demo中的局限性更改方案
B.分布式事务之 XA规范以及2PC/3PC分布式事务
如果一个系统操作多个数据库,当中如果出现上一节说道支付流程当中其中一环异常出错,需要事务回滚,这必然不能使用传统Spring事务处理的方法,需要使用分布式事务去解决这个问题
XA规范
有个叫做X/Open的组织定义了分布式事务的模型,这里面有几个角色,就是AP(Application,应用程序),TM(Transaction Manager,事务管理器),RM(Resource Manager,资源管理器),CRM(Communication Resource Manager,通信资源管理器)
PS: 其实Application说白了就是我们的系统,TM的话就是一个在系统里嵌入的一个专门管理横跨多个数据库的事务的一个组件,RM的话说白了就是数据库(比如MySQL),CRM可以是消息中间件(但是也可以不用这个东西)

这里定义了一个很重要的概念,就是全局事务,这个玩意儿说白了就是一个横跨多个数据库的事务,就是一个事务里,涉及了多个数据库的操作,然后要保证多个数据库中,任何一个操作失败了,其他所有库的操作全部回滚,这就是所谓的分布式事务
上面这套东西就是所谓的X/Open组织搞的一个分布式事务的模型,那么XA是啥呢?说白了,就是定义好的那个TM与RM之间的接口规范,就是管理分布式事务的那个组件跟各个数据库之间通信的一个接口
XA仅仅是个规范,具体的实现是数据库产商来提供的,比如说MySQL就会提供XA规范的接口函数和类库实现
2PC理论
2PC(Two-Phase-Commitment-Protocol)就是基于XA规范搞的一套分布式事务的理论,也可以叫做一套规范、或者是协议。
2PC实现分布式事务过程中的一些细节:
(1)准备阶段
就是TM先发送个prepare消息给各个数据库,让各个库先把分布式事务里要执行的各种操作,先准备执行,其实此时各个库会差不多先执行好,就是不提交罢了。
然后各个数据库都返回一个响应消息给事务管理器准备好了,如果成功了就发送一个成功的消息,如果失败了就发送一个失败的消息(这里还没分布式事务确认)
PS:可以认为是prepare发消息,各个库先在本地开个事务,执行好SQL,注意这里各个数据库会准备好随时可以提交或者是回滚,且有对应的日志记录
(2)提交阶段
- 第一种情况:要是TM发现某个RM的业务处理或者相对应的数据库失败了,TM直接判定这个分布式事务失败!通知全部RM都要回滚,操作完成之后通知TM已经完成操作
- 第二种情况:要是TM接收到所有的数据库返回的消息都是成功,TM发送各个RM提交事务,完成后通知TM整个事务已经完成
2PC的缺陷
i). 同步阻塞
在阶段一里执行prepare操作会占用资源,一直到整个分布式事务完成,才会释放资源,这个过程中,如果有其他人要访问这个资源,就会被阻塞住ii). 单点故障
TM是个单点,一旦挂掉就完蛋了iii). 事务状态丢失
即使把TM做成一个双机热备的,一个TM挂了自动选举其他的TM出来,但是如果TM挂掉的同时,接收到commit消息的某个库也挂了,此时即使重新选举了其他的TM,压根儿不知道这个分布式事务当前的状态,因为不知道哪个库接收过commit消息,那个接收过commit消息的库也挂了iv). 脑裂问题
在阶段二中,如果发生了脑裂问题,那么就会导致某些数据库没有接收到commit消息,有些库收到了commit消息,结果有些库没有收到针对2PC的问题引入3PC分布式事务方案
3PC(three-phase-commitment),三阶段提交协议,这个是针对2PC做的一个改进,主要就是为了解决2PC协议的一些问题
3PC实现分布式事务过程中的一些细节:i)CanCommit阶段
TM发送一个CanCommit消息给各个数据库,然后各个库返回个结果,不会执行实际的SQL语句,仅仅就是各个库看看自己网络环境各方面是否ready
ii)PreCommit阶段
会执行各个SQL语句,只是不提交,如果有个库对CanCommit消息返回了失败,TM发送abort消息给各个库,取消分布式事务
iii)DoCommit阶段
PreCommit阶段都返回了成功,那么发送DoCommit消息给各个库,提交事务。各个库如果都返回提交成功给TM,那么分布式事务成功
如果有个库对PreCommit返回的是失败,或者超时一直没返回,那么TM认为分布式事务失败,直接发abort消息给各个库,回滚,各个库回滚成功之后通知TM,分布式事务回滚成功
3PC比2PC的改进
(1)引入了CanCommit阶段
(2)在DoCommit阶段,各个库自己也有超时机制,如果超时还没收到TM发送的DoCommit消息或者是abort消息,直接判定为TM可能出故障了,各个库就执行DoCommit操作提交事务,解决了TM挂掉的单点问题
(3)另外资源阻塞问题也能减轻一下,因为一个库如果一直接收不到DoCommit消息,不会一直锁着资源,人家自己会提交释放资源的,所有能减轻资源阻塞问题3PC的缺陷
TM在DoCommit阶段发送了abort消息给各个库,结果因为脑裂问题,某个库没接收到abort消息,自己还因为超时机制的执行了commit操作C.MySQL对XA分布式事务的支持(了解)
Mysql是支持XA分布式事务的,而且支持2PC的协议。Mysql XA分布式事务实现如下图:
具体下面代码 ```java package com.zhss.data.refill.center;
import com.mysql.jdbc.jdbc2.optional.MysqlXAConnection; import com.mysql.jdbc.jdbc2.optional.MysqlXid; import javax.sql.XAConnection; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException;
public class MySQLXATest {
public static void main(String[] args) throws SQLException {// 创建抽奖库的RM实例Connection lotteryConnection = DriverManager.getConnection("jdbc:mysql://localhost:3306/data-refill-center-lottery","root","root");// 这里的这个true参数,是说打印出来XA分布式事务的一些日志XAConnection lotteryXAConnection = new MysqlXAConnection((com.mysql.jdbc.Connection)lotteryConnection, true);// 这个XAResource其实你可以认为是RM(Resource Manager)的一个代码中的对象实例XAResource lotteryResource = lotteryXAConnection.getXAResource();// 创建积分库的RM实例Connection creditConnection = DriverManager.getConnection("jdbc:mysql://localhost:3306/data-refill-center-credit","root","root");XAConnection creditXAConnection = new MysqlXAConnection((com.mysql.jdbc.Connection)creditConnection, true);XAResource creditResource = creditXAConnection.getXAResource();// 下面俩东西是分布式事务id(txid)的构成部分byte[] gtrid = "g12345".getBytes();int formatId = 1;try {// 这是说在分布式事务中的抽奖库的子事务的标识// 我们在抽奖库要执行的操作隶属于分布式事务的一个子事务,子事务有自己的一个标识byte[] bqual1 = "b00001".getBytes();Xid xid1 = new MysqlXid(gtrid, bqual1, formatId); // 这个xid代表了抽奖库中的子事务// 这就是说通过START和END两个操作,定义好了分布式事务中,抽奖库中要执行的SQL语句// 但是这里的SQL绝对不会执行的,只是说先定义好我要在分布式事务中,这个数据库里要执行哪些SQL语句lotteryResource.start(xid1, XAResource.TMNOFLAGS);PreparedStatement lotteryPreparedStatement = lotteryConnection.prepareStatement("UPDATE lottery_draw SET lottery_draw_count=lottery_draw_count+1 WHERE id=1");lotteryPreparedStatement.execute();lotteryResource.end(xid1, XAResource.TMSUCCESS);// 这是说在分布式事务中的积分库的子事务的标识// 大家看下,积分库的子事务的xid中的,gtrid和formatId是一样的,bqual是不一样的// 在一个分布式事务中,涉及到多个数据库的子事务,每个子事务的txid,有一部分是一样的,一部分是不一样的byte[] bqual2 = "b00002".getBytes();Xid xid2 = new MysqlXid(gtrid, bqual2, formatId);// 这就是说通过START和END两个操作,定义好了分布式事务中,积分库中要执行的SQL语句creditResource.start(xid2, XAResource.TMNOFLAGS);PreparedStatement creditPreparedStatement = creditConnection.prepareStatement("UPDATE credit SET POINT=POINT+1.2 WHERE id=1");creditPreparedStatement.execute();creditResource.end(xid2, XAResource.TMSUCCESS);// 到这里为止,其实还啥都没干呢,不过就是定义了分布式事务中的两个库要执行的SQL语句罢了// 2PC的阶段一:向两个库都发送prepare消息,执行事务中的SQL语句,但是不提交int lotteryPrepareResult = lotteryResource.prepare(xid1);int creditPrepareResult = creditResource.prepare(xid2);// 2PC的阶段二:两个库都发送commit消息,提交事务// 如果两个库对prepare都返回ok,那么就全部commit,对每个库都发送commit消息,完成自己本地事务的提交if (lotteryPrepareResult == XAResource.XA_OK&& creditPrepareResult == XAResource.XA_OK) {lotteryResource.commit(xid1, false);creditResource.commit(xid2, false);}// 如果如果不是所有库都对prepare返回ok,那么就全部rollbackelse {lotteryResource.rollback(xid1);creditResource.rollback(xid2);}} catch (XAException e) {e.printStackTrace();}
}
D.JTA事务以及全局事务呢
i). 全局事务(Global Transaction)
是DTP模型中的一个概念,全局事务,指的其实就是说跨多个数据库的这么一个分布式事务
ii). JTA事务(Java Transaction API,简称JTA)
是一个Java企业版 的应用程序接口,在Java环境中,允许完成跨越多个XA资源的分布式事务
- JTA事务比JDBC事务更强大。一个JTA事务可以有多个参与者,而一个JDBC事务则被限定在一个单一的数据库连接
- JTA和它的同胞Java事务服务(JTS;Java TransactionService),为J2EE平台提供了分布式事务服务。不过JTA只是提供了一个接口,并没有提供具体的实现,而是由J2EE服务器提供商根据JTS规范提供的
iii). 二者对比
全局事务、JTA事务 都是跨多个库的事务,是通过JTA API来支持的,通过JTA API可以协调和管理横跨多个数据库的分布式事务
E. 引入XA分布式事务(Atomikos框架)
N. 现在比较流行的分布式事务方案
TCC
Seata框架
RocketMQ
截取资料
分布式事务学习笔记 分布式事务笔记 对比 5 种分布式事务方案,还是宠幸了阿里的 Seata(原理 + 实战) 基于RocketMQ的分布式事务
