seata源码解析系列-XA模式

写在前面

seata-XA模式是利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种解决方案。

跟AT、 TCC模式对比,XA拥有全局事务的隔离性,并且基于数据库现有的XA能力,seata可以对全局事务的实现进行“打薄”(比如undolog的保存重放删除、全局锁等都可以不做),并且适配已经在使用XA模式的业务形态。但缺点显而易见,XA是三种模式中性能最差的。

在代码实现中,AT模式与XA模式大同小异,都是一样通过TM、RM、TC三种协同处理全局事务。只不过TCC模式下分支事务的注册、提交或回滚是调用自己实现的TCC行为类进行处理。

seata源码解析系列-XA模式_第1张图片
XA模式三端协同处理图

XA-TM端分析

首先说明,XA流程同样需要打上@GolbalTransaction注解的TM范围中完成分支注册、提交或回滚,不在细说。

XA-RM端分析

XA模式同样是通过自上而下的动态代理数据库相关类(Database、Connection、Statement),在原方法执行过程中对sql的执行进行拦截处理。
首先AT-RM端会将原DataSource进行代理形成DataSourceProxyXA,这样可以通过DataSourceProxyXA从原DataSource获取Connection进行代理形成XAConnection封装的ConnectionProxyXA,最后可以通过ConnectionProxyXA获取Statement进行代理形成StatementProxyXA。由ConnectionProxyXA与StatementProxyXA接管sql的执行与全局事务的处理。


seata源码解析系列-XA模式_第2张图片
AT与XA模式的代理对比
    public DataSourceProxyXA(DataSource dataSource, String resourceGroupId) {
        if (dataSource instanceof SeataDataSourceProxy) {
            LOGGER.info("Unwrap the data source, because the type is: {}", dataSource.getClass().getName());
            dataSource = ((SeataDataSourceProxy) dataSource).getTargetDataSource();
        }
        this.dataSource = dataSource;
        this.branchType = BranchType.XA;
        JdbcUtils.initDataSourceResource(this, dataSource, resourceGroupId);

        //Set the default branch type to 'XA' in the RootContext.
        RootContext.setDefaultBranchType(this.getBranchType());
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        Connection connection = dataSource.getConnection(username, password);
        return getConnectionProxy(connection);
    }

    @Override
    public Statement createStatement() throws SQLException {
        Statement targetStatement = originalConnection.createStatement();
        return new StatementProxyXA(this, targetStatement);
    }

同样的,看StatementProxyXA的方法都被ExecuteTemplateXA执行模板封装执行。

    @Override
    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
        return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.executeUpdate((String)args[0], (int[])args[1]), targetStatement, sql, columnIndexes);
    }

来到ExecuteTemplateXA,同样会执行先开门再关门操作,调用 connectionProxyXA.setAutoCommit(false)先开门,执行原sql,再根据执行结果调用connectionProxyXA.commit或者connectionProxyXA.rollback,最后调用connectionProxyXA.setAutoCommit(true)进行关门。

    public static  T execute(AbstractConnectionProxyXA connectionProxyXA,
                                                     StatementCallback statementCallback,
                                                     S targetStatement,
                                                     Object... args) throws SQLException {
        boolean autoCommitStatus = connectionProxyXA.getAutoCommit();
        if (autoCommitStatus) {
            // XA Start
            connectionProxyXA.setAutoCommit(false);
        }
        try {
            T res = null;
            try {
                // execute SQL
                res = statementCallback.execute(targetStatement, args);

            } catch (Throwable ex) {
                if (autoCommitStatus) {
                    // XA End & Rollback
                    try {
                        connectionProxyXA.rollback();
                    } catch (SQLException sqle) {
                        // log and ignore the rollback failure.
                        LOGGER.warn(
                            "Failed to rollback xa branch of " + connectionProxyXA.xid +
                                "(caused by SQL execution failure(" + ex.getMessage() + ") since " + sqle.getMessage(),
                            sqle);
                    }
                }

                if (ex instanceof SQLException) {
                    throw ex;
                } else {
                    throw new SQLException(ex);
                }

            }
            if (autoCommitStatus) {
                try {
                    // XA End & Prepare
                    connectionProxyXA.commit();
                } catch (Throwable ex) {
                    LOGGER.warn(
                        "Failed to commit xa branch of " + connectionProxyXA.xid + ") since " + ex.getMessage(),
                        ex);
                    // XA End & Rollback
                    if (!(ex instanceof SQLException) || AbstractConnectionProxyXA.SQLSTATE_XA_NOT_END != ((SQLException) ex).getSQLState()) {
                        try {
                            connectionProxyXA.rollback();
                        } catch (SQLException sqle) {
                            // log and ignore the rollback failure.
                            LOGGER.warn(
                                "Failed to rollback xa branch of " + connectionProxyXA.xid +
                                    "(caused by commit failure(" + ex.getMessage() + ") since " + sqle.getMessage(),
                                sqle);
                        }
                    }

                    if (ex instanceof SQLException) {
                        throw ex;
                    } else {
                        throw new SQLException(ex);
                    }

                }
            }
            return res;
        } finally {
            if (autoCommitStatus) {
                connectionProxyXA.setAutoCommit(true);
            }

        }
    }

