XA協議
XA是一個分布式事務協議,由Tuxedo提出。XA中大致分為兩部分:事務管理器和本地資源管理器。其中本地資源管理器往往由數據庫實現,比如Oracle、DB2這些商業數據庫都實現了XA接口,而事務管理器作為全局的調度者,負責各個本地資源的提交和回滾。XA實現分布式事務的原理如下:
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驅動。
二階段提交看起來確實能夠提供原子性的操作,但是不幸的事,二階段提交還是有幾個缺點的:
1、同步阻塞問題。執行過程中,所有參與節點都是事務阻塞型的。當參與者占有公共資源時,其他第三方節點訪問公共資源不得不處於阻塞狀態。
2、單點故障。由於協調者的重要性,一旦協調者發生故障。參與者會一直阻塞下去。尤其在第二階段,協調者發生故障,那么所有的參與者還都處於鎖定事務資源的狀態中,而無法繼續完成事務操作。(如果是協調者掛掉,可以重新選舉一個協調者,但是無法解決因為協調者宕機導致的參與者處於阻塞狀態的問題)
3、數據不一致。在二階段提交的階段二中,當協調者向參與者發送commit請求之后,發生了局部網絡異常或者在發送commit請求過程中協調者發生了故障,這回導致只有一部分參與者接受到了commit請求。而在這部分參與者接到commit請求之后就會執行commit操作。但是其他部分未接到commit請求的機器則無法執行事務提交。於是整個分布式系統便出現了數據部一致性的現象。
4、二階段無法解決的問題:協調者再發出commit消息之后宕機,而唯一接收到這條消息的參與者同時也宕機了。那么即使協調者通過選舉協議產生了新的協調者,這條事務的狀態也是不確定的,沒人知道事務是否被已經提交。
由於二階段提交存在着諸如同步阻塞、單點問題、腦裂等缺陷,所以,研究者們在二階段提交的基礎上做了改進,提出了三階段提交。
3PC
三階段提交(Three-phase commit),也叫三階段提交協議(Three-phase commit protocol),是二階段提交(2PC)的改進版本。
與兩階段提交不同的是,三階段提交有兩個改動點。
1、引入超時機制。同時在協調者和參與者中都引入超時機制。2、在第一階段和第二階段中插入一個准備階段。保證了在最后提交階段之前各參與節點的狀態是一致的。
也就是說,除了引入超時機制之外,3PC把2PC的准備階段再次一分為二,這樣三階段提交就有CanCommit
、PreCommit
、DoCommit
三個階段。
CanCommit階段
3PC的CanCommit階段其實和2PC的准備階段很像。協調者向參與者發送commit請求,參與者如果可以提交就返回Yes響應,否則返回No響應。
1.事務詢問 協調者向參與者發送CanCommit請求。詢問是否可以執行事務提交操作。然后開始等待參與者的響應。
2.響應反饋 參與者接到CanCommit請求之后,正常情況下,如果其自身認為可以順利執行事務,則返回Yes響應,並進入預備狀態。否則反饋No
PreCommit階段
協調者根據參與者的反應情況來決定是否可以記性事務的PreCommit操作。根據響應情況,有以下兩種可能。
假如協調者從所有的參與者獲得的反饋都是Yes響應,那么就會執行事務的預執行。
1.發送預提交請求 協調者向參與者發送PreCommit請求,並進入Prepared階段。
2.事務預提交 參與者接收到PreCommit請求后,會執行事務操作,並將undo和redo信息記錄到事務日志中。
3.響應反饋 如果參與者成功的執行了事務操作,則返回ACK響應,同時開始等待最終指令。
假如有任何一個參與者向協調者發送了No響應,或者等待超時之后,協調者都沒有接到參與者的響應,那么就執行事務的中斷。
1.發送中斷請求 協調者向所有參與者發送abort請求。
2.中斷事務 參與者收到來自協調者的abort請求之后(或超時之后,仍未收到協調者的請求),執行事務的中斷。
doCommit階段
該階段進行真正的事務提交,也可以分為以下兩種情況。
執行提交
1.發送提交請求 協調接收到參與者發送的ACK響應,那么他將從預提交狀態進入到提交狀態。並向所有參與者發送doCommit請求。
2.事務提交 參與者接收到doCommit請求之后,執行正式的事務提交。並在完成事務提交之后釋放所有事務資源。
3.響應反饋 事務提交完之后,向協調者發送Ack響應。
4.完成事務 協調者接收到所有參與者的ack響應之后,完成事務。
中斷事務 協調者沒有接收到參與者發送的ACK響應(可能是接受者發送的不是ACK響應,也可能響應超時),那么就會執行中斷事務。
1.發送中斷請求 協調者向所有參與者發送abort請求
2.事務回滾 參與者接收到abort請求之后,利用其在階段二記錄的undo信息來執行事務的回滾操作,並在完成回滾之后釋放所有的事務資源。
3.反饋結果 參與者完成事務回滾之后,向協調者發送ACK消息
4.中斷事務 協調者接收到參與者反饋的ACK消息之后,執行事務的中斷。
2PC與3PC的區別
相對於2PC,3PC主要解決的單點故障問題,並減少阻塞,因為一旦參與者無法及時收到來自協調者的信息之后,他會默認執行commit。而不會一直持有事務資源並處於阻塞狀態。但是這種機制也會導致數據一致性問題,因為,由於網絡原因,協調者發送的abort響應沒有及時被參與者接收到,那么參與者在等待超時之后執行了commit操作。這樣就和其他接到abort命令並執行回滾的參與者之間存在數據不一致的情況。
轉:https://my.oschina.net/u/138995/blog/178280