XA 分布式事务原理
XA 分布式事务原理
Xa distributed transaction principle
概念 Concept

资源管理器(resource manager):用来管理系统资源,是通向事务资源的途径。数据库就是一种资源管理器。资源管理还应该具有管理事务提交或回滚的能力。 File Explorer: Used to manage system resources, is a way to access transaction resources. The database is a File Explorer. Resource management should also have the ability to manage transaction commits or rollbacks
事务管理器(transaction manager):事务管理器是分布式事务的核心管理者。事务管理器与每个资源管理器(resource manager)进行通信,协调并完成事务的处理。事务的各个分支由唯一命名进行标识 Transaction Manager: The Transaction Manager is the core manager of a distributed transaction. The transaction manager communicates with each File Explorer to coordinate and complete the transaction. Each branch of a transaction is identified by a unique name
Xid 接口 Xid, Xid 接口是 X/Open 事务标识符 XID 结构的 Java 映射。此接口指定三个访问器方法,以检索全局事务格式 ID、全局事务 ID 和分支限定符。Xid 接口供事务管理器和资源管理器使用。此接口对应用程序不可见。 The XID interface XID, the XID interface is a Java mapping of the X/Open transaction identifier XID structure. This interface specifies three accessor methods to retrieve the global transaction format ID, Global Transaction ID, and branch qualifier. The XID interface is used by transaction managers and File Explorer. This interface is not visible to the application
XA 不能自动提交。
Xa does not automatically commit.
分段提交 Section submission
XA需要两阶段提交: prepare 和 commit. XA requires two-phase commit: prepare and commit
第一阶段为 准备(prepare)阶段。即所有的参与者准备执行事务并锁住需要的资源。参与者ready时,向transaction manager报告已准备就绪。 The first stage is the preparation stage. That is, all participants are ready to execute the transaction and lock in the required resources. When the participant is ready, report readiness to the transaction manager
第二阶段为提交阶段(commit)。当transaction manager确认所有参与者都ready后,向所有参与者发送commit命令。
The second phase is the commit phase. When the transaction manager verifies that all participants are ready, the commit command is sent to all participants.
假设有两个Connection, con1, con2, 大体的过程如下 .
Suppose there are two connections, Con 1, Con 2, and the general process is as follows.
-
con1 = XAResouce1.getConnection...
-
con2 = XAResouce2.getConnection...
-
-
con1 do some thing.
-
con2 do some thing.
-
after they finish.
-
-
pre1 = XAResouce1.prepare();
-
pre2 = XAResouce2.prepare();
-
-
if( both pre1 and pre2 are OK){
-
XAResouce1 and 2 commit
-
}else {
-
XAResouce1 and 2 rollback
-
}
事务协调/管理者 Transaction Coordinator/manager
因为XA 事务是基于两阶段提交协议的,所以需要有一个事务协调者(transaction manager)来保证所有的事务参与者都完成了准备工作(第一阶段)。如果事务协调者(transaction manager)收到所有参与者都准备好的消息,就会通知所有的事务都可以提交了(第二阶段)。MySQL 在这个XA事务中扮演的是参与者的角色,而不是事务协调者(transaction manager)。 Because XA transactions are based on a two-phase commit protocol, a transaction manager is required to ensure that all transaction participants are ready (Phase 1) . If the transaction manager receives a message that all participants are ready, it notifies that all transactions are ready to be committed (Phase II) . MySQL acts as a participant, not a transaction manager, in this XA transaction
测试用例 Test Case
-
import com.alibaba.druid.pool.xa.DruidXADataSource;
-
import com.mysql.jdbc.jdbc2.optional.MysqlXid;
-
-
import javax.sql.XAConnection;
-
import javax.transaction.xa.XAException;
-
import javax.transaction.xa.XAResource;
-
import javax.transaction.xa.Xid;
-
import java.io.IOException;
-
import java.sql.Connection;
-
import java.sql.SQLException;
-
import java.sql.Statement;
-
import java.util.Properties;
-
-
-
class DistributeTransaction {
-
-
private Properties props;
-
private String propertyfile = "jdbc.properties";
-
-
private String sql_1 = "delete from test3 where pk_t=3;";
-
private String sql_2 = "INSERT INTO test(name) VALUES('tyz');";
-
-
DistributeTransaction() {
-
Connection connection_1 = null;
-
Connection connection_2 = null;
-
DruidXADataSource xaDataSource_1 = null;
-
DruidXADataSource xaDataSource_2 = null;
-
Xid xid_1 = null;
-
Xid xid_2 = null;
-
XAConnection xaConnection_1 = null;
-
XAConnection xaConnection_2 = null;
-
XAResource xaResource_1 = null;
-
XAResource xaResource_2 = null;
-
-
try {
-
props = new Properties();
-
props.load(getClass().getResourceAsStream(propertyfile));
-
} catch (IOException io) {
-
System.err.println("Error while accessing the properties file (" + propertyfile + "). Abort.");
-
System.exit(1);
-
}
-
-
DruidXADataSource[] xaDataSources = initXADataSource();
-
xaDataSource_1 = xaDataSources[0];
-
xaDataSource_2 = xaDataSources[1];
-
-
XAConnection[] xaConnections = initXAConnection(xaDataSource_1, xaDataSource_2);
-
xaConnection_1 = xaConnections[0];
-
xaConnection_2 = xaConnections[1];
-
-
xaResource_1 = initXAResource(xaConnection_1);
-
xaResource_2 = initXAResource(xaConnection_2);
-
-
connection_1 = getDatabaseConnection(xaConnection_1);
-
connection_2 = getDatabaseConnection(xaConnection_2);
-
-
// create a separate branch for a common transaction
-
Xid[] xids = createXID();
-
xid_1 = xids[0];
-
xid_2 = xids[1];
-
-
try {
-
execBranch(connection_1, xaResource_1, xid_1, sql_1);
-
execBranch(connection_2, xaResource_2, xid_2, sql_2);
-
-
if (prepareCommit(xaResource_1, xid_1) == XAResource.XA_OK &&
-
prepareCommit(xaResource_2, xid_2) == XAResource.XA_OK) {
-
commitBranch(xaResource_1, xid_1);
-
commitBranch(xaResource_2, xid_2);
-
} else {
-
throw new RuntimeException();
-
}
-
} catch (Exception e) {
-
rollbackBranch(xaResource_1, xid_1);
-
rollbackBranch(xaResource_2, xid_2);
-
}
-
}
-
-
DruidXADataSource[] initXADataSource() {
-
System.out.print("Create a XADataSource_1 data source: ");
-
DruidXADataSource xaDataSource_1 = new DruidXADataSource();
-
xaDataSource_1.setDbType(props.getProperty("db1.dbtype"));
-
xaDataSource_1.setUrl(props.getProperty("db1.url"));
-
xaDataSource_1.setUsername(props.getProperty("db1.username"));
-
xaDataSource_1.setPassword(props.getProperty("db1.password"));
-
System.out.println("Okay.");
-
-
System.out.print("Create a XADataSource_2 data source: ");
-
DruidXADataSource xaDataSource_2 = new DruidXADataSource();
-
xaDataSource_2.setDbType(props.getProperty("db2.dbtype"));
-
xaDataSource_2.setUrl(props.getProperty("db2.url"));
-
xaDataSource_2.setUsername(props.getProperty("db2.username"));
-
xaDataSource_2.setPassword(props.getProperty("db2.password"));
-
System.out.println("Okay.");
-
return new DruidXADataSource[]{xaDataSource_1, xaDataSource_2};
-
}
-
-
XAConnection[] initXAConnection(DruidXADataSource xaDataSource_1, DruidXADataSource xaDataSource_2) {
-
XAConnection xaconn_1 = null;
-
XAConnection xaconn_2 = null;
-
try {
-
System.out.print("Set up DB_1 XA connection: ");
-
xaconn_1 = xaDataSource_1.getXAConnection();
-
System.out.println("Okay.");
-
-
System.out.print("Set up DB_2 XA connection: ");
-
xaconn_2 = xaDataSource_2.getXAConnection();
-
System.out.println("Okay.");
-
} catch (SQLException e) {
-
sqlerr(e);
-
}
-
return new XAConnection[]{xaconn_1, xaconn_2};
-
}
-
-
XAResource initXAResource(XAConnection xacon) {
-
XAResource xares = null;
-
try {
-
System.out.print("Setting up a XA resource: ");
-
xares = xacon.getXAResource();
-
System.out.println("Okay.");
-
} catch (SQLException e) {
-
sqlerr(e);
-
}
-
return xares;
-
}
-
-
Connection getDatabaseConnection(XAConnection xacon) {
-
Connection con = null;
-
try {
-
System.out.print("Establish database connection: ");
-
con = xacon.getConnection();
-
con.setAutoCommit(false);
-
System.out.println("Okay.");
-
} catch (SQLException e) {
-
sqlerr(e);
-
}
-
return con;
-
}
-
-
Xid[] createXID() {
-
Xid xid_1 = null;
-
byte[] gid_1 = new byte[1];
-
byte[] bid_1 = new byte[1];
-
gid_1[0] = (Byte.decode(props.getProperty("xid.global"))).byteValue();
-
bid_1[0] = (Byte.decode(props.getProperty("xid.branch.db_1"))).byteValue();
-
System.out.print("Creating an XID (" + Byte.toString(gid_1[0]) + ", " + Byte.toString(bid_1[0]) + ") for DB_1: ");
-
xid_1 = new MysqlXid(gid_1, bid_1, 0);
-
System.out.println("Okay.");
-
-
Xid xid_2 = null;
-
byte[] gid_2 = new byte[1];
-
byte[] bid_2 = new byte[1];
-
gid_2[0] = (Byte.decode(props.getProperty("xid.global"))).byteValue();
-
bid_2[0] = (Byte.decode(props.getProperty("xid.branch.db_2"))).byteValue();
-
System.out.print("Creating an XID (" + Byte.toString(gid_2[0]) + ", " + Byte.toString(bid_2[0]) + ") for DB_2: ");
-
xid_2 = new MysqlXid(gid_2, bid_2, 0);
-
System.out.println("Okay.");
-
return new Xid[]{xid_1, xid_2};
-
}
-
-
void execBranch(Connection con, XAResource xares, Xid xid, String sql) {
-
try {
-
xares.start(xid, XAResource.TMNOFLAGS);
-
Statement stmt = con.createStatement();
-
stmt.executeUpdate(sql);
-
xares.end(xid, XAResource.TMSUCCESS);
-
} catch (XAException e) {
-
System.err.println("XA exception caught:");
-
System.err.println("Cause : " + e.getCause());
-
System.err.println("Message: " + e.getMessage());
-
e.printStackTrace();
-
throw new RuntimeException(e);
-
} catch (SQLException e) {
-
sqlerr(e);
-
throw new RuntimeException(e);
-
}
-
}
-
-
int prepareCommit(XAResource xares, Xid xid) {
-
int rc = 0;
-
System.out.print("Prepare XA branch (" +
-
Byte.toString((xid.getGlobalTransactionId())[0]) + ", " +
-
Byte.toString((xid.getBranchQualifier())[0]) + "): ");
-
try {
-
xares.prepare(xid);
-
} catch (XAException e) {
-
xaerr(e);
-
throw new RuntimeException(e);
-
}
-
System.out.println("Okay.");
-
return rc;
-
}
-
-
void commitBranch(XAResource xares, Xid xid) {
-
System.out.print("Commit XA branch (" +
-
Byte.toString((xid.getGlobalTransactionId())[0]) + ", " +
-
Byte.toString((xid.getBranchQualifier())[0]) + "): ");
-
try {
-
// second parameter is 'false' since we have a two phase commit
-
xares.commit(xid, false);
-
} catch (XAException e) {
-
xaerr(e);
-
throw new RuntimeException(e);
-
}
-
System.out.println("Okay.");
-
}
-
-
void rollbackBranch(XAResource xares, Xid xid) {
-
System.out.print("Rollback XA branch (" +
-
Byte.toString((xid.getGlobalTransactionId())[0]) + ", " +
-
Byte.toString((xid.getBranchQualifier())[0]) + "): ");
-
try {
-
xares.rollback(xid);
-
} catch (XAException e) {
-
xaerr(e);
-
throw new RuntimeException(e);
-
}
-
System.out.println("Okay.");
-
}
-
-
void sqlerr(SQLException exception) {
-
System.err.println("FAILED.");
-
while (exception != null) {
-
System.err.println("==> SQL Exception caught");
-
System.err.println("--> SQLCODE : " + exception.getErrorCode());
-
System.err.println("--> SQLSTATE: " + exception.getSQLState());
-
System.err.println("--> Message : " + exception.getMessage());
-
exception = exception.getNextException();
-
}
-
}
-
-
void xaerr(XAException exception) {
-
System.err.println("FAILED.");
-
System.err.println("==> XA Exception caught");
-
System.err.println("--> Cause : " + exception.getCause());
-
System.err.println("--> Message: " + exception.getMessage());
-
exception.printStackTrace();
-
}
-
-
public static void main (String args[]) {
-
new DistributeTransaction();
-
}
-
-
}
XA性能局限性 XA performance limitations
效率低下,准备阶段的成本持久,全局事务状态的成本持久,性能与本地事务相差10倍左右;
提交前,出现故障难以恢复和隔离问题。
Low efficiency, long preparation cost, long global transaction state cost, performance and local transaction difference about 10 times; before the submission, the failure is difficult to recover and isolation.

浙公网安备 33010602011771号