1.来到connectionProxyXA.setAutoCommit(false),内部的操作其实并不只是设置自动提交状态为否,还带有向TC注册分支的操作,然后调用
xaResource.start,启动XA分支事务。

 @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        if (currentAutoCommitStatus == autoCommit) {
            return;
        }
        if (autoCommit) {
            // According to JDBC spec:
            // If this method is called during a transaction and the
            // auto-commit mode is changed, the transaction is committed.
            if (xaActive) {
                commit();
            }
        } else {
            if (xaActive) {
                throw new SQLException("should NEVER happen: setAutoCommit from true to false while xa branch is active");
            }
            // Start a XA branch
            long branchId = 0L;
            try {
                // 1. register branch to TC then get the branchId
                branchId = DefaultResourceManager.get().branchRegister(BranchType.XA, resource.getResourceId(), null, xid, null,
                    null);
            } catch (TransactionException te) {
                cleanXABranchContext();
                throw new SQLException("failed to register xa branch " + xid + " since " + te.getCode() + ":" + te.getMessage(), te);
            }
            // 2. build XA-Xid with xid and branchId
            this.xaBranchXid = XAXidBuilder.build(xid, branchId);
            try {
                // 3. XA Start
                xaResource.start(this.xaBranchXid, XAResource.TMNOFLAGS);
            } catch (XAException e) {
                cleanXABranchContext();
                throw new SQLException("failed to start xa branch " + xid + " since " + e.getMessage(), e);
            }
            // 4. XA is active
            this.xaActive = true;

        }

        currentAutoCommitStatus = autoCommit;
    }

2.statementCallback.execute,将在XA分支事务的范围下执行sql,不再细说。

3调用 connectionProxyXA.commit或者connectionProxyXA.rollback。

先看connectionProxyXA.commit,由上文可知,到这个方法处理前,已经完成了xaResource.start开始XA分支事务与执行sql,因此commit方法需要调用xaResource.end表明执行成功结束XA分支事务,并且调用xaResource.prepare方法,保持xa连接,将等待TC全局提交的回调,最后清除上下文。

  @Override
    public void commit() throws SQLException {
        if (currentAutoCommitStatus) {
            // Ignore the committing on an autocommit session.
            return;
        }
        if (!xaActive || this.xaBranchXid == null) {
            throw new SQLException("should NOT commit on an inactive session", SQLSTATE_XA_NOT_END);
        }
        try {
            // XA End: Success
            xaResource.end(xaBranchXid, XAResource.TMSUCCESS);
            // XA Prepare
            xaResource.prepare(xaBranchXid);
            // Keep the Connection if necessary
            keepIfNecessary();
        } catch (XAException xe) {
            try {
                // Branch Report to TC: Failed
                DefaultResourceManager.get().branchReport(BranchType.XA, xid, xaBranchXid.getBranchId(),
                    BranchStatus.PhaseOne_Failed, null);
            } catch (TransactionException te) {
                LOGGER.warn("Failed to report XA branch commit-failure on " + xid + "-" + xaBranchXid.getBranchId()
                    + " since " + te.getCode() + ":" + te.getMessage() + " and XAException:" + xe.getMessage());

            }
            throw new SQLException(
                "Failed to end(TMSUCCESS)/prepare xa branch on " + xid + "-" + xaBranchXid.getBranchId() + " since " + xe
                    .getMessage(), xe);
        } finally {
            cleanXABranchContext();
        }
    }

