在開始講解XA事務前,先引出一個例子來說解這樣比較容易理解XA事務。好比有一筆交易,在交易完成後,接受到到交易成功信息和扣款成功信息,代碼以下:html
public void savePayOrder(PayOrder payOrder) throws Exception { try { ...//交易前預備邏輯 PayOrderResult payOrderResult= payOrderService.save(payOrder); noticeService.excuteNotice(payOrderResult); } catch (PayOrderExecutionException e) { logger.error(e); sessionCtx.setRollbackOnly(); throw e; } }
在開頭首先查詢了一下訂單相關的業務參數,而後先保存交易信息,而後再更新相關信息,而這個過程須要操做多個表,最後達成交易。注意這裏有可能在保存一筆交易的時候,就拋出異常,致使後面沒法更新交易的相關信息。java
若是順利的話,這段代碼能夠保證遵循ACID準則。數據庫
這時候若是咱們加入一個新需求,好比須要交易成功後發送一條信息通知用戶交易成功,而且send()
方法裏實現全部消息邏輯,則例子改爲以下:segmentfault
public void savePayOrder(PayOrder payOrder) throws Exception { try { ...//交易前預備邏輯 PayOrderResult payOrderResult= payOrderService.save(payOrder); smsService.send(payOrderResult);//發送短信 noticeService.excuteNotice(payOrderResult); } catch (PayOrderExecutionException e) { logger.error(e); sessionCtx.setRollbackOnly(); throw e; } }
這段代碼卻不會保證ACID準則。若是excuteNotice()
方法拋出了PayOrderExecutionException
,數據庫更改將會回滾,但交易後發出的消息將會經過send()
方法被髮送到JMS
進行相關的訂閱或發送。網絡
在非XA環境中,消息隊列的插入過程獨立於數據庫更新操做,ACID準則中的原子性和獨立性不能獲得保證,從而總體上數據完整性受到損害。咱們須要的是,有一種方式可以讓消息隊列和數據庫處於單一事務的控制之下,以致於兩個資源能被協調造成單一工做單元。使用X/Open的XA接口,咱們便可以作到協調多個資源,保證維持ACID準則。session
XA接口是雙向的系統接口,分佈式事務是由一個一個應用程序(Application Program
)、一個事務管理器(Transaction Manager
)以及一個或多個資源管理器(Resource Manager
)之間造成通訊橋樑。事務管理器控制着JTA事務,管理事務生命週期,並協調資源。併發
在JTA
中,事務管理器抽象爲javax.transaction.TransactionManager
接口,並經過底層事務服務(即JTS
)實現。資源管理器負責控制和管理實際資源(如數據庫或JMS
隊列)。下圖說明了事務管理器、資源管理器,以及典型JTA環境中客戶端應用之間的關係:分佈式
XA分佈式事務是由一個或者多個Resource Managerd
,一個事務管理器Transaction Manager
以及一個應用程序 Application Program
組成。性能
資源管理器:提供訪問事務資源的方法,一般一個數據庫就是一個資源管理器。ui
事務管理器:協調參與全局事務中的各個事務。須要和參與全局事務中的資源管理器進行通訊。
應用程序:定義事務的邊界,指定全局事務中的操做。
許多事務管理器採用這種單階段提交的模式,能夠避免單一事務資源下的過分開銷,以及性能的降低,若是在不適合的場景中引入XA數據庫驅動,特別是資源比較侷限的狀況下使用本地事務模型(Local Transaction Model
)。
那究竟什麼狀況下使用XA事務呢?
通常來講,當你的上下邏輯結構涉及的表或者須要協調的資源(如數據庫,以及消息主題或隊列等)比較多的時候,建議使用XA。
或者對於該系統在將來對整個結構模塊趨於穩定,要求負載、代碼擴展等方面穩定性大於性能,則可選擇XA。
若是這些資源並不在同一個事務中使用,就沒有必要去用XA。
而對於性能要求很高的系統,建議使用 一階段提交(
Best Efforts 1PC)
或事務補償機制
。
二階段提交是分佈式事務的重要的一個關鍵點,二階段提交協議包含了兩個階段:第一階段(也稱準備階段)和第二階段(也稱提交階段)。
引用《Java事務設計策略》一圖
1. 準備階段:準備階段,每一個資源管理器都會被輪訓一遍,事務管理器給每一個資源管理器發送Prepare
消息,每一個資源管理器要麼直接返回失敗(如權限驗證失敗)或異常,要麼在本地執行事務等等,但不Commoit
,處於Ready
狀態。
2. 提交階段:若是事務管理器收到了資源管理器的失敗信息(如異常、超時等),直接給每一個資源管理器發送回滾(Rollback
)消息;不然,發送提交(Commit
)消息;資源管理器根據事務管理器的指令執行Commit
或者Rollback
操做,釋放全部事務處理過程當中使用的鎖資源。(注意:必須在最後階段釋放鎖資源)
能夠看出,二階段提交這麼作的就是讓前面都完成了準備工做,才能提交整個事務,若中間由某一環節出現問題,則整個事務回滾。
從兩階段提交的工做方式來看,很顯然,在提交事務的過程當中須要在多個節點之間進行協調,而各節點對鎖資源的釋放必須等到事務最終提交時,這樣,比起一階段提交,兩階段提交在執行一樣的事務時會消耗更多時間。事務執行時間的延長意味着鎖資源發生衝突的機率增長,當事務的併發量達到必定數量的時候,就會出現大量事務積壓甚至出現死鎖,系統性能就會嚴重下滑。
二階段提交看起來確實可以提供原子性的操做,可是不幸的事,二階段提交仍是有幾個缺點的:
一、同步阻塞問題。執行過程當中,全部參與節點都是事務阻塞型的。當參與者佔有公共資源時,其餘第三方節點訪問公共資源不得不處於阻塞狀態。
二、單點故障。因爲協調者的重要性,一旦協調者發生故障。參與者會一直阻塞下去。尤爲在第二階段,協調者發生故障,那麼全部的參與者還都處於鎖定事務資源的狀態中,而沒法繼續完成事務操做。(若是是協調者掛掉,能夠從新選舉一個協調者,可是沒法解決由於協調者宕機致使的參與者處於阻塞狀態的問題)
三、數據不一致。在二階段提交的階段二中,當協調者向參與者發送commit請求以後,發生了局部網絡異常或者在發送commit請求過程當中協調者發生了故障,這回致使只有一部分參與者接受到了commit請求。而在這部分參與者接到commit請求以後就會執行commit操做。可是其餘部分未接到commit請求的機器則沒法執行事務提交。因而整個分佈式系統便出現了數據部一致性的現象。
四、二階段沒法解決的問題:協調者再發出commit消息以後宕機,而惟一接收到這條消息的參與者同時也宕機了。那麼即便協調者經過選舉協議產生了新的協調者,這條事務的狀態也是不肯定的,沒人知道事務是否被已經提交。
參考資料
http://www.infoq.com/cn/articles/xa-transactions-handle
http://blog.csdn.net/bluishglc/article/details/7612811
http://www.open-open.com/lib/view/open1429863503010.html
http://hedengcheng.com/?p=136
http://www.hollischuang.com/archives/681
http://www.javashuo.com/article/p-dujybzdo-kv.html原創文章,版權全部,轉載請註明出處