X/Open DTP模型与XA协议之我见

X/Open DTP(Distributed Transaction Process)是一个分布式事务模型。这个模型主要使用了两段提交(2PC - Two-Phase-Commit)来保证分布式事务的完整性。

参考博文1中给除了基于DTP模型的分布式事务大致的流程:

而且,博客中提到:XA 协议描述了 TM 与 RM 之间的接口,允许多个资源在同一分布式事务中访问。XA 协议使用 2PC(Two Phase Commit,两阶段提交)原子提交协议来保证分布式事务原子性。两阶段提交是指将提交过程分为两个阶段,即准备阶段(投票阶段)和提交阶段(执行阶段):

看到这里我不禁心生疑惑:图中的步骤3和5存在的意义是什么呢?假如将步骤3合并到步骤7中,将步骤5和并到步骤8中会不会有什么问题呢?因为在参考书5中介绍的两阶段提交,就是在准备阶段进行业务操作,在第二阶段进行提交或回滚。

带着这个疑惑,我又找到了参考博客2。博客中提到使用X/Open DTP编程的一般方式为:

1. 配置TM,通过TM或者RM提供的方式,把RM注册到TM。可以理解为给TM注册RM作为数据源。一个TM可以注册多个RM。

2. AP从TM获取资源管理器的代理(例如:使用JTA接口,从TM管理的上下文中,获取出这个TM所管理的RM的JDBC连接或JMS连接)

3. AP向TM发起一个全局事务。这时,TM会通知各个RM。XID(全局事务ID)会通知到各个RM。

4. AP通过2中获取的连接,直接操作RM进行业务操作。这时,AP在每次操作时把XID(包括所属分支的信息)传递给RM,RM正是通过这个XID与3步中的XID关联来知道操作和事务的关系的。

5. AP结束全局事务。此时TM会通知RM全局事务结束。

6. 开始二段提交,也就是prepare - commit的过程。

看到这里,大概了解了AP是通过从TM获取的RM的代理来对RM进行业务操作,而业务操作的事务性是通过TM提供的全局事务XID来标识的。不过看到这里还是一头雾水。接着我找到了参考博客3。博客3中给了一个代码示例:

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 MysqlXAConnectionTest {
   public static void main(String[] args) throws SQLException {
      //true表示打印XA语句,,用于调试
      boolean logXaCommands = true;
      // 获得资源管理器操作接口实例 RM1
      Connection conn1 = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "shxx12151022");
      XAConnection xaConn1 = new MysqlXAConnection((com.mysql.jdbc.Connection) conn1, logXaCommands);
      XAResource rm1 = xaConn1.getXAResource();
      // 获得资源管理器操作接口实例 RM2
      Connection conn2 = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root",
            "shxx12151022");
      XAConnection xaConn2 = new MysqlXAConnection((com.mysql.jdbc.Connection) conn2, logXaCommands);
      XAResource rm2 = xaConn2.getXAResource();
      // AP请求TM执行一个分布式事务,TM生成全局事务id
      byte[] gtrid = "g12345".getBytes();
      int formatId = 1;
      try {
         // ==============分别执行RM1和RM2上的事务分支====================
         // TM生成rm1上的事务分支id
         byte[] bqual1 = "b00001".getBytes();
         Xid xid1 = new MysqlXid(gtrid, bqual1, formatId);
         // 执行rm1上的事务分支
         rm1.start(xid1, XAResource.TMNOFLAGS);//One of TMNOFLAGS, TMJOIN, or TMRESUME.
         PreparedStatement ps1 = conn1.prepareStatement("INSERT into user(name) VALUES ('tianshouzhi')");
         ps1.execute();
         rm1.end(xid1, XAResource.TMSUCCESS);
         // TM生成rm2上的事务分支id
         byte[] bqual2 = "b00002".getBytes();
         Xid xid2 = new MysqlXid(gtrid, bqual2, formatId);
         // 执行rm2上的事务分支
         rm2.start(xid2, XAResource.TMNOFLAGS);
         PreparedStatement ps2 = conn2.prepareStatement("INSERT into user(name) VALUES ('wangxiaoxiao')");
         ps2.execute();
         rm2.end(xid2, XAResource.TMSUCCESS);
         // ===================两阶段提交================================
         // phase1:询问所有的RM 准备提交事务分支
         int rm1_prepare = rm1.prepare(xid1);
         int rm2_prepare = rm2.prepare(xid2);
         // phase2:提交所有事务分支
         boolean onePhase = false; //TM判断有2个事务分支,所以不能优化为一阶段提交
         if (rm1_prepare == XAResource.XA_OK
               && rm2_prepare == XAResource.XA_OK
               ) {//所有事务分支都prepare成功,提交所有事务分支
            rm1.commit(xid1, onePhase);
            rm2.commit(xid2, onePhase);
         } else {//如果有事务分支没有成功,则回滚
            rm1.rollback(xid1);
            rm1.rollback(xid2);
         }
      } catch (XAException e) {
         // 如果出现异常,也要进行回滚
         e.printStackTrace();
      }
   }
}

