XA分布式事务

概念

关键词

概念
XA XA是指X/Open Distributed Transaction Processing(DTP)协议规范中定义的一种事务处理标准。XA协议规范定义了事务管理器(Transaction Manager)资源管理器(Resource Manager)之间的接口规范,使得不同的数据库系统和中间件可以支持分布式事务的实现,确保事务的正确性和一致性。XA协议规范包含了两阶段提交(2PC)的流程,2PC是指分布式事务的两个阶段:准备阶段提交阶段。在准备阶段,事务管理器向所有参与者(Resource Manager)发送prepare请求,每个参与者都将事务结果保存在本地日志中,等待最终提交命令。在提交阶段,如果所有参与者都能够正常响应提交请求,则最终提交命令会被发送给所有参与者,此时分布式事务被提交成功,否则分布式事务被回滚。

结构

XA分布式事务_第1张图片

逻辑

比如,现在要从支付宝向余额宝转5000元,那么就需要从支付宝扣5000元,同时向余额宝加5000元,并且保证这两个操作在同一个事务中。由于支付宝与余额宝分属于各自不同的数据库,就需要进行分布式事务。当需要开始两阶段提交时,首先将请求交给事务协调器。事务协调器在收到请求以后,就会同时向事务的各方发送各自的请求,比如支付宝扣5000元,余额宝加5000元。但是,事务的各方在执行操作时,只操作不提交(只写了日志,没有commit),这就是两阶段提交的第一阶段。当事务的各方成功完成各自的操作并反馈给事务协调器后,开始进入第二阶段(commit)。

只操作不提交,就意味着,事务的各方还是处于更新前的状态。当事务的各方成功完成各自的操作并进入第二阶段时,事务协调器就会同时发出提交命令。众所周知,数据库的提交操作是在一瞬间就执行完了的,我们达到的效果就是,在那一瞬间事务的各方就由更新前状态转变为了更新后状态,从而保障了事务的一致性。这就是“两阶段提交”。

场景

采用两阶段提交,在第一阶段是“只操作不提交”,那么如果事务中的某一方执行失败,则同时回滚,从而保障了事务一致性。然而,在这个过程中,假设支付宝早早就完成了自己的操作,它也只能处于锁定状态,等待余额宝操作完成。在这段时间里,由于处于锁定状态,其他用户就不能操作,只能等待。如果支付宝等待了半天,等来的却是余额宝执行失败,不得不回滚,那么支付宝的等待既影响了系统性能,又影响了系统整体的吞吐量。因此,系统需要通过设计,有效提高事务的成功率,即“要么就不做,要做就必须要成功”。因此,在两阶段提交的基础上,在第一阶段的前面再增加一个校验的阶段,校验事务的各方是否能完成各自的事务,这就是“三阶段提交”。如支付宝账号没有5000元钱,或者余额宝账号无效,那么就没有必要执行该事务了。

XA分布式事务_第2张图片

降低失败的概率

XA分布式事务_第3张图片

优缺点

维度 优点 缺点
性能 适合简单业务场景,对于性能要求不高 “只操作不提交”会锁定数据库资源一段时间,其他请求只能等待,当业务复杂或者分库较多的场景下,会成为性能瓶颈
XA支持

Oracle数据库

IBM DB2数据库

Microsoft SQL Server数据库

MySQL数据库

PostgreSQL数据库

Sybase Adaptive Server Enterprise(ASE)

Informix Dynamic Server

SAP HANA

Teradata

数据一致性 当二阶段提交某一个节点出现异常,无法回滚已经提交的数据
复杂度 简单

网络连接断开,或者数据库异常,事务协调器异常流程中断,需要有监控告警,人工接入

XA分布式事务_第4张图片

示例

此示例包括以下步骤:

  1. 创建两个MySQL数据库的XA数据源对象。
  2. 获取两个数据库的XA连接和JDBC连接。
  3. 开始分布式事务并创建XID
  4. 在两个数据库上执行SQL语句并提交事务。
  5. 结束分布式事务并关闭连接。