再看connectionProxyXA.rollback,xa分支事务回滚需要向TC报告分支事务执行失败,然后需要调用xaResource.end表明执行失败结束XA分支事务,最后清除上下文。

  @Override
    public void rollback() throws SQLException {
        if (currentAutoCommitStatus) {
            // Ignore the committing on an autocommit session.
            return;
        }
        if (!xaActive || this.xaBranchXid == null) {
            throw new SQLException("should NOT rollback on an inactive session");
        }
        try {
            // Branch Report to TC
            DefaultResourceManager.get().branchReport(BranchType.XA, xid, xaBranchXid.getBranchId(), BranchStatus.PhaseOne_Failed, null);

        } catch (TransactionException te) {
            // log and ignore the report failure
            LOGGER.warn("Failed to report XA branch rollback on " + xid + "-" + xaBranchXid.getBranchId()
                + " since " + te.getCode() + ":" + te.getMessage());
        }
        try {
            // XA End: Fail
            xaResource.end(xaBranchXid, XAResource.TMFAIL);
        } catch (XAException xe) {
            throw new SQLException(
                "Failed to end(TMFAIL) xa branch on " + xid + "-" + xaBranchXid.getBranchId() + " since " + xe
                    .getMessage(), xe);
        } finally {
            cleanXABranchContext();
        }
    }

TC的全局提交或回滚回调会流经ResourceManagerXA进行处理,都会进入到finishBranch方法,如果是全局提交回调则调用connectionProxyXA.xaCommit,否则调用connectionProxyXA.xaRollback,报错则会通知TC进行补偿重试。

private BranchStatus finishBranch(boolean committed, BranchType branchType, String xid, long branchId, String resourceId,
                                       String applicationData) throws TransactionException {
        XAXid xaBranchXid = XAXidBuilder.build(xid, branchId);
        Resource resource = dataSourceCache.get(resourceId);
        if (resource instanceof AbstractDataSourceProxyXA) {
            try (ConnectionProxyXA connectionProxyXA = ((AbstractDataSourceProxyXA)resource).getConnectionForXAFinish(xaBranchXid)) {
                if (committed) {
                    connectionProxyXA.xaCommit(xid, branchId, applicationData);
                    LOGGER.info(xaBranchXid + " was committed.");
                    return BranchStatus.PhaseTwo_Committed;
                } else {
                    connectionProxyXA.xaRollback(xid, branchId, applicationData);
                    LOGGER.info(xaBranchXid + " was rolled back.");
                    return BranchStatus.PhaseTwo_Rollbacked;
                }
            } catch (XAException | SQLException sqle) {
                if (sqle instanceof XAException) {
                    if (((XAException)sqle).errorCode == XAException.XAER_NOTA) {
                        if (committed) {
                            return BranchStatus.PhaseTwo_Committed;
                        } else {
                            return BranchStatus.PhaseTwo_Rollbacked;
                        }
                    }
                }
                if (committed) {
                    LOGGER.info(xaBranchXid + " commit failed since " + sqle.getMessage(), sqle);
                    // FIXME: case of PhaseTwo_CommitFailed_Unretryable
                    return BranchStatus.PhaseTwo_CommitFailed_Retryable;
                } else {
                    LOGGER.info(xaBranchXid + " rollback failed since " + sqle.getMessage(), sqle);
                    // FIXME: case of PhaseTwo_RollbackFailed_Unretryable
                    return BranchStatus.PhaseTwo_RollbackFailed_Retryable;
                }
            }
        } else {
            LOGGER.error("Unknown Resource for XA resource " + resourceId + " " + resource);
            if (committed) {
                return BranchStatus.PhaseTwo_CommitFailed_Unretryable;
            } else {
                return BranchStatus.PhaseTwo_RollbackFailed_Unretryable;
            }
        }
    }

connectionProxyXA的xaCommit与xaRollback,会分别调用xaResource.commit与xaResource.rollback方法,完成二阶段的提交或回滚,并且释放缓存的XA连接。

 public void xaCommit(String xid, long branchId, String applicationData) throws XAException {
        XAXid xaXid = XAXidBuilder.build(xid, branchId);
        xaResource.commit(xaXid, false);
        releaseIfNecessary();

    }

 public void xaRollback(String xid, long branchId, String applicationData) throws XAException {
        XAXid xaXid = XAXidBuilder.build(xid, branchId);
        xaResource.rollback(xaXid);
        releaseIfNecessary();

    }

XA-TC端分析

同样的,RM端分支注册会创建BatchSession保存在数据库,TM执行全局事务的提交或回滚时也会进行回调与补偿处理,TC端的处理不再细说。

你可能感兴趣的:(seata源码解析系列-XA模式)