对XA协议的认识

一、何为XA协议     

        XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2、MySQL等这些数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。

二、XA协议的原理

        我们知道,在当一个系统到达了一定的规模的时候,必然就会涉及到分布式事务。而分布式事务有很多种解决方案,比如2PC、3PC、TCC、MQ等。而我们这里说的XA协议就属于2PC的实现方案。对于对并发度要求不高,只要满足事务的特性,那么XA协议就是一个不错的选择,因为它使用起来简单,和普通的事务使用起来没有太大的区别。

       XA协议同样也具备事务的ACID特性。

       XA协议的大致原理如下图

      提交

 

      回滚

    XA协议,其实会遵循以下几个步骤

  • 开启xa事务,XA start <xid>
  • DML语句,即SQL增删改查语句
  • 终止XA事务,XA end <xid>
  • 预提交事务, XA prepare <xid>,这一步是有返回值的
  • 提交,XA commit <xid>,根据prepare操作的返回结果做的处理
  • 回滚,XA rollback <xid>,根据prepare操作的返回结果做的处理

   经过实践证明,在没的执行commit或rollback前,表是被锁着的。commit或rollback之后,锁才会被释放。

   我们需要注意的是,如上图所示,如果不同的事务在第一阶段(即prepare阶段)都是正常的,但是在第二段commit时,由于网络或者其它因素,导致commit动作没有执行成功,那么这个数据就再也没有机会提交,换句话说,就是这个数据丢失了,因为prepare这个动作时候,并没有写日志。但是如典型的XA协议的实现者,atomikos框架就会来写这个日志,它写的的这个日志的格式内容如下

{"id":"192.168.18.1.tm159163155247100001","wasCommitted":true,"participants":[{"uri":"192.168.18.1.tm1","state":"COMMITTING","expires":1591631563078,"resourceName":"test1DataSource"},{"uri":"192.168.18.1.tm2","state":"COMMITTING","expires":1591631563078,"resourceName":"test2DataSource"}]}
{"id":"192.168.18.1.tm159163155247100001","wasCommitted":true,"participants":[{"uri":"192.168.18.1.tm1","state":"TERMINATED","expires":1591631563259,"resourceName":"test1DataSource"},{"uri":"192.168.18.1.tm2","state":"TERMINATED","expires":1591631563259,"resourceName":"test2DataSource"}]}

    只要有日志,数据自然就不会丢失,因为可以根据这个日志来进行恢复。

    除了XA协议好用,能解决分布式事务之外,我们还得清楚,其实XA协议的效率是很低的,因为会存在由于某个数据库的节点的网络等原因,导致prepare迟迟不能返回结果,那个这个时候TM就需要一直等待,直到返回结果为止,如此一来,就拖慢了处理事务的速度。所以XA协议一般用在并发度不高的分布式事务的的场景中。

   基于XA协议实现的事务,demo如下

package com.xiangxue.jack.xa;

import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import com.mysql.jdbc.jdbc2.optional.MysqlXid;

import javax.sql.XAConnection;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.sql.Connection;
import java.sql.Statement;

public class XADemo {
    public static MysqlXADataSource getDataSource(String connStr, String user, String pwd) {
        try {

            MysqlXADataSource ds = new MysqlXADataSource();
            ds.setUrl(connStr);
            ds.setUser(user);
            ds.setPassword(pwd);

            return ds;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    public static void main(String[] arg) {
        String connStr1 = "jdbc:mysql://118.89.107.162:3306/wjq";
        String connStr2 = "jdbc:mysql://118.89.107.162:3307/wjq";

        try {
            //从不同数据库获取数据库数据源
            MysqlXADataSource ds1 = getDataSource(connStr1, "root", "XXXXXXXX");
            MysqlXADataSource ds2 = getDataSource(connStr2, "root", "XXXXXXXX");

            //数据库1获取连接
            XAConnection xaConnection1 = ds1.getXAConnection();
            XAResource xaResource1 = xaConnection1.getXAResource();
            Connection connection1 = xaConnection1.getConnection();
            Statement statement1 = connection1.createStatement();

            //数据库2获取连接
            XAConnection xaConnection2 = ds2.getXAConnection();
            XAResource xaResource2 = xaConnection2.getXAResource();
            Connection connection2 = xaConnection2.getConnection();
            Statement statement2 = connection2.createStatement();

            //创建事务分支的xid
            Xid xid1 = new MysqlXid(new byte[]{0x01}, new byte[]{0x02}, 100);
            Xid xid2 = new MysqlXid(new byte[]{0x011}, new byte[]{0x012}, 100);

            try {
                //事务分支1关联分支事务sql语句
                xaResource1.start(xid1, XAResource.TMNOFLAGS);
                int update1Result = statement1.executeUpdate("UPDATE accounts SET BALANCE = CAST('9700.00' AS DECIMAL) WHERE CUSTOMER_NO = '001'");
                xaResource1.end(xid1, XAResource.TMSUCCESS);

                //事务分支2关联分支事务sql语句
                xaResource2.start(xid2, XAResource.TMNOFLAGS);
                int update2Result = statement2.executeUpdate("INSERT INTO user_purchase_his(CUSTOMER_NO, SERIAL_NO, AMOUNT, CURRENCY, REMARK) "
                        + " VALUES ('001', '20190303204700000001', 200, 'CNY', '购物消费')");
                xaResource2.end(xid2, XAResource.TMSUCCESS);

                // 两阶段提交协议第一阶段
                int ret1 = xaResource1.prepare(xid1);
                int ret2 = xaResource2.prepare(xid2);

                // 两阶段提交协议第二阶段
                if (XAResource.XA_OK == ret1 && XAResource.XA_OK == ret2) {
                    //引擎级别提交
                    xaResource1.commit(xid1, false);
                    xaResource2.commit(xid2, false);

                    System.out.println("reslut1:" + update1Result + ", result2:" + update2Result);
                } else {
                    xaResource1.rollback(xid1);
                    xaResource2.rollback(xid2);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值