看完这个示例,对XA的协议理解又深了一步,但仍然没有解决前面提到的那个疑惑,有过java连接数据库开发经验的就知道,ps1.execute()和ps2.execute()方法是有返回值的,返回值为bool值。也就是说,代码中的“phase1:询问所有RM”这个操作完全可以通过判断ps1和ps2的execute()方法的返回值来实现,完全没必要单独再执行phase1阶段。

最后,关于这个疑惑,我在参考博客4中似乎得到了答案。参考博客4中描述了二阶段提交的流程示意图:

  1. 在开始一个全局事务之前,涉及的RM必须通过ax_regr(),向TM注册以加入集群;对应的,在没有事务需要处理的时候,RM可以通过ax_unreg()向TM要求注销,离开集群。

  2. TM在对一个RM执行xa_开头的具体操作前,必须先通过xa_open()打开这个RM(本质是建立对话)——这其实也是分配XID的一个行为;与之相应的,TM执行xa_close()来关闭RM。

  3. TM对RM调用的xa_start()和xa_stop()这对组合,一般用于标记局部事务的开头和结尾。这里需要注意的有三点:

  4. 对于同一个RM,根据全局事务的要求,可以前后执行多对组合——俾如说,先标记一个流水账INSERT的局部事务操作,然后再标记账户UPDATE的局部事务操作。

  5. TM执行该组合只是起到标记事务的作用,具体的业务命令是由AP交给RM的。

  6. 该组合除了执行这些标记工作外,其实还能在RM中实现多线程的join/suspend/resume管理。

  7. TM调用RM的xa_prepare()来进行第一阶段,调用xa_commit()或xa_rollback()执行第二阶段。

答案就在步骤4中。步骤4中提到,对于同一个RM,根据全局事务的要求,可以前后执行多对组合。也就是说,对同一个RM,在一个事务中可能会执行多次业务操作,其中有些业务操作可能会成功,有的则可能失败。每次业务操作的结果都会记录在当前XID所标记的事务上下文中,当AP通知TM结束事务时,TM通过两阶段来结束事务:先通过准备阶段询问业务操作是否都成功,然后再决定事务是该提交还是该回滚。因而可以说,DTP模型在使用2PC时,是将第一阶段进一步细分成了两个步骤:其一是业务操作阶段;其二是询问业务操作结果阶段。

参考博客/书:

1. 分布式事务综述 分布式事务综述

2. X/Open DTP模型与XA协议的学习笔记 - G.N.O - 博客园 X/Open DTP模型与XA协议的学习笔记

3. mysql 对XA事务的支持_分布式事务教程_田守枝Java技术博客  mysql 对XA事务的支持

4. 初识Open/X XA - 简书 初识Open/X XA

5. 书《从PAXOS到ZOOKEEPER分布式一致性原理与实践》

6. MySQL的事务实现原理介绍:undo log、redo log、checkpoint和LSN_Saintyyu的博客-CSDN博客 MySQL的事务实现原理介绍:undo log、redo log、checkpoint和LSN

7. 分布式事务概述_分布式事务教程_田守枝Java技术博客  分布式事务概述

8. https://blog.51cto.com/xvjunjie/2420402  分布式事务中的三种解决方案详解

9、分布式事务(三)mysql对XA协议的支持 - 只会一点java - 博客园

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值