Shardingsphere整合Atomikos對XA分佈式事務的支持
Apache ShardingSphere 是一套開源的分佈式數據庫中間件解決方案組成的生態圈,它由 JDBC、Proxy 和 Sidecar(規劃中)這 3 款相互獨立,卻又能夠混合部署配合使用的產品組成。 它們均提供標準化的數據分片、分佈式事務和數據庫治理功能,可適用於如 Java 同構、異構語言、雲原生等各種多樣化的應用場景。
ShardingSphere 已於2020年4月16日成爲 Apache 軟件基金會的頂級項目。
咋們話不多,接上篇,我們直接進入正題。
Atomikos簡單介紹
Atomikos(https://www.atomikos.com/),其實是一家公司的名字,提供了基於JTA規範的XA分佈式事務TM的實現
。其旗下最著名的產品就是事務管理器。產品分兩個版本:
-
TransactionEssentials:開源的免費產品;
-
ExtremeTransactions:上商業版,需要收費。
這兩個產品的關係如下圖所示:
ExtremeTransactions在TransactionEssentials的基礎上額外提供了以下功能(重要的):
-
支持TCC:這是一種柔性事務
-
支持通過RMI、IIOP、SOAP這些遠程過程調用技術,進行事務傳播。
-
事務日誌雲存儲,雲端對事務進行恢復,並且提供了完善的管理後臺。
org.apache.shardingsphere.transaction.xa.XAShardingTransactionManager詳解
我們簡單的來回顧下org.apache.shardingsphere.transaction.spi.ShardingTransactionManager
public interface ShardingTransactionManager extends AutoCloseable {
/**
* Initialize sharding transaction manager.
*
* @param databaseType database type
* @param resourceDataSources resource data sources
*/
void init(DatabaseType databaseType, Collection<ResourceDataSource> resourceDataSources);
/**
* Get transaction type.
*
* @return transaction type
*/
TransactionType getTransactionType();
/**
* Judge is in transaction or not.
*
* @return in transaction or not
*/
boolean isInTransaction();
/**
* Get transactional connection.
*
* @param dataSourceName data source name
* @return connection
* @throws SQLException SQL exception
*/
Connection getConnection(String dataSourceName) throws SQLException;
/**
* Begin transaction.
*/
void begin();
/**
* Commit transaction.
*/
void commit();
/**
* Rollback transaction.
*/
void rollback();
}
我們重點縣關注init
方法,從它的命名,你就應該能夠看出來,這是整個框架的初始化方法,讓我們來看看它是如何進行初始化的。
private final Map<String, XATransactionDataSource> cachedDataSources = new HashMap<>();
private final XATransactionManager xaTransactionManager = XATransactionManagerLoader.getInstance().getTransactionManager();
@Override
public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources) {
for (ResourceDataSource each : resourceDataSources) {
cachedDataSources.put(each.getOriginalName(), new XATransactionDataSource(databaseType, each.getUniqueResourceName(), each.getDataSource(), xaTransactionManager));
}
xaTransactionManager.init();
}
-
首先SPI的方式加載XATransactionManager的具體實現類,這裏返回的就是
org.apache.shardingsphere.transaction.xa.atomikos.manager.AtomikosTransactionManager
。 -
我們在關注下
new XATransactionDataSource()
, 進入org.apache.shardingsphere.transaction.xa.jta.datasource。XATransactionDataSource
類的構造方法。
public XATransactionDataSource(final DatabaseType databaseType, final String resourceName, final DataSource dataSource, final XATransactionManager xaTransactionManager) {
this.databaseType = databaseType;
this.resourceName = resourceName;
this.dataSource = dataSource;
if (!CONTAINER_DATASOURCE_NAMES.contains(dataSource.getClass().getSimpleName())) {
// 重點關注 1 ,返回了xaDatasource
xaDataSource = XADataSourceFactory.build(databaseType, dataSource);
this.xaTransactionManager = xaTransactionManager;
// 重點關注2 註冊資源
xaTransactionManager.registerRecoveryResource(resourceName, xaDataSource);
}
}
- 我們重點來關注
XADataSourceFactory.build(databaseType, dataSource)
,從名字我們就可以看出,這應該是返回JTA規範裏面的XADataSource
,在ShardingSphere裏面很多的功能,可以從代碼風格的命名上就能猜出來,這就是優雅代碼(吹一波)。不多逼逼,我們進入該方法。
public final class XADataSourceFactory {
public static XADataSource build(final DatabaseType databaseType, final DataSource dataSource) {
return new DataSourceSwapper(XADataSourceDefinitionFactory.getXADataSourceDefinition(databaseType)).swap(dataSource);
}
}
- 首先又是一個SPI定義的
XADataSourceDefinitionFactory
,它根據不同的數據庫類型,來加載不同的方言。然後我們進入swap
方法。
public XADataSource swap(final DataSource dataSource) {
XADataSource result = createXADataSource();
setProperties(result, getDatabaseAccessConfiguration(dataSource));
return result;
}
-
很簡明,第一步創建,
XADataSource
,第二步給它設置屬性(包含數據的連接,用戶名密碼等),然後返回。 -
返回
XATransactionDataSource
類,關注xaTransactionManager.registerRecoveryResource(resourceName, xaDataSource);
從名字可以看出,這是註冊事務恢復資源。這個我們在事務恢復的時候詳解。 -
返回
XAShardingTransactionManager.init()
,我們重點來關注:xaTransactionManager.init();
,最後進入AtomikosTransactionManager.init()
。流程圖如下:
代碼:
public final class AtomikosTransactionManager implements XATransactionManager {
private final UserTransactionManager transactionManager = new UserTransactionManager();
private final UserTransactionService userTransactionService = new UserTransactionServiceImp();
@Override
public void init() {
userTransactionService.init();
}
}
- 進入
UserTransactionServiceImp.init()
private void initialize() {
//添加恢復資源 不用關心
for (RecoverableResource resource : resources_) {
Configuration.addResource ( resource );
}
for (LogAdministrator logAdministrator : logAdministrators_) {
Configuration.addLogAdministrator ( logAdministrator );
}
//註冊插件 不用關心
for (TransactionServicePlugin nxt : tsListeners_) {
Configuration.registerTransactionServicePlugin ( nxt );
}
//獲取配置屬性 重點關心
ConfigProperties configProps = Configuration.getConfigProperties();
configProps.applyUserSpecificProperties(properties_);
//進行初始化
Configuration.init();
}
- 我們重點關注,獲取配置屬性。最後進入
com.atomikos.icatch.provider.imp.AssemblerImp.initializeProperties()
方法。
@Override
public ConfigProperties initializeProperties() {
//讀取classpath下的默認配置transactions-defaults.properties
Properties defaults = new Properties();
loadPropertiesFromClasspath(defaults, DEFAULT_PROPERTIES_FILE_NAME);
//讀取classpath下,transactions.properties配置,覆蓋transactions-defaults.properties中相同key的值
Properties transactionsProperties = new Properties(defaults);
loadPropertiesFromClasspath(transactionsProperties, TRANSACTIONS_PROPERTIES_FILE_NAME);
//讀取classpath下,jta.properties,覆蓋transactions-defaults.properties、transactions.properties中相同key的值
Properties jtaProperties = new Properties(transactionsProperties);
loadPropertiesFromClasspath(jtaProperties, JTA_PROPERTIES_FILE_NAME);
//讀取通過java -Dcom.atomikos.icatch.file方式指定的自定義配置文件路徑,覆蓋之前的同名配置
Properties customProperties = new Properties(jtaProperties);
loadPropertiesFromCustomFilePath(customProperties);
//最終構造一個ConfigProperties對象,來表示實際要使用的配置
Properties finalProperties = new Properties(customProperties);
return new ConfigProperties(finalProperties);
}
- 接下來重點關注,
Configuration.init()
, 進行初始化。
ublic static synchronized boolean init() {
boolean startupInitiated = false;
if (service_ == null) {
startupInitiated = true;
//SPI方式加載插件註冊,無需過多關心
addAllTransactionServicePluginServicesFromClasspath();
ConfigProperties configProperties = getConfigProperties();
//調用插件的beforeInit方法進行初始化話,無需過多關心
notifyBeforeInit(configProperties);
//進行事務日誌恢復的初始化,很重要,接下來詳解
assembleSystemComponents(configProperties);
//進入系統註解的初始化,一般重要
initializeSystemComponents(configProperties);
notifyAfterInit();
if (configProperties.getForceShutdownOnVmExit()) {
addShutdownHook(new ForceShutdownHook());
}
}
return startupInitiated;
}
- 我們先來關注
assembleSystemComponents(configProperties);
進入它,進入com.atomikos.icatch.provider.imp.AssemblerImp.assembleTransactionService()
方法:
@Override
public TransactionServiceProvider assembleTransactionService(
ConfigProperties configProperties) {
RecoveryLog recoveryLog =null;
//打印日誌
logProperties(configProperties.getCompletedProperties());
//生成唯一名字
String tmUniqueName = configProperties.getTmUniqueName();
long maxTimeout = configProperties.getMaxTimeout();
int maxActives = configProperties.getMaxActives();
boolean threaded2pc = configProperties.getThreaded2pc();
//SPI方式加載OltpLog ,這是最重要的擴展地方,如果用戶沒有SPI的方式去擴展那麼就爲null
OltpLog oltpLog = createOltpLogFromClasspath();
if (oltpLog == null) {
LOGGER.logInfo("Using default (local) logging and recovery...");
//創建事務日誌存儲資源
Repository repository = createRepository(configProperties);
oltpLog = createOltpLog(repository);
//??? Assemble recoveryLog
recoveryLog = createRecoveryLog(repository);
}
StateRecoveryManagerImp recoveryManager = new StateRecoveryManagerImp();
recoveryManager.setOltpLog(oltpLog);
//生成唯一id生成器,以後生成XID會用的到
UniqueIdMgr idMgr = new UniqueIdMgr ( tmUniqueName );
int overflow = idMgr.getMaxIdLengthInBytes() - MAX_TID_LENGTH;
if ( overflow > 0 ) {
// see case 73086
String msg = "Value too long : " + tmUniqueName;
LOGGER.logFatal ( msg );
throw new SysException(msg);
}
return new TransactionServiceImp(tmUniqueName, recoveryManager, idMgr, maxTimeout, maxActives, !threaded2pc, recoveryLog);
}
- 我們重點來分析
createOltpLogFromClasspath()
, 採用SPI的加載方式來獲取,默認這裏會返回null
, 什麼意思呢? 就是當沒有擴展的時候,atomikos,會創建框架自定義的資源,來存儲事務日誌。
private OltpLog createOltpLogFromClasspath() {
OltpLog ret = null;
ServiceLoader<OltpLogFactory> loader = ServiceLoader.load(OltpLogFactory.class,Configuration.class.getClassLoader());
int i = 0;
for (OltpLogFactory l : loader ) {
ret = l.createOltpLog();
i++;
}
if (i > 1) {
String msg = "More than one OltpLogFactory found in classpath - error in configuration!";
LOGGER.logFatal(msg);
throw new SysException(msg);
}
return ret;
}
- 我們跟着進入
Repository repository = createRepository(configProperties);
private CachedRepository createCoordinatorLogEntryRepository(
ConfigProperties configProperties) throws LogException {
//創建內存資源存儲
InMemoryRepository inMemoryCoordinatorLogEntryRepository = new InMemoryRepository();
//進行初始化
inMemoryCoordinatorLogEntryRepository.init();
//創建使用文件存儲資源作爲backup
FileSystemRepository backupCoordinatorLogEntryRepository = new FileSystemRepository();
//進行初始化
backupCoordinatorLogEntryRepository.init();
//內存與file資源進行合併
CachedRepository repository = new CachedRepository(inMemoryCoordinatorLogEntryRepository, backupCoordinatorLogEntryRepository);
repository.init();
return repository;
}
-
這裏就會創建出
CachedRepository
,裏面包含了InMemoryRepository
與FileSystemRepository
-
回到主線
com.atomikos.icatch.config.Configuration.init()
, 最後來分析下notifyAfterInit();
private static void notifyAfterInit() {
//進行插件的初始化
for (TransactionServicePlugin p : tsListenersList_) {
p.afterInit();
}
for (LogAdministrator a : logAdministrators_) {
a.registerLogControl(service_.getLogControl());
}
//設置事務恢復服務,進行事務的恢復
for (RecoverableResource r : resourceList_ ) {
r.setRecoveryService(recoveryService_);
}
}
- 插件的初始化會進入
com.atomikos.icatch.jta.JtaTransactionServicePlugin.afterInit()
public void afterInit() {
TransactionManagerImp.installTransactionManager(Configuration.getCompositeTransactionManager(), autoRegisterResources);
//如果我們自定義擴展了 OltpLog ,這裏就會返回null,如果是null,那麼XaResourceRecoveryManager就是null
RecoveryLog recoveryLog = Configuration.getRecoveryLog();
long maxTimeout = Configuration.getConfigProperties().getMaxTimeout();
if (recoveryLog != null) {
XaResourceRecoveryManager.installXaResourceRecoveryManager(new DefaultXaRecoveryLog(recoveryLog, maxTimeout),Configuration.getConfigProperties().getTmUniqueName());
}
}
-
重點注意
RecoveryLog recoveryLog = Configuration.getRecoveryLog();
,如果用戶採用SPI的方式
,擴展了com.atomikos.recovery.OltpLog
,這裏就會返回 null
。 如果是null,則不會對XaResourceRecoveryManager
進行初始化。 -
回到
notifyAfterInit()
, 我們來分析setRecoveryService
。
public void setRecoveryService ( RecoveryService recoveryService )
throws ResourceException
{
if ( recoveryService != null ) {
if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( "Installing recovery service on resource "
+ getName () );
this.branchIdentifier=recoveryService.getName();
recover();
}
}
- 我們進入
recover()
方法:
public void recover() {
XaResourceRecoveryManager xaResourceRecoveryManager = XaResourceRecoveryManager.getInstance();
//null for LogCloud recovery
if (xaResourceRecoveryManager != null) {
try {
xaResourceRecoveryManager.recover(getXAResource());
} catch (Exception e) {
refreshXAResource(); //cf case 156968
}
}
}
- 看到最關鍵的註釋了嗎,如果用戶採用
SPI的方式
,擴展了com.atomikos.recovery.OltpLog
,那麼XaResourceRecoveryManager
爲null,則就會進行雲端恢復,反之則進行事務恢復。 事務恢復很複雜,我們會單獨來講。
到這裏atomikos的基本的初始化已經完成。
atomikos事務begin流程
我們知道,本地的事務,都會有一個 trainsaction.begin
, 對應XA分佈式事務來說也不另外,我們再把思路切換回XAShardingTransactionManager.begin()
, 會調用com.atomikos.icatch.jta.TransactionManagerImp.begin()
。流程圖如下:
代碼:
public void begin ( int timeout ) throws NotSupportedException,
SystemException
{
CompositeTransaction ct = null;
ResumePreviousTransactionSubTxAwareParticipant resumeParticipant = null;
ct = compositeTransactionManager.getCompositeTransaction();
if ( ct != null && ct.getProperty ( JTA_PROPERTY_NAME ) == null ) {
LOGGER.logWarning ( "JTA: temporarily suspending incompatible transaction: " + ct.getTid() +
" (will be resumed after JTA transaction ends)" );
ct = compositeTransactionManager.suspend();
resumeParticipant = new ResumePreviousTransactionSubTxAwareParticipant ( ct );
}
try {
//創建事務補償點
ct = compositeTransactionManager.createCompositeTransaction ( ( ( long ) timeout ) * 1000 );
if ( resumeParticipant != null ) ct.addSubTxAwareParticipant ( resumeParticipant );
if ( ct.isRoot () && getDefaultSerial () )
ct.setSerial ();
ct.setProperty ( JTA_PROPERTY_NAME , "true" );
} catch ( SysException se ) {
String msg = "Error in begin()";
LOGGER.logError( msg , se );
throw new ExtendedSystemException ( msg , se );
}
recreateCompositeTransactionAsJtaTransaction(ct);
}
- 這裏我們主要關注
compositeTransactionManager.createCompositeTransaction()
,
public CompositeTransaction createCompositeTransaction ( long timeout ) throws SysException
{
CompositeTransaction ct = null , ret = null;
ct = getCurrentTx ();
if ( ct == null ) {
ret = getTransactionService().createCompositeTransaction ( timeout );
if(LOGGER.isDebugEnabled()){
LOGGER.logDebug("createCompositeTransaction ( " + timeout + " ): "
+ "created new ROOT transaction with id " + ret.getTid ());
}
} else {
if(LOGGER.isDebugEnabled()) LOGGER.logDebug("createCompositeTransaction ( " + timeout + " )");
ret = ct.createSubTransaction ();
}
Thread thread = Thread.currentThread ();
setThreadMappings ( ret, thread );
return ret;
}
- 創建了事務補償點,然後把他放到了用當前線程作爲key的Map當中,這裏思考,
爲啥它不用 threadLocal
。
到這裏atomikos的事務begin流程已經完成。 大家可能有些疑惑,begin好像什麼都沒有做,XA start 也沒調用? 別慌,下一節繼續來講。
XATransactionDataSource getConnection() 流程
我們都知道想要執行SQL語句,必須要獲取到數據庫的connection。讓我們再回到 XAShardingTransactionManager.getConnection()
最後會調用到org.apache.shardingsphere.transaction.xa.jta.datasourceXATransactionDataSource.getConnection()
。流程圖如下:
代碼 :
public Connection getConnection() throws SQLException, SystemException, RollbackException {
//先檢查是否已經有存在的connection,這一步很關心,也是XA的關鍵,因爲XA事務,必須在同一個connection
if (CONTAINER_DATASOURCE_NAMES.contains(dataSource.getClass().getSimpleName())) {
return dataSource.getConnection();
}
//獲取數據庫連接
Connection result = dataSource.getConnection();
//轉成XAConnection,其實是同一個連接
XAConnection xaConnection = XAConnectionFactory.createXAConnection(databaseType, xaDataSource, result);
//獲取JTA事務定義接口
Transaction transaction = xaTransactionManager.getTransactionManager().getTransaction();
if (!enlistedTransactions.get().contains(transaction)) {
//進行資源註冊
transaction.enlistResource(new SingleXAResource(resourceName, xaConnection.getXAResource()));
transaction.registerSynchronization(new Synchronization() {
@Override
public void beforeCompletion() {
enlistedTransactions.get().remove(transaction);
}
@Override
public void afterCompletion(final int status) {
enlistedTransactions.get().clear();
}
});
enlistedTransactions.get().add(transaction);
}
return result;
}
-
首先第一步很關心,尤其是對shardingsphere來說,因爲在一個事務裏面,會有多個SQL語句,打到相同的數據庫,所以對相同的數據庫,必須獲取同一個XAConnection,這樣才能進行XA事務的提交與回滾。
-
我們接下來關心
transaction.enlistResource(new SingleXAResource(resourceName, xaConnection.getXAResource()));
, 會進入com.atomikos.icatch.jta.TransactionImp.enlistResource()
, 代碼太長,截取一部分。
try {
restx = (XAResourceTransaction) res
.getResourceTransaction(this.compositeTransaction);
// next, we MUST set the xa resource again,
// because ONLY the instance we got as argument
// is available for use now !
// older instances (set in restx from previous sibling)
// have connections that may be in reuse already
// ->old xares not valid except for 2pc operations
restx.setXAResource(xares);
restx.resume();
} catch (ResourceException re) {
throw new ExtendedSystemException(
"Unexpected error during enlist", re);
} catch (RuntimeException e) {
throw e;
}
addXAResourceTransaction(restx, xares);
- 我們直接看
restx.resume();
public synchronized void resume() throws ResourceException {
int flag = 0;
String logFlag = "";
if (this.state.equals(TxState.LOCALLY_DONE)) {// reused instance
flag = XAResource.TMJOIN;
logFlag = "XAResource.TMJOIN";
} else if (!this.knownInResource) {// new instance
flag = XAResource.TMNOFLAGS;
logFlag = "XAResource.TMNOFLAGS";
} else
throw new IllegalStateException("Wrong state for resume: "
+ this.state);
try {
if (LOGGER.isDebugEnabled()) {
LOGGER.logDebug("XAResource.start ( " + this.xidToHexString
+ " , " + logFlag + " ) on resource "
+ this.resourcename
+ " represented by XAResource instance "
+ this.xaresource);
}
this.xaresource.start(this.xid, flag);
} catch (XAException xaerr) {
String msg = interpretErrorCode(this.resourcename, "resume",
this.xid, xaerr.errorCode);
LOGGER.logWarning(msg, xaerr);
throw new ResourceException(msg, xaerr);
}
setState(TxState.ACTIVE);
this.knownInResource = true;
}
- 哦多尅,看見了嗎,各位,看見了
this.xaresource.start(this.xid, flag);
了嗎????,我們進去,假設我們使用的Mysql數據庫:
public void start(Xid xid, int flags) throws XAException {
StringBuilder commandBuf = new StringBuilder(300);
commandBuf.append("XA START ");
appendXid(commandBuf, xid);
switch(flags) {
case 0:
break;
case 2097152:
commandBuf.append(" JOIN");
break;
case 134217728:
commandBuf.append(" RESUME");
break;
default:
throw new XAException(-5);
}
this.dispatchCommand(commandBuf.toString());
this.underlyingConnection.setInGlobalTx(true);
}
- 組裝
XA start Xid
SQL語句,進行執行。
到這裏,我們總結下,在獲取數據庫連接的時候,我們執行了XA協議接口中的 XA start xid
atomikos事務commit流程
好了,上面我們已經開啓了事務,現在我們來分析下事務commit流程,我們再把視角切換回XAShardingTransactionManager.commit()
,最後我們會進入com.atomikos.icatch.imp.CompositeTransactionImp.commit()
方法。流程圖如下:
代碼:
public void commit () throws HeurRollbackException, HeurMixedException,
HeurHazardException, SysException, SecurityException,
RollbackException
{
//首先更新下事務日誌的狀態
doCommit ();
setSiblingInfoForIncoming1pcRequestFromRemoteClient();
if ( isRoot () ) {
//真正的commit操作
coordinator.terminate ( true );
}
}
- 我們關注
coordinator.terminate ( true );
protected void terminate ( boolean commit ) throws HeurRollbackException,
HeurMixedException, SysException, java.lang.SecurityException,
HeurCommitException, HeurHazardException, RollbackException,
IllegalStateException
{
synchronized ( fsm_ ) {
if ( commit ) {
//判斷有幾個參與者,如果只有一個,直接提交
if ( participants_.size () <= 1 ) {
commit ( true );
} else {
//否則,走XA 2階段提交流程,先prepare, 再提交
int prepareResult = prepare ();
// make sure to only do commit if NOT read only
if ( prepareResult != Participant.READ_ONLY )
commit ( false );
}
} else {
rollback ();
}
}
}
-
首先會判斷參與者的個數,這裏我們可以理解爲MySQL的database數量,如果只有一個,退化成一階段,直接提交。 如果有多個,則走標準的XA二階段提交流程。
-
我們來看
prepare ();
流程,最後會走到com.atomikos.icatch.imp.PrepareMessage.send()
--->com.atomikos.datasource.xa.XAResourceTransaction.prepare()
int ret = 0;
terminateInResource();
if (TxState.ACTIVE == this.state) {
// tolerate non-delisting apps/servers
suspend();
}
// duplicate prepares can happen for siblings in serial subtxs!!!
// in that case, the second prepare just returns READONLY
if (this.state == TxState.IN_DOUBT)
return Participant.READ_ONLY;
else if (!(this.state == TxState.LOCALLY_DONE))
throw new SysException("Wrong state for prepare: " + this.state);
try {
// refresh xaresource for MQSeries: seems to close XAResource after
// suspend???
testOrRefreshXAResourceFor2PC();
if (LOGGER.isTraceEnabled()) {
LOGGER.logTrace("About to call prepare on XAResource instance: "
+ this.xaresource);
}
ret = this.xaresource.prepare(this.xid);
} catch (XAException xaerr) {
String msg = interpretErrorCode(this.resourcename, "prepare",
this.xid, xaerr.errorCode);
if (XAException.XA_RBBASE <= xaerr.errorCode
&& xaerr.errorCode <= XAException.XA_RBEND) {
LOGGER.logWarning(msg, xaerr); // see case 84253
throw new RollbackException(msg);
} else {
LOGGER.logError(msg, xaerr);
throw new SysException(msg, xaerr);
}
}
setState(TxState.IN_DOUBT);
if (ret == XAResource.XA_RDONLY) {
if (LOGGER.isDebugEnabled()) {
LOGGER.logDebug("XAResource.prepare ( " + this.xidToHexString
+ " ) returning XAResource.XA_RDONLY " + "on resource "
+ this.resourcename
+ " represented by XAResource instance "
+ this.xaresource);
}
return Participant.READ_ONLY;
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.logDebug("XAResource.prepare ( " + this.xidToHexString
+ " ) returning OK " + "on resource "
+ this.resourcename
+ " represented by XAResource instance "
+ this.xaresource);
}
return Participant.READ_ONLY + 1;
}
- 終於,我們看到了這麼一句
ret = this.xaresource.prepare(this.xid);
但是等等,我們之前不是說了,XA start xid
以後要先XA end xid
嗎? 答案就在suspend();
裏面。
public synchronized void suspend() throws ResourceException {
// BugzID: 20545
// State may be IN_DOUBT or TERMINATED when a connection is closed AFTER
// commit!
// In that case, don't call END again, and also don't generate any
// error!
// This is required for some hibernate connection release strategies.
if (this.state.equals(TxState.ACTIVE)) {
try {
if (LOGGER.isDebugEnabled()) {
LOGGER.logDebug("XAResource.end ( " + this.xidToHexString
+ " , XAResource.TMSUCCESS ) on resource "
+ this.resourcename
+ " represented by XAResource instance "
+ this.xaresource);
}
//執行了 xa end 語句
this.xaresource.end(this.xid, XAResource.TMSUCCESS);
} catch (XAException xaerr) {
String msg = interpretErrorCode(this.resourcename, "end",
this.xid, xaerr.errorCode);
if (LOGGER.isTraceEnabled())
LOGGER.logTrace(msg, xaerr);
// don't throw: fix for case 102827
}
setState(TxState.LOCALLY_DONE);
}
}
到了這裏,我們已經執行了 XA start xid -> XA end xid --> XA prepare xid, 接下來就是最後一步 commit
- 我們再回到
terminate(false)
方法,來看 commit()流程。其實和 prepare流程一樣,最後會走到com.atomikos.datasource.xa.XAResourceTransaction.commit()
。 commit執行完,數據提交
//繁雜代碼過多,就顯示核心的
this.xaresource.commit(this.xid, onePhase);
思考:這裏的參與者提交是在一個循環裏面,一個一個提交的,如果之前的提交了,後面的參與者提交的時候,掛了,就會造成數據的不一致性。
Atomikos rollback() 流程
上面我們已經分析了commit流程,其實rollback流程和commit流程一樣,我們在把目光切換回 org.apache.shardingsphere.transaction.xa.XAShardingTransactionManager.rollback()
,最後會執行到com.atomikos.icatch.imp.CompositeTransactionImp.rollback()
。
public void rollback () throws IllegalStateException, SysException
{
//清空資源,更新事務日誌狀態等
doRollback ();
if ( isRoot () ) {
try {
coordinator.terminate ( false );
} catch ( Exception e ) {
throw new SysException ( "Unexpected error in rollback: " + e.getMessage (), e );
}
}
}
- 重點關注
coordinator.terminate ( false );
,這個和 commit流程是一樣的,只不過在 commit流程裏面,參數傳的是true。
protected void terminate ( boolean commit ) throws HeurRollbackException,
HeurMixedException, SysException, java.lang.SecurityException,
HeurCommitException, HeurHazardException, RollbackException,
IllegalStateException
{
synchronized ( fsm_ ) {
if ( commit ) {
if ( participants_.size () <= 1 ) {
commit ( true );
} else {
int prepareResult = prepare ();
// make sure to only do commit if NOT read only
if ( prepareResult != Participant.READ_ONLY )
commit ( false );
}
} else {
//如果是false,走的是rollback
rollback ();
}
}
}
- 我們重點關注
rollback()
,最後會走到com.atomikos.datasource.xa.XAResourceTransaction.rollback()
。
public synchronized void rollback()
throws HeurCommitException, HeurMixedException,
HeurHazardException, SysException {
terminateInResource();
if (rollbackShouldDoNothing()) {
return;
}
if (this.state.equals(TxState.TERMINATED)) {
return;
}
if (this.state.equals(TxState.HEUR_MIXED))
throw new HeurMixedException();
if (this.state.equals(TxState.HEUR_COMMITTED))
throw new HeurCommitException();
if (this.xaresource == null) {
throw new HeurHazardException("XAResourceTransaction "
+ getXid() + ": no XAResource to rollback?");
}
try {
if (this.state.equals(TxState.ACTIVE)) { // first suspend xid
suspend();
}
// refresh xaresource for MQSeries: seems to close XAResource after
// suspend???
testOrRefreshXAResourceFor2PC();
if (LOGGER.isDebugEnabled()) {
LOGGER.logDebug("XAResource.rollback ( " + this.xidToHexString
+ " ) " + "on resource " + this.resourcename
+ " represented by XAResource instance "
+ this.xaresource);
}
this.xaresource.rollback(this.xid);
- 先在
supend()
方法裏面執行了XA end xid
語句, 接下來執行this.xaresource.rollback(this.xid);
進行數據的回滾。
文章到此,已經寫的很長很多了,我們分析了ShardingSphere對於XA方案,提供了一套SPI解決方案,對Atomikos進行了整合,也分析了Atomikos初始化流程,開始事務流程,獲取連接流程,提交事務流程,回滾事務流程。希望對大家理解XA的原理有所幫助。
作者介紹: 肖宇,Apache ShardingSphere Committer,開源hmily分佈式事務框架作者, 開源soul網關作者,熱愛開源,追求寫優雅代碼。目前就職入京東數科,參與ShardingSphere的開源建設,以及分佈式數據庫的研發工作。