此代码示例使用MySQL数据库的XA协议和Java事务API,演示了如何使用XA事务管理器在两个数据库之间执行分布式事务。

package org.example;

import com.mysql.cj.jdbc.MysqlXADataSource;

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

public class XATransactionExample {

    public static void main(String[] args) throws SQLException {
        XADataSource ds1 = getDataSource("jdbc:mysql://localhost:3306/testdb", "user1", "password1");
        XADataSource ds2 = getDataSource("jdbc:mysql://localhost:3306/testdb", "user2", "password2");
        Connection conn1 = null;
        Connection conn2 = null;
        XAConnection xaConn1 = null;
        XAConnection xaConn2 = null;
        try {
            xaConn1 = ds1.getXAConnection();
            xaConn2 = ds2.getXAConnection();
            conn1 = xaConn1.getConnection();
            conn2 = xaConn2.getConnection();
            conn1.setAutoCommit(false);
            conn2.setAutoCommit(false);
            XAResource xaRes1 = xaConn1.getXAResource();
            XAResource xaRes2 = xaConn2.getXAResource();
            // create Xid
            byte[] gid = new byte[]{0x01};
            byte[] bqual1 = new byte[]{0x01};
            byte[] bqual2 = new byte[]{0x02};
            Xid xid1 = new MyXid(0x1234, gid, bqual1);
            Xid xid2 = new MyXid(0x5678, gid, bqual2);
            // start XA transaction
            xaRes1.start(xid1, XAResource.TMNOFLAGS);
            xaRes2.start(xid2, XAResource.TMNOFLAGS);
            // update account balance
            Statement stmt1 = conn1.createStatement();
            stmt1.executeUpdate("UPDATE account SET balance=balance-100 WHERE id=1");
            Statement stmt2 = conn2.createStatement();
            stmt2.executeUpdate("UPDATE account SET balance=balance+100 WHERE id=2");
            // prepare XA transaction
            int ret1 = xaRes1.prepare(xid1);
            int ret2 = xaRes2.prepare(xid2);
            if (ret1 == XAResource.XA_OK && ret2 == XAResource.XA_OK) {
                // commit XA transaction
                xaRes1.commit(xid1, false);
                xaRes2.commit(xid2, false);
            } else {
                // rollback XA transaction
                xaRes1.rollback(xid1);
                xaRes2.rollback(xid2);
            }
        } catch (SQLException ex) {
            ex.printStackTrace();
        } catch (XAException ex) {
            ex.printStackTrace();
        } finally {
            try { if (xaConn1 != null) xaConn1.close(); } catch (SQLException ex) {}
            try { if (xaConn2 != null) xaConn2.close(); } catch (SQLException ex) {}
        }
    }
    private static XADataSource getDataSource(String url, String user, String password) throws SQLException {
        MysqlXADataSource ds = new MysqlXADataSource();
        ds.setURL(url);
        ds.setUser(user);
        ds.setPassword(password);
        ds.setReconnectAtTxEnd(false);
        return ds;
    }
    static class MyXid implements Xid {
        private int formatId;
        private byte[] globalTransactionId;
        private byte[] branchQualifier;
        public MyXid(int formatId, byte[] globalTransactionId, byte[] branchQualifier) {
            this.formatId = formatId;
            this.globalTransactionId = globalTransactionId;
            this.branchQualifier = branchQualifier;
        }
        public int getFormatId() {
            return formatId;
        }
        public byte[] getGlobalTransactionId() {
            return globalTransactionId;
        }
        public byte[] getBranchQualifier() {
            return branchQualifier;
        }
    }
}

maven依赖


        
        
            mysql
            mysql-connector-java
            8.0.28
        

你可能感兴趣的:(分布式事务,分布式,事务,XA)