写在前面
seata-XA模式是利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种解决方案。
跟AT、 TCC模式对比,XA拥有全局事务的隔离性,并且基于数据库现有的XA能力,seata可以对全局事务的实现进行“打薄”(比如undolog的保存重放删除、全局锁等都可以不做),并且适配已经在使用XA模式的业务形态。但缺点显而易见,XA是三种模式中性能最差的。
在代码实现中,AT模式与XA模式大同小异,都是一样通过TM、RM、TC三种协同处理全局事务。只不过TCC模式下分支事务的注册、提交或回滚是调用自己实现的TCC行为类进行处理。
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的执行与全局事务的处理。
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端的处理不再细说。