在具体的 Demo 之前,先来补充一点 XA 事务的知识: DTP 模型与 XA 规范。
DTP 模型与 XA 规范是由 X/Open 维护,也就是现在的 open group,官方网址:http://www.opengroup.org/。open group 是一个独立的组织,主要负责制定各种行业技术标准。由各大知名公司或者厂商进行支持,主要有如下公司:
open group 目前有八家公司,华为就是其中的一家。在分布式事务处理(Distributed Transaction Processing,简称DTP)方面,X/Open主要提供了以下参考文档:
DTP 模型
在《Distributed Transaction Processing: Reference Model 》 第3版中,规定了构成 DTP 模型的 5个基本元素:
DTP 模型元素更深层次的东西可以参考 opengroup 的文档,接下来聊一聊 DTP 实例,一个 DTP 实例至少包含 AP、RMs、TM 三部分。如下图所示:
我们可以看出 AP、RMs、TM 三者之间都是有交互的,大概流程如下:
那什么是 XA 协议呢?XA 规范是定义交互接口,从上面的图中可以看出,整个 DTP 中,有三个交互接口,XA 规范主要是 TM 和 RMs 之间。下面这张图好理解一些:
好了,关于 DTP 模型与 XA 规范就聊这么多,具体的可以查看 opengroup 提供的文档,下面就用我们熟悉的 MySQL 数据库来实现一个 XA 事务协议第二阶段提交。
MySQL 从5.0.3开始支持XA分布式事务,且只有InnoDB存储引擎支持。如下图:
其他的我就不说了,这里我提一下 XA 事务状态,一个完整的事务流程如下:
总结一下,XA 事务, 通过 Start 启动一个 XA 事务,并且被置为 Active 状态,处在 active 状态的事务可以执行 SQL 语句,通过 END 方法将 XA 事务置为 IDLE 状态。处于 IDLE 状态可以执行 PREPARE 操作或者 COMMIT…ONE PHASE 操作,也就是二阶段提交中的第一阶段,PREPARED 状态的 XA事务的时候就可以 Commit 或者 RollBack,也就是二阶段提交的第二阶段。
可能你注意到了上面有一个 XID 值,简单的讲一下,MySQL 中使用xid来作为一个事务分支的标识符。关于 xid 在 XA 规范中有定义,XA规范定义了一个xid由4个部分组成:
好了,关于 XA 事务就 BB 这么多了,接下来,我们通过一个实例,来实现一把基于 XA 事务协议第二阶段提交。
场景: 模拟现金 + 红包组合支付,假设我们购买了 100 块钱的东西,90块使用现金支付,10 块红包支付,现金和红包处在不同的库。
假设: 现在有两个库:xa_account(账户库,现金库)、xa_red_account(红包库)。两个库下面都有一张 account 表,account 表中的字段也比较简单,就 id、user_id、balance_amount 三个字段,SQL 我就不贴了。
好了,具体代码如下:
public class XaDemo {
public static void main(String[] args) throws Exception{
// 是否开启日志
boolean logXaCommands = true;
// 获取账户库的 rm(ap做的事情)
Connection accountConn = DriverManager.getConnection("jdbc:mysql://106.12.12.xxxx:3306/xa_account?useUnicode=true&characterEncoding=utf8","root","xxxxx");
XAConnection accConn = new MysqlXAConnection((JdbcConnection) accountConn, logXaCommands);
XAResource accountRm = accConn.getXAResource();
// 获取红包库的RM
Connection redConn = DriverManager.getConnection("jdbc:mysql://106.12.12.xxxx:3306/xa_red_account?useUnicode=true&characterEncoding=utf8","root","xxxxxx");
XAConnection Conn2 = new MysqlXAConnection((JdbcConnection) redConn, logXaCommands);
XAResource redRm = Conn2.getXAResource();
// XA 事务开始了
// 全局事务
byte[] globalId = UUID.randomUUID().toString().getBytes();
// 就一个标识
int formatId = 1;
// 账户的分支事务
byte[] accBqual = UUID.randomUUID().toString().getBytes();;
Xid xid = new MysqlXid(globalId, accBqual, formatId);
// 红包分支事务
byte[] redBqual = UUID.randomUUID().toString().getBytes();;
Xid xid1 = new MysqlXid(globalId, redBqual, formatId);
try {
// 账号事务开始 此时状态:ACTIVE
accountRm.start(xid, XAResource.TMNOFLAGS);
// 模拟业务
String sql = "update account set balance_amount=balance_amount-90 where user_id=1";
PreparedStatement ps1 = accountConn.prepareStatement(sql);
ps1.execute();
accountRm.end(xid, XAResource.TMSUCCESS);
// 账号 XA 事务 此时状态:IDLE
// 红包分支事务开始
redRm.start(xid1, XAResource.TMNOFLAGS);
// 模拟业务
String sql1 = "update account set balance_amount=balance_amount-10 where user_id=1";
PreparedStatement ps2 = redConn.prepareStatement(sql1);
ps2.execute();
redRm.end(xid1, XAResource.TMSUCCESS);
// 第一阶段:准备提交
int rm1_prepare = accountRm.prepare(xid);
int rm2_prepare = redRm.prepare(xid1);
// XA 事务 此时状态:PREPARED
// 第二阶段:TM 根据第一阶段的情况决定是提交还是回滚
boolean //TM判断有2个事务分支,所以不能优化为一阶段提交
if (rm1_prepare == XAResource.XA_OK && rm2_prepare == XAResource.XA_OK) {
accountRm.commit(xid, onePhase);
redRm.commit(xid1, onePhase);
} else {
accountRm.rollback(xid);
redRm.rollback(xid1);
}
} catch (Exception e) {
// 出现异常,回滚
accountRm.rollback(xid);
redRm.rollback(xid1);
e.printStackTrace();
}
}
}
运行程序,可以看到如下结果:
从图中可以清楚看出 XA 事务两阶段提交过程,更多细节请查阅 MySQL 数据库 XA Transactions 模块。
今天的分享就这些,希望这篇文章对你的学习或者工作有所帮助.
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。