標籤:
XA介面詳解
X/Open XA介面是雙向的系統介面,在交易管理員(Transaction Manager)以及一個或多個資源管理員(Resource Manager)之間形成通訊橋樑。交易管理員控制著JTA事務,管理事務生命週期,並協調資源。在JTA中,交易管理員抽象為javax.transaction.TransactionManager介面,並通過底層事務服務(即JTS)實現。資源管理員負責控制和管理實際資源(如資料庫或JMS隊列)。說明了交易管理員、資源管理員,以及典型JTA環境中用戶端應用之間的關係:
注意,中XA介面形成了交易管理員和資源管理員之間的通訊橋樑。因為XA介面的雙向特質,XA支援兩階段交易認可協議,我們將在本章的後續部分討論。
本章所敘述的內容很難覆蓋XA介面的所有細節。如果讀者關心XA的細節,請參考X/Open XA介面規範(可在http://www.opengroup.org/onlinepubs/009680699/toc.pdf通過pdf的格式拿到)。
什麼時候應該使用XA?
在Java交易管理中,常常令人困惑的一個問題是什麼時候應該使用XA,什麼時候不應使用XA。由於大多數商業應用伺服器執行單階段提交(one-phase commit)操作,效能下降並非一個值得考慮的問題。然而,非必要性的在您的應用中引入XA資料庫驅動,會導致不可預料的後果與錯誤,特別是在使用本地事務模型(Local Transaction Model)時。因此,一般來說在您不需要XA的時候,應該盡量避免使用它。下面的最佳實務描述了什麼時候應當使用XA:
最佳實務 僅在同一個事務上下文中需要協調多種資源(即資料庫,以及訊息主題或隊列)時,才有必要使用X/Open XA介面。 |
這裡體現了一個重要的觀點,即雖然您的應用可能使用到多個資源,但僅當這些資源必須在同一個事務範疇內被協調時,才有必要用到XA。多個資源的情形包括訪問兩個或更多的資料庫(並不止是多個表,而是彼此分開的多個資料庫),或者一個資料庫加上一個訊息佇列,又或者是多個訊息佇列。您可能有一個應用同時使用到一個資料庫和一個訊息佇列。然而,如果這些資源並不在同一個事務中使用,就沒有必要去用XA。本章開始的代碼,預置一個固定收入交易,而後向隊列發送一條訊息,就是需要使用XA以便維護ACID特性的例子。
需要並使用XA最常見的情境是在同一個事務中協調資料庫更改和訊息佇列(或主題)。注意這兩種操作有可能在應用完全不同的地方出現(特別是在使用像hibernate這樣的ORM架構的時候)。XA事務必須在復原事件發生時協調兩種類型的資源,或讓更改與其他事務保持隔離。如果沒有XA,送往隊列或主題的訊息甚至會在事務終止前到達並被讀取。而在XA環境下,隊列中的訊息在事務提交之前不會被釋放。此外,如果是協調一個操作型資料庫和一個唯讀資料庫(即參考資料庫),您就不需要XA。然而,由於XA支援“唯讀最佳化”,當把一個唯讀資料來源引入XA事務時,您可能並不會看到任何的效能損失。
當意圖在您的企業Java應用中使用XA時,有幾個隱含的問題是需要考慮的。這些問題包括兩階段交易認可(2PC,two-phase commit process),經驗異常,以及XA驅動的使用。以下章節分別詳述了這些問題。
兩階段交易認可
兩階段交易認可協議(The two-phase commit protocol,2PC)是XA用於在全域事務中協調多個資源的機制。兩階段協議遵循OSI(Open System Interconnection,開放系統互聯)/DTP標準,雖然它比標準本身早若干年出現。兩階段交易認可協議包含了兩個階段:第一階段(也稱準備階段)和第二階段(也稱提交階段)。一個描述兩階段交易認可很好的類比是典型的結婚儀式,每個參與者(結婚典禮中的新郎和新娘)都必須服從安排,在正式步入婚姻生活之前說“我願意”。考慮有的杯具情形,“參與者”之一在做出承諾前的最後一刻反悔。兩階段交易認可之於此的結果也成立,雖然不具備那麼大的破壞性。
當commit()請求從用戶端向交易管理員發出,交易管理員開始兩階段交易認可過程。在第一階段,所有的資源被輪詢到,問它們是否準備好了提交作業。每個參與者可能回答“準備好(READY)”,“唯讀(READ_ONLY)”,或“未準備好(NOT_READY)”。如果有任意一個參與者在第一階段響應“未準備好(NOT_READY)”,則整個交易回復。如果所有參與者都回答“準備好(READY)”,那這些資源就在第二階段提交。回答“唯讀(READ_ONLY)”的資源,則在協議的第二階段處理中被排除掉。
由於XA環境中雙向通訊的能力,兩階段交易認可變得可能。在非XA事務環境中,通訊僅僅是單向的,兩階段交易認可沒法做到,這是因為交易管理員沒法接收到來自資源管理員的響應。大多數交易管理員為了最佳化效能,儘快釋放資源的目的,用多執行緒第一階段輪詢以及第二階段提交流程。展示了兩階段交易認可的基本流程:
展示了當資源管理員之一(DBMS)在第一階段輪詢時發生錯誤的情況下兩階段交易認可的過程,
在這個樣本中,一個提交請求被運行全域事務(global transaction,一個運行於XA之下的JTA事務)的用戶端發送到交易管理員。在第一階段,第二個資源管理員回給交易管理員一個“未準備好(NOT_READY)”響應。在本例中交易管理員對所有參與者發出復原請求,因此協調了在全域事務中的所有資源。
一些商業的應用程式容器提供一種稱之為“最後參與者支援(Last Participant Support)”的特性,該特性有個另外的名字叫“最後資源提交最佳化(Last Resource Commit Optimization)”。“最後參與者支援”允許非XA資源參與進全域事務。在“最後參與者支援”下,當一個XA環境的提交請求到達交易管理員,交易管理員會首先對XA資源發起第一階段流程。一旦XA參與者產生的結果一致返回,交易管理員隨即對非XA參與者發起提交(或復原)的請求。這個請求的結果決定了兩階段交易認可流程剩下的工作如何進行。如果對非XA資源的請求成功,交易管理員會發起第二階段,並對XA參與者發起提交請求。如果對非XA參與者的請求不成功,交易管理員會發起第二階段,並要求所有XA參與者復原事務。
“最後參與者支援”機制存在兩個問題。第一,它不是在應用程式容器間可移植的。第二,因為在第一階段輪詢過程和非XA最後參與資源提交之間有較長的時間等待,您會發現在使用這一特性時發生經驗異常(Heuristic Exception)的幾率增加了(將會在下一節詳述)。基於這些原因,“最後參與者支援”特性應該在一般情況下避免使用,除非迫不得已。
大多數商業應用伺服器還支援另一個最佳化,稱之為“一階段提交最佳化(One-Phase Commit Optimization)”。如果事務只包括一個參與者,第一階段處理會被忽略,單一的參與者被通知提交。在這種情況下,整個XA事務的後果取決於單一參與者的結果。
經驗異常(Heuristic Exception)處理
在兩階段交易認可的過程,資源管理員可能會使用“經驗化決策”的策略,或者提交,或者復原它自己的工作,而不受交易管理員的控制。“經驗化決策”是指根據多種內部和外部因素做出智能決定的過程。當資源管理員這麼做了,它會向用戶端報上一個經驗異常(Heuristic Exception)。
所幸的是,經驗異常並不是特別常見。它僅僅發生在XA環境下,做兩階段交易認可的過程中,特別是事務參與者在第一階段產生了響應之後。經驗異常最常見的原因是第一階段和第二階段之間的逾時情況。當通訊延遲或丟失,資源管理員或許要做出提交或復原其工作的決定,以釋放資源。不出意料,經驗異常發生最頻繁的時候正是高資源利用時間段。當您在應用中發現經驗異常時,您應該尋找是否有事務逾時問題,資源鎖定問題,以及資源使用過量問題,這些問題常常是經驗異常的根本原因。偶爾網路延遲或網路故障也會導致經驗異常。同樣的,如上面的章節所述,使用“最後參與者支援”特性會導致經驗異常更為頻繁的發生。
JTA暴露出的三種JTA經驗異常為HeuristicRollbackException,HeuristicCommitException,以及HeuristicMixedException。我們分別用下面的情境說明之:
情境1:在commit操作階段的HeuristicRollbackException異常
在此情境中,用戶端在XA環境下執行更新操作,向交易管理員發起提交當前事務的請求。交易管理員開啟兩階段交易認可流程的第一階段,隨即輪詢資源管理員。所有資源管理員向交易管理員報告說它們已經做好了提交事務的準備。然而,在(兩階段交易認可流程的)第一階段和第二階段之間每個資源管理員獨立的做出了復原它們已完成工作的經驗性決定。當進入第二階段,提交請求被發送到資源管理員時,因為所做的工作已經在此之前復原了,交易管理員將會向調用者報告HeuristicRollbackException異常。
當接受到此類異常時,常用的正確處理方式是將此異常傳回用戶端,讓用戶端重新提交請求。我們不能簡單的再次調用commit請求,因為對資料庫產生的更新已經隨復原操作從資料庫交易記錄中刪除了。下面的順序圖表說明了這一情境:
第一步:第一階段處理(準備階段)
第二步:在第一階段和第二階段之間
第三步:第二階段處理(提交階段)
正如您從順序圖表中看到的,兩個資源管理員復原了他們自己的工作,雖然他們在第一階段都向交易管理員報告了READY的響應。別擔心這些異常因何發生,我們在後續章節會做深入探討。
情境2:在commit操作階段的HeuristicMixedException異常
在此情境中,用戶端在XA環境下執行更新操作,向交易管理員發起提交當前事務的請求。交易管理員開啟兩階段交易認可流程的第一階段,隨即輪詢資源管理員。所有資源管理員向交易管理員報告說它們已經做好了提交事務的準備。和第一種情境不同的是,在第一階段和第二階段發生的間隙,有資源管理員(例如訊息佇列)做出了經驗性的決定提交其工作,而其他資源管理員(例如資料庫)做出了復原的經驗性決定。在這種情況下,交易管理員向調用者報告HeuristicMixedException異常。
這種情況下,非常難於選擇正確的後續應對方式,因為我們不知道哪些資源提交了工作,哪些資源復原了工作。所有目標資源因此處於一種不一致的狀態。因為資源管理員彼此互不干預的獨立操作,就經驗性決定而言,他們之間沒有任何協調和通訊。解決這一異常通常需要人力介入。下面的順序圖表說明了這一情境:
第一步:第一階段處理(準備階段)
第二步:在第一階段和第二階段之間
第三步:第二階段處理(提交階段)
注意在上面的圖示中,一個資源管理員提交了它的工作,而其他資源管理員選擇復原其工作。在這種情況下交易管理員將會報告HeuristicMixedException。
對訊息佇列或主題使用XA
在XA介面下使用的資源必須實現javax.transaction.xa.XAResource介面,以便自己能夠加入XA全域事務。對於JMS目標(隊列或主題),這可以通過在特定的應用伺服器控制台或管理程式中配置完成。真正啟用XA的部分是JMS串連工廠(Connection Factory)。一旦JMS串連工廠支援XA,發送給JMS隊列或主題的訊息在兩階段交易認可過程結束之前不會被釋放。在沒有XA的情況下,不論所處的事務上下文結果如何,發送給JMS目標的訊息會被立即釋放,並可被接收者拾取。
在WebLogic應用伺服器中,可在管理主控台的Services|JMS|Connection Factories配置中啟用XA的JMS串連工廠。在Transactions這個頁面,有一個名為XA Connection Factory Enabled的選項,經由此可以將JMS目標包含到JTA全域事務中。對於IBM WebSphere,可在管理主控台的Resources|WebSphere JMS Providers|Connection Factories路徑下選擇使用XA的JMS串連工廠功能。勾上名為EnableXA的選擇框就啟用了XA的JMS串連工廠。
為資料庫使用XA
可通過使用XA版的資料庫驅動來使資料庫支援XA。由於XA版的資料庫驅動通常比非XA的難用許多,一個忠告是不到不得已的時候別使用XA驅動。
使用XA版的資料庫驅動常會導致不可預期且難於解決的錯誤。例如,將非XA驅動替換為XA版驅動常常會產生難於跟蹤的錯誤。因此,應該在項目開發與測試階段儘早的引入XA驅動(,及早暴露問題並解決之)。
在使用XA版的資料庫驅動時,可能碰到的錯誤種類包括本地事務錯誤和嵌套事務錯誤。當您在一個XA全域事務進行中的過程中試圖開啟新的事務,這些錯誤就會產生。這種情形會在多個環境下發生,但最常見的情況是混合本地事務模型與聲明式事務模型導致,以及在XA環境下使用預存程序的情況。
當在XA環境下使用預存程序,在預存程序裡調用DDL(資料定義語句,如CREATE TABLE,BEGIN TRAN,END TRAN)常導致錯誤。這是最頻繁導致XA錯誤的罪魁禍首,並很難修正。例如在Oracle中,使用XA時,您可能會看到下面的錯誤資訊:
ORA-02089: COMMIT is not allowed in a subordinate session
如果使用非XA的資料庫驅動,大概您不會看到這個錯誤,因為DDL語句執行的時候JTA事務會暫停。當看到這個錯誤資訊,表明了您的預存程序中包含DDL代碼,並且(由資源管理員管理的)本地事務嘗試提交它的工作。
通常要從預存程序中刪除既有的DDL語句是困難的,因為它們在那裡一定有存在的理由,或者也許這些預存程序被其他應用所共用(,要刪除它們牽扯麵太大)。一個有效繞開問題的做法是,調用這些預存程序之前,手工的暫停事務;而在這些預存程序返回後,繼續事務。使用這個技巧會避免XA相關的本地和嵌套錯誤。然而,如果這樣做,預存程序做出的修改會獨立於JTA全域事務提交,因此違背了事務的ACID特性。所以說,這種做法僅僅是繞開問題,而不是解決問題。下面的程式碼片段展示了此技巧的細節:
... InitialContext ctx = new InitialContext(); TransactionManager tm = (javax.transaction.TransactionManager) ctx.lookup(“javax.transaction.TransactionManager”); Transaction currentTx = null; try { currentTx = tm.suspend(); invokeSPWithDDL(); } finally { if (currentTx != null) tm.resume(); }
即便在聲明式事務的環境下,我們仍然可以使用TransactionManager去用代碼方式暫停和繼續事務。這個技巧能夠避免XA環境下的SQL異常資訊,但它沒有真正的解決問題。——真正解決問題的唯一方法是在有關預存程序中刪除那些犯規的DDL語句,或者使用支援嵌套事務的JTA事務服務。
總結
本章要表達的最重要的思想是,理解什麼時候您真正需要使用XA版的資料庫驅動。許多開發人員和架構師總是堅持要使用XA版的資料庫驅動,雖然事實上不存在使用它們的合理理由。如果您需要在同一個事務中協調多個更改的資源(資料庫、訊息佇列、主題,或者JCA),那麼毫無疑問您需要引入XA介面。否則,千萬避開XA。
另一條關於使用XA的建議是,碰到問題時別總去猜測您使用的是一個可能錯誤百出的XA資料庫驅動。問題很有可能是您的應用代碼或交易處理邏輯造成的,而非XA驅動。
XA交易處理