如何设计分布式系统-分布式事务-XA?

       如何设计分布式系统-CAP和BASE理论?_技术分子的博客-CSDN博客​​​​​​

     

         什么是事务?

       处理问题整个过程中同时具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),称为事务。

        什么分布式事务?

        分布式事务是涉及两个或多个网络主机的数据库事务。

        XA规范

        XA 是由 X/Open 组织提出的分布式事务规范,XA 规范主要定义了事务协调者(Transaction Manager)和资源管理器(Resource Manager)之间的接口。

        

        DTP(Distributed Transaction Processing) 模型图

       如何设计分布式系统-分布式事务-XA?_第1张图片

  Xa主要规定了RM与TM之间的交互,下面来看下XA规范中定义的RM 和 TM交互的接口:

XA协议二阶段提交的一个流程示意图:

如何设计分布式系统-分布式事务-XA?_第2张图片

       MySQL 从5.0.3开始支持XA规范,且只有InnoDB存储引擎支持。

      如何设计分布式系统-分布式事务-XA?_第3张图片

         

        MySQL支持XA规范后,分为内部XA事务、外部XA事务

       内部XA事务       

       MySQL整体架构分为三层: 网络连接层, 服务层, 存储引擎层。

       在向存储引擎提交数据时(存储引擎层),同时需要将提交的信息写入二进制日志(服务层),这就是一个分布式事务被称为内部XA事务。

        

       外部XA事务

       MySQL数据库外部XA可以用在分布式数据库代理层,实现对MySQL数据库的分布式事务支持,可以提供跨库的分布式事务。当然也就成了外部XA事务的协调者角色。在crash recover时控制悬挂事务是全局commit,或者rollback。

java 使用 MySQL XA 外部事务 demo


   javax.transaction
   jta
   1.1


 

    mysql
    mysql-connector-java
    6.0.6
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://192.168.0.1:3306/test";
        String connStr2 = "jdbc:mysql://192.168.0.2:3306/test";

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

            //数据库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 account_from set money=money - 50 where id=1");
                xaResource1.end(xid1, XAResource.TMSUCCESS);

                //事务分支2关联分支事务sql语句
                xaResource2.start(xid2, XAResource.TMNOFLAGS);
                int update2Result = statement2.executeUpdate("update account_to set money= money + 50 where id=1");
                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);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

通过程序配合MySQL的XA事务功能,可能遇到的问题:

问题一、主备数据可以可能不一致性

        MySQL数据库的主备数据库的同步,通过Binlog的复制完成。而Binlog是MySQL数据库内部XA事务的协调者,并且MySQL数据库为binlog做了优化——binlog不写prepare日志,只写commit日志。

         所有的参与节点prepare完成,在进行xa commit前crash,crash recover如果选择commit此事务。

           由于binlog在prepare阶段未写,因此主库中看来,此分布式事务最终提交了,但是此事务的操作并未写到binlog中,因此也就未能成功复制到备库,从而导致主备库数据不一致的情况出现。

  在MySQL 5.5.16版本中做过测试,这个问题实际存在。crash recover之后,对xa recover返回的事务运行xa commit,对应事务提交,但是操作并未写入binlog,因此无法复制到备库。

        那么是否回滚所有prepare的事务,就可以避免此问题呢?结论是仍旧不行,不仅不能解决问题一,甚至可能引起问题二。

问题二:同一事务,在各参与节点,最终状态不一致(部分提交,部分回滚)。

   所有节点完成prepare,commit阶段部分已经提交 部分机器crash。crash recove 过程选择了 回滚。最终导致同一分布式事务,在各参与节点,最终状态不一致。

结论:

XA无法彻底解决分布式一致问题。

如何设计分布式系统-分布式事务-2PC、3PC?_技术分子的博客-CSDN博客

参考:

分布式事务

XA规范

XA模型

分布式事务如何实现?深入解读 Seata 的 XA 模式

你可能感兴趣的:(分布式概念,分布式,架构,后端,中间件,mysql)