XA分散式交易

良葛格在簡介交易中已經把交易的性質說明的很清楚了,要嘛全部成功、要嘛全數陣亡;而distributed transaction(XA)是用來進行全域(global)交易,也就是把一堆交易綁(group)在一起,如同單一交易般具有ACID的特性。

要瞭解XA,有三篇文章必看:XA Exposed:Part IPartIIPart III

簡單說明Two Phase Commit(2PC)其原則:
  1. 在XA中每一個XAResource代表不同的交易標的,每一組操做以一個Xid來表示
  2. 對每一個XAResource呼叫 start(),並開始作業處理(比如新增修改或刪除資料),當作業完成後再對XAResource呼叫end()
    1. 若是作業出錯,必須對該作業與其前已完成的作業XAResource,呼叫rollback()
    2. XA以失敗告終,中斷交易
  3. 準備階段,當所有的XAResource都完成 start() ~ end()後,此階段對每一個XAResource叫用prepare()輪詢,所有輪詢結果必須是XA_OK(準備好等待叫用commit())或是XA_RDONLY(資料未改變,無需叫用commit())
    1. 若是回傳結果不如預期,則對所有XAResource叫用rollback()
    2. 若是這階段出現了XAException,coordinator(比如TransactionManager或是寫程式的你)則要負責收攤子(recover(復原),例如網路中斷後又連上了)或是放棄(救不回來了只好forget)
  4. 確認所有輪詢結果無誤後,對每一個XAResource呼叫commit()
    1. 跟上階段一樣,若出現了XAException,coordinator也是要負責收攤子(復原或是放棄)
  5. 完成交易作業

測試程式說明:

首先程式測試前會用H2 database啟動兩個資料庫,並在啟動時塞入一筆叫Rollback的資料
測試代碼中有四個測試,建議一次執行一個(把其它的remark掉://@Test再執行$mvn test),由Log中看看它到底作了什麼事。
我們使用JDBC來作XA 通常須要一個XADataSource,因為它可以透過getXAConnection()取得XAConnection,然後我們才可以取得 XAResource

第一個測試testRollbackWithRaw():目的在測試第一資料庫塞入一筆”rollback”後再在第二個資料庫塞入一筆”Rollback”資料以引起作業錯誤,然後rollback整個XA交易。
我們必須為每一組作業建立一個Xid識別符,每個Xid包含formatId、globalTransactionId與branchQualifier。
表示不同的格式,0視為使用OSI CCR 格式,我們的測試程式取個大於0的數即可
相同號碼表示為同一交易群組(因為在Server環境內,TxMgr要服務很多Client,所以得靠Xid來識別是不是同一國)
用來區分不同作業識別
然後按照前述原則來完成XA作業,最後檢查第一個資料庫是否有rollback(資料沒有塞入成功)

第二個測試testCommitWithRaw(),跟第一個測試差不多,不過這次我們預期資料會成功塞入

前兩次的作業都是手動打造XA交易,正常的設計師當然不會每次都這樣寫,所以我們必須透過TransactionManager來作這件事。
通常我們說交易,很多人會認為是指資料庫,其實不然,例如,用戶上傳了一張圖片,我們除了在資料庫中塞入一筆紀錄,也會把圖存起來,但是萬一存圖失敗呢?那就回不去了
所以的處理過程如下:
  1. TransactionManager.begin() 宣告交易開始
  2. 對每一個XAResource開始作業處理(比如新增修改刪除資料或儲存檔案…)
  3. 最後叫用TransactionManager.commit()完成交易
    1. 上述步驟當中出了問題,立即叫用TransactionManager.rollback()退回並結束交易
在隨後的交易測試中加入了檔案讀寫,而檔案的交易使用了XADisk,她的範例中有針對XA交易說明,特別要說明的是XADisk是以目錄為基礎的,也就是說一個XADiskXAResource必須對應一個目錄。

第三個測試testRollbackWithTxm()使用Atomikos服務建立TransactionManager,程式依序加入了兩個資料庫與XADisk的的XAResource,然後在資料庫1塞入了一筆資料,其次將檔案寫入目錄,然後則在資料庫2塞入一筆重覆的資料以引發資料庫錯誤,最後檢查資料庫1的資料有無返回並確認之前寫入的檔案不存在
最後一個測試testCommitWithTxm()與第三個測試差不多,不過這次我們預期所有資料都會新增成功並且檔案也順利寫入目錄中。

關於UserTransaction

首先從命名來看,User Transaction,是以用戶角度來看,而不是全域角度出發(在AppServer的應用程式內,用戶沒有理由看到TransactionManager),用戶角度來看用戶從來不需要管理Resource是打那來的(用戶只知道透過JNDI取用資源),所以也無從知道要如何將Resource加入到交易中,用戶只管begin()commit()rollback()(Ejb3甚至只要用@TransactionAttribute來宣告即可),所以JEE api 才說TransactionManager是給Applicaton Server用來管理交易,而UserTransaction是讓程式來管理交易的。
所以理論上只要我們把Resource交給Application Server去管理,而我們只負責begin()commit()rollback()就夠了
真的要抱怨一下JBoss,用過JBoss的都知道其DataSrource的JNDI分為三種形態,大家比較知道的有<local-tx-datasource>與<xa-datasource>(Jboss EAP則為Datasource與XADataSource)。
從名稱上來看讓我誤解若是要做2PC就的宣告為為XADataSource,但是JBoss in Action的3.4.1 Deploying data sources說道:
<local-tx-datasource> Identifies a data source that uses transactions, even distributed transactions within the local application server, but doesn’t use distributed
transactions among multiple application servers.
If you’re
clustering your application servers or wanting to use distributed transactions among
multiple application servers, then you should use <xa-datasource>.
所以除非要架JBoss Cluster,否則用一般的DataSource就夠了(即使要做2PC)
註:我在JBoss-eap-6.4上測的結果是,若是一般的DataSource,它會警告:
This is transactionally unsafe and should not be relied upon.
所以應該還是用XA-DataSource比較好

所以我們只要將資料庫設定為Application Server的Datasource JNDI即可,至於XADisk要如何納入Application Server內管理?只要將XADisk發佈為JCA Resource Adapter即可,在作者者的討論區中有教JBossGlassFish(其它的可以直接上去問),不過特別要注意的是,發佈用的檔案必須是從官網上下載的Zip檔中取出的 rar 檔案,不要從其它的發佈到其它地方下載的Jar檔
至於Tomcat使用Atomikos, 官方有教學文(當然也有販售好的整合包),至於Jetty的設定參考這裡,但是XADisk如何在Tomcat或是Jetty裡設定JCA Resource,抱歉,目前為止沒有找到好的解決方法。所以不可避免地,TransactionManager會出現在程式中,也就是說目前除非把XADisk Deploy為JCA,否則就要想辦法在交易開始後呼到
enlistResource(),並在交易結束前對XADisk叫用delistResource()。
至於與Spring的整理,這裡有一篇可以參考, 我也找到一個範例專案,同時作者也在討論區提出了一個範例,請自行參考一下

留言

這個網誌中的熱門文章

企業人員的統一管理-FreeIPA學習筆記

證交所最佳五檔的程式解析

Postgresql HA