0 前文
上一文解析了 ShardingSphere 強一致性事務支援 XAShardingTransactionManager ,本文繼續:
- 講解該類
- 介紹支援柔性事務的 SeataATShardingTransactionManager
sharding-transaction-xa-core中關於 XAShardingTransactionManager,本文研究 XATransactionManager 和 ShardingConnection 類實現。
1 XAShardingTransactionManager
1.1 init
public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources) {
for (ResourceDataSource each : resourceDataSources) {
// 根據傳入的 ResourceDataSource建立XATransactionDataSource並快取
cachedDataSources.put(each.getOriginalName(), new XATransactionDataSource(databaseType, each.getUniqueResourceName(), each.getDataSource(), xaTransactionManager));
}
// 對透過 SPI 建立的 XATransactionManager 也執行其 init 初始化
xaTransactionManager.init();
}
1.2 其它方法
實現也簡單:
@Override
public TransactionType getTransactionType() {
return TransactionType.XA;
}
@SneakyThrows
@Override
public boolean isInTransaction() {
return Status.STATUS_NO_TRANSACTION != xaTransactionManager.getTransactionManager().getStatus();
}
@Override
public Connection getConnection(final String dataSourceName) throws SQLException {
return cachedDataSources.get(dataSourceName).getConnection();
}
1.3 事務操作相關
begin、commit 和 rollback直接委託儲存在 XATransactionManager#TransactionManager 完成:
@SneakyThrows
@Override
public void begin() {
xaTransactionManager.getTransactionManager().begin();
}
@SneakyThrows
@Override
public void commit() {
xaTransactionManager.getTransactionManager().commit();
}
@SneakyThrows
@Override
public void rollback() {
xaTransactionManager.getTransactionManager().rollback();
}
2 AtomikosTransactionManager
TransactionManager預設實現。
2.1 AtomikosXARecoverableResource
代表資源:
public final class AtomikosXARecoverableResource extends JdbcTransactionalResource {
private final String resourceName;
AtomikosXARecoverableResource(final String serverName, final XADataSource xaDataSource) {
super(serverName, xaDataSource);
resourceName = serverName;
}
// 比對SingleXAResource#ResourceName,確定是否在使用資源,此即設計包裝 XAResource 的 SingleXAResource 類的原因
@Override
public boolean usesXAResource(final XAResource xaResource) {
return resourceName.equals(((SingleXAResource) xaResource).getResourceName());
}
}
2.2 AtomikosXARecoverableResource
public final class AtomikosTransactionManager implements XATransactionManager {
private final UserTransactionManager transactionManager = new UserTransactionManager();
private final UserTransactionService userTransactionService = new UserTransactionServiceImp();
@Override
public void init() {
userTransactionService.init();
}
@Override
public void registerRecoveryResource(final String dataSourceName, final XADataSource xaDataSource) {
userTransactionService.registerResource(new AtomikosXARecoverableResource(dataSourceName, xaDataSource));
}
@Override
public void removeRecoveryResource(final String dataSourceName, final XADataSource xaDataSource) {
userTransactionService.removeResource(new AtomikosXARecoverableResource(dataSourceName, xaDataSource));
}
@Override
@SneakyThrows
public void enlistResource(final SingleXAResource xaResource) {
transactionManager.getTransaction().enlistResource(xaResource);
}
@Override
public TransactionManager getTransactionManager() {
return transactionManager;
}
@Override
public void close() {
userTransactionService.shutdown(true);
}
}
對 Atomikos 的 UserTransactionManager、UserTransactionService 簡單呼叫,Atomikos#UserTransactionManager 實現 TransactionManager 介面,封裝所有 TransactionManager 需要完成的工作。
看完 sharding-transaction-xa-atomikos-manager,再看 sharding-transaction-xa-bitronix-manager 工程。基於 bitronix 的 XATransactionManager 實現方案
3 BitronixXATransactionManager
public final class BitronixXATransactionManager implements XATransactionManager {
private final BitronixTransactionManager bitronixTransactionManager = TransactionManagerServices.getTransactionManager();
@Override
public void init() {
}
@SneakyThrows
@Override
public void registerRecoveryResource(final String dataSourceName, final XADataSource xaDataSource) {
ResourceRegistrar.register(new BitronixRecoveryResource(dataSourceName, xaDataSource));
}
@SneakyThrows
@Override
public void removeRecoveryResource(final String dataSourceName, final XADataSource xaDataSource) {
ResourceRegistrar.unregister(new BitronixRecoveryResource(dataSourceName, xaDataSource));
}
@SneakyThrows
@Override
public void enlistResource(final SingleXAResource singleXAResource) {
bitronixTransactionManager.getTransaction().enlistResource(singleXAResource);
}
@Override
public TransactionManager getTransactionManager() {
return bitronixTransactionManager;
}
@Override
public void close() {
bitronixTransactionManager.shutdown();
}
}
XA兩階段提交核心類:
4 ShardingConnection
上圖的整個流程源頭ShardingConnection類,建構函式發現建立 ShardingTransactionManager 過程:
@Getter
public final class ShardingConnection extends AbstractConnectionAdapter {
public ShardingConnection(...) {
...
shardingTransactionManager = runtimeContext.getShardingTransactionManagerEngine().getTransactionManager(transactionType);
}
}
ShardingConnection多處用到上面建立的shardingTransactionManager。如:
createConnection
獲取連線:
@Override
protected Connection createConnection(final String dataSourceName, final DataSource dataSource) throws SQLException {
return isInShardingTransaction() ? shardingTransactionManager.getConnection(dataSourceName) : dataSource.getConnection();
}
isInShardingTransaction
判斷是否在同一事務:
private boolean isInShardingTransaction() {
return null != shardingTransactionManager && shardingTransactionManager.isInTransaction();
}
setAutoCommit
@Override
public void setAutoCommit(final boolean autoCommit) throws SQLException {
if (TransactionType.LOCAL == transactionType) {
super.setAutoCommit(autoCommit);
return;
}
if (autoCommit && !shardingTransactionManager.isInTransaction() || !autoCommit && shardingTransactionManager.isInTransaction()) {
return;
}
if (autoCommit && shardingTransactionManager.isInTransaction()) {
shardingTransactionManager.commit();
return;
}
if (!autoCommit && !shardingTransactionManager.isInTransaction()) {
closeCachedConnections();
shardingTransactionManager.begin();
}
}
事務型別為本地事務時,直接呼叫 ShardingConnection 父類 AbstractConnectionAdapter#setAutoCommit 完成本地事務自動提交:
- autoCommit=true 且執行在事務中,調shardingTransactionManager.commit()完成提交
- autoCommit=false 且當前不在事務中時,調 shardingTransactionManager.begin() 啟動事務
commit、rollback
類似setAutoCommit ,按事務型別決定是否進行分散式提交和回滾:
@Override
public void commit() throws SQLException {
if (TransactionType.LOCAL == transactionType) {
super.commit();
} else {
shardingTransactionManager.commit();
}
}
@Override
public void rollback() throws SQLException {
if (TransactionType.LOCAL == transactionType) {
super.rollback();
} else {
shardingTransactionManager.rollback();
}
}
ShardingSphere提供兩階段提交的 XA 協議實現方案的同時,也實現柔性事務。看完 XAShardingTransactionManager,來看基於 Seata 框架的柔性事務 TransactionManager 實現類 SeataATShardingTransactionManager。
5 SeataATShardingTransactionManager
該類完全採用阿里Seata框架提供分散式事務特性,而非遵循類似 XA 這樣的開發規範,所以程式碼實現比 XAShardingTransactionManager 類層結構簡單,複雜性都遮蔽在了框架內部。
整合 Seata,先要初始化 TMClient、RMClient,在 Seata 內部,這兩個客戶端之間會基於RPC通訊。
SeataATShardingTransactionManager#init的initSeataRPCClient初始化這倆客戶端物件:
// 根據 seata.conf 建立配置物件
FileConfiguration configuration = new FileConfiguration("seata.conf");
initSeataRPCClient() {
String applicationId = configuration.getConfig("client.application.id");
Preconditions.checkNotNull(applicationId, "please config application id within seata.conf file");
String transactionServiceGroup = configuration.getConfig("client.transaction.service.group", "default");
TMClient.init(applicationId, transactionServiceGroup);
RMClient.init(applicationId, transactionServiceGroup);
}
Seata也提供一套構建在 JDBC 規範之上的實現策略,類似03文介紹的 ShardingSphere 與 JDBC 規範之間相容性。
Seata使用DataSourceProxy、ConnectionProxy代理物件,如DataSourceProxy:
實現了自定義Resource介面,繼承AbstractDataSourceProxy(最終實現JDBC的DataSource介面)。所以,初始化 Seata 框架時,也要根據輸入 DataSource 物件構建 DataSourceProxy,並透過 DataSourceProxy 獲取 ConnectionProxy。
init、getConnection
@Override
public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources) {
// 初始化 Seata 客戶端
initSeataRPCClient();
// 建立 DataSourceProxy 並放入Map
for (ResourceDataSource each : resourceDataSources) {
dataSourceMap.put(each.getOriginalName(), new DataSourceProxy(each.getDataSource()));
}
}
@Override
public Connection getConnection(final String dataSourceName) {
// 根據 DataSourceProxy 獲取 ConnectionProxy
return dataSourceMap.get(dataSourceName).getConnection();
}
初始化後,提供了事務開啟和提交相關的入口。Seata的GlobalTransaction是核心介面,封裝了面向使用者操作層的分散式事務訪問入口:
public interface GlobalTransaction {
void begin() throws TransactionException;
void begin(int timeout) throws TransactionException;
void begin(int timeout, String name) throws TransactionException;
void commit() throws TransactionException;
void rollback() throws TransactionException;
GlobalStatus getStatus() throws TransactionException;
String getXid();
}
ShardingSphere 作 GlobalTransaction 的使用者層,也基於 GlobalTransaction 完成分散式事務操作。但 ShardingSphere 並未直接使用這層,而是設計位於sharding-transaction-base-seata-at的SeataTransactionHolder類,儲存執行緒安全的 GlobalTransaction 物件。
SeataTransactionHolder
final class SeataTransactionHolder {
private static final ThreadLocal<GlobalTransaction> CONTEXT = new ThreadLocal<>();
static void set(final GlobalTransaction transaction) {
CONTEXT.set(transaction);
}
static GlobalTransaction get() {
return CONTEXT.get();
}
static void clear() {
CONTEXT.remove();
}
}
使用 ThreadLocal 確保對 GlobalTransaction 訪問的執行緒安全性。
咋判斷當前操作是否處於一個全域性事務?Seata存在一個上下文物件RootContex儲存參與者和發起者之間傳播的 Xid:
- 當事務發起者開啟全域性事務,將 Xid 填入 RootContext
- 然後 Xid 沿服務呼叫鏈一直傳播,進而填充到每個事務參與者程序的 RootContext
- 事務參與者發現 RootContext 存在 Xid,就可知自己處於全域性事務
因此,只需判斷:
@Override
public boolean isInTransaction() {
return null != RootContext.getXID();
}
Seata 也提供針對全域性事務的上下文類 GlobalTransactionContext,可用:
- getCurrent 獲取一個 GlobalTransaction物件
- 或透過 getCurrentOrCreate 在無法獲取 GlobalTransaction 物件時新建一個
就不難理解如下實現了
begin
@Override
@SneakyThrows
public void begin() {
// 建立一個 GlobalTransaction,儲存到 SeataTransactionHolder
SeataTransactionHolder.set(GlobalTransactionContext.getCurrentOrCreate());
// 從 SeataTransactionHolder 獲取一個 GlobalTransaction,並調 begin 啟動事務
SeataTransactionHolder.get().begin();
SeataTransactionBroadcaster.collectGlobalTxId();
}
注意到最後的類:
SeataTransactionBroadcaster
儲存 Seata 全域性 Xid 的一個容器類。事務啟動時收集全域性 Xid 並進行儲存,而在事務提交或回滾時清空這些 Xid。
class SeataTransactionBroadcaster {
String SEATA_TX_XID = "SEATA_TX_XID";
static void collectGlobalTxId() {
if (RootContext.inGlobalTransaction()) {
ShardingExecuteDataMap.getDataMap().put(SEATA_TX_XID, RootContext.getXID());
}
}
static void broadcastIfNecessary(final Map<String, Object> shardingExecuteDataMap) {
if (shardingExecuteDataMap.containsKey(SEATA_TX_XID) && !RootContext.inGlobalTransaction()) {
RootContext.bind((String) shardingExecuteDataMap.get(SEATA_TX_XID));
}
}
static void clear() {
ShardingExecuteDataMap.getDataMap().remove(SEATA_TX_XID);
}
}
因此
commit、rollback和close
實現就清楚了:
@Override
public void commit() {
try {
SeataTransactionHolder.get().commit();
} finally {
SeataTransactionBroadcaster.clear();
SeataTransactionHolder.clear();
}
}
@Override
public void rollback() {
try {
SeataTransactionHolder.get().rollback();
} finally {
SeataTransactionBroadcaster.clear();
SeataTransactionHolder.clear();
}
}
@Override
public void close() {
dataSourceMap.clear();
SeataTransactionHolder.clear();
TmRpcClient.getInstance().destroy();
RmRpcClient.getInstance().destroy();
}
sharding-transaction-base-seata-at 工程中的程式碼實際上就只有這些內容,這些內容也構成了在 ShardingSphere中 整合 Seata 框架的實現過程。
6 從原始碼到開發
本文給出應用程式咋整合 Seata 分散式事務框架的詳細過程,ShardingSphere 提供一種模版實現。日常開發,若想在業務程式碼整合 Seata,可參考 SeataTransactionHolder、SeataATShardingTransactionManager 等核心程式碼,而無需太多修改。
7 總結
XAShardingTransactionManager理解難在從 ShardingConnection 到底層 JDBC 規範的整個整合和相容過程。
8 整合Seata框架
參考 ShardingSphere 的實現:
1. 配置 Seata 環境
- 配置檔案準備: 建立
seata.conf
檔案,定義applicationId
和transactionServiceGroup
等引數。 - 啟動 Seata 服務: 啟動 Seata Server 並確保其與資料庫的事務協調機制正常工作。
2. 初始化 Seata 客戶端
專案中初始化 TMClient 和 RMClient,它們分別代表事務管理器和資源管理器:
FileConfiguration configuration = new FileConfiguration("seata.conf");
String applicationId = configuration.getConfig("client.application.id");
String transactionServiceGroup = configuration.getConfig("client.transaction.service.group", "default");
TMClient.init(applicationId, transactionServiceGroup);
RMClient.init(applicationId, transactionServiceGroup);
3. 資料來源代理
構建 DataSourceProxy
: 使用 Seata 的 DataSourceProxy
對資料來源進行代理。
DataSourceProxy dataSourceProxy = new DataSourceProxy(originalDataSource);
獲取連線代理:從代理資料來源中獲取 ConnectionProxy
,使每個資料庫連線支援事務傳播。
Connection connection = dataSourceProxy.getConnection();
4. 全域性事務上下文管理
基於 GlobalTransactionContext
獲取或建立事務物件:
GlobalTransaction transaction = GlobalTransactionContext.getCurrentOrCreate();
繫結全域性事務 XID: 當事務發起時,將全域性事務的 XID 儲存在 RootContext
中:
RootContext.bind(transaction.getXid());
透過 RootContext
判斷事務狀態:
boolean isInTransaction = RootContext.inGlobalTransaction();
5. 事務操作實現
開啟事務:
transaction.begin();
提交事務:
try {
transaction.commit();
} finally {
RootContext.unbind();
}
回滾事務:
try {
transaction.rollback();
} finally {
RootContext.unbind();
}
6. 整合業務邏輯
將分散式事務的核心邏輯封裝在工具類中,例如 SeataTransactionHolder
,以便方便地管理全域性事務上下文:
SeataTransactionHolder.set(GlobalTransactionContext.getCurrentOrCreate());
7. 清理資源
在應用關閉時,清理客戶端資源:
TmRpcClient.getInstance().destroy();
RmRpcClient.getInstance().destroy();
8. 注意事項
- 確保所有資料來源透過
DataSourceProxy
代理,避免事務管理失效。 - 配置資料庫支援 Undo Log 表,確保事務回滾記錄正常儲存。
- 除錯過程中,檢查 Seata Server 日誌和應用日誌,定位事務協調的問題。
透過上述步驟,可以在業務程式碼中順利整合 Seata,實現分散式事務管理,保障資料一致性。
關注我,緊跟本系列專欄文章,咱們下篇再續!
作者簡介:魔都架構師,多家大廠後端一線研發經驗,在分散式系統設計、資料平臺架構和AI應用開發等領域都有豐富實踐經驗。
各大技術社群頭部專家博主。具有豐富的引領團隊經驗,深厚業務架構和解決方案的積累。
負責:
- 中央/分銷預訂系統效能最佳化
- 活動&券等營銷中臺建設
- 交易平臺及資料中臺等架構和開發設計
- 車聯網核心平臺-物聯網連線平臺、大資料平臺架構設計及最佳化
- LLM Agent應用開發
- 區塊鏈應用開發
- 大資料開發挖掘經驗
- 推薦系統專案
目前主攻市級軟體專案設計、構建服務全社會的應用系統。
參考:
- 程式設計嚴選網
本文由部落格一文多發平臺 OpenWrite 釋出!