轉載:https://yq.aliyun.com/articles/283392
1、什么是分布式事務
分布式事務就是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位于不同的分布式系統的不同節點之上。以上是百度百科的解釋,簡單的說,就是一次大的操作由不同的小操作組成,這些小的操作分布在不同的服務器上,且屬于不同的應用,分布式事務需要保證這些小操作要么全部成功,要么全部失敗。本質上來說,分布式事務就是為了保證不同數據庫的數據一致性。
2、分布式事務的產生的原因
2.1、數據庫分庫分表
當數據庫單表一年產生的數據超過1000W,那么就要考慮分庫分表,具體分庫分表的原理在此不做解釋,以后有空詳細說,簡單的說就是原來的一個數據庫變成了多個數據庫。這時候,如果一個操作既訪問01庫,又訪問02庫,而且要保證數據的一致性,那么就要用到分布式事務。
2.2、應用SOA化
所謂的SOA化,就是業務的服務化。比如原來單機支撐了整個電商網站,現在對整個網站進行拆解,分離出了訂單中心、用戶中心、庫存中心。對于訂單中心,有專門的數據庫存儲訂單信息,用戶中心也有專門的數據庫存儲用戶信息,庫存中心也會有專門的數據庫存儲庫存信息。這時候如果要同時對訂單和庫存進行操作,那么就會涉及到訂單數據庫和庫存數據庫,為了保證數據一致性,就需要用到分布式事務。
以上兩種情況表象不同,但是本質相同,都是因為要操作的數據庫變多了!
3、事務的ACID特性
3.1、原子性(A)
所謂的原子性就是說,在整個事務中的所有操作,要么全部完成,要么全部不做,沒有中間狀態。對于事務在執行中發生錯誤,所有的操作都會被回滾,整個事務就像從沒被執行過一樣。
3.2、一致性(C)
事務的執行必須保證系統的一致性,就拿轉賬為例,A有500元,B有300元,如果在一個事務里A成功轉給B50元,那么不管并發多少,不管發生什么,只要事務執行成功了,那么最后A賬戶一定是450元,B賬戶一定是350元。
3.3、隔離性(I)
所謂的隔離性就是說,事務與事務之間不會互相影響,一個事務的中間狀態不會被其他事務感知。
3.4、持久性(D)
所謂的持久性,就是說一單事務完成了,那么事務對數據所做的變更就完全保存在了數據庫中,即使發生停電,系統宕機也是如此。
4、分布式事務的應用場景
4.1、支付
最經典的場景就是支付了,一筆支付,是對買家賬戶進行扣款,同時對賣家賬戶進行加錢,這些操作必須在一個事務里執行,要么全部成功,要么全部失敗。而對于買家賬戶屬于買家中心,對應的是買家數據庫,而賣家賬戶屬于賣家中心,對應的是賣家數據庫,對不同數據庫的操作必然需要引入分布式事務。
4.2、在線下單
買家在電商平臺下單,往往會涉及到兩個動作,一個是扣庫存,第二個是更新訂單狀態,庫存和訂單一般屬于不同的數據庫,需要使用分布式事務保證數據一致性。
5、常見的分布式事務解決方案
5.1、基于XA協議的兩階段提交
XA是一個分布式事務協議,由Tuxedo提出。XA中大致分為兩部分:事務管理器和本地資源管理器。其中本地資源管理器往往由數據庫實現,比如Oracle、DB2這些商業數據庫都實現了XA接口,而事務管理器作為全局的調度者,負責各個本地資源的提交和回滾。XA實現分布式事務的原理如下:
總的來說,XA協議比較簡單,而且一旦商業數據庫實現了XA協議,使用分布式事務的成本也比較低。但是,XA也有致命的缺點,那就是性能不理想,特別是在交易下單鏈路,往往并發量很高,XA無法滿足高并發場景。XA目前在商業數據庫支持的比較理想,在mysql數據庫中支持的不太理想,mysql的XA實現,沒有記錄prepare階段日志,主備切換回導致主庫與備庫數據不一致。許多nosql也沒有支持XA,這讓XA的應用場景變得非常狹隘。
XA事務與MySQL
XA事務就是兩階段提交的一種實現方式
XA規范主要定義了事務管理器TM,和資源管理器RM之間的接口
根據2PC的規范,將一次事務分割成兩個階段
1. prepare階段
TM向所有RM發送prepare指令,RM接受到指令后執行數據修改和日志記錄等操作,然后返回 可以提交/不可提交 給TM
(按照我的理解應該類似于MySQL在開啟一個事務之后,只差最后的COMMIT或者ROLLBACK的狀態)
2. commit階段
TM接受到所有RM的prepare結果
如果有RM返回是 不可提交 或者超時,那么向所有RM發送ROLLBACK命令
如果所有RM都返回可以提交,那么向所有RM發送COMMIT命令
XA的異常情況處理
MySQL與XA事務的關系有兩種情況
1. 內部XA
在使用innodb作為存儲引擎,并且開啟binlog的情況下,MySQL同時維護了binlog日志與innodb的redo log
為了保證這兩個日志的一致性,MySQL使用了XA事務,由于只在單機上工作,所以被稱為內部XA
2. 外部XA
就是一般談論的分布式事務了
MySQL支持XA START/END/PREPARE/COMMIT這些sql語句,通過使用這些命令,我們是可以完成分布式事務的
狀態轉移圖如下
(我有點不能理解的是,為什么一定需要XA END這個語句,直接XA PREPARE不行嗎)
在MySQL5.7.7之前,XA事務是有bug的
如果有一個XA事務處于PREPARE狀態
1. 如果連接關閉,或者MySQL服務器正常退出,這個事務會被回滾(但是根據XA規范,這個事務應該被保留)
2. 如果MySQL服務器被強制結束,在重啟之后,用XA RECOVER命令可以看到這個事務,這個事務也可以被XA COMMIT所提交,但是相關的binlog記錄會丟失,這樣就會導致數據庫引擎中的數據與binlog中的數據不一致 (參考資料)
這兩個bug被提出了十年之久,終于在5.7.7中被修正了(第一個bug阿里自己也搞了個修正)
就目前來看,MySQL的XA事務現在做得還不錯,應該是可用的
還是有一些不能理解的地方
1. 官方文檔中強調:在使用分布式事務的時候,需要使用串行隔離級別,為什么?
(As with nondistributed transactions, SERIALIZABLE may be preferred if your applications are sensitive to read phenomena. REPEATABLE READ may not be sufficient for distributed transactions.)
原因:為了盡可能提高分布式事物的隔離級別,如果分庫上使用MySQL默認的RR,那么導致總的分布式事務的隔離級別為RU
參考資料
2. MySQL redolog與組提交 資料1 資料2 資料3 資料4
普通事務
普通事務的實現是比較好理解的。以jdbm3為例,大概是這樣的過程:
每個事務都新建一個事務文件,當commit時,先把修改過的數據塊,寫到事務文件里,然后再一次性地寫到數據庫文件里。
如果commit時掛掉了,那么重啟之后,會再次從事務文件里把修改過的塊寫到數據庫文件里。最后再刪除事務文件。
https://github.com/jankotek/JDBM3
但是XA事務,即所謂的分布式事務卻令人感到云里霧里。一是資料很少,網上的各種配置資料都是流于表面;二是可能實際應用的人也少。
最近研究了下,算是找到點門道了。
二階段提交(Two-phase Commit)
首先,XA事務是基于二階段提交(Two-phase Commit)實現的。二階段提交本身并沒有什么令人疑惑的地方。看wiki就可以知道是怎么回事了。
簡而言之,有二種角色,事務管理者(DM, Transaction Manager),資源管理器(RM, Resource Manager),通常即數據庫或者JMS服務器。
下面兩個圖片來自:http://www.infoq.com/cn/articles/xa-transactions-handle
[圖片上傳失敗...(image-463692-1585407318380)]
出錯回滾:
[圖片上傳失敗...(image-da1992-1585407318380)]
當然,還有各種中間出錯時,要處理的情況,詳細可以看infoq的原文。
令人疑惑的atomikos
二階段提交協議是很容易理解的,但是真正令我疑惑的是Java實現的atomikos,一個分布式事務的Transaction Manager組件。
開始的時候,我以為事務管理器(TM)都是獨立的一個服務,或者一個獨立的進程,它和資源管理器(RM)之間通過網絡通迅。
但是在網上看一些atomikos配置文章,都沒有提到如何配置一個獨立的Transaction Manager,只是簡單地介紹了下如何配置atomikos,這些配置都是和應用在一起的。
而從配置里面也沒法看出是如何保證在事務過程中,如果應用的進程掛掉后,是如何恢復的。
再把atomikos的例子代碼下載下來,發現也沒有提到是如何保證事務在失敗后,如何協調的。
比如,在第二段提交時,當RM1 commit完成了,而RM2 commit還沒有完成,而這時TM,即配置了atomikos的應用程序崩潰,那么這個事務并沒有完成,還需要TM重啟后協調,才能最終完成這個事務。但是沒看到恢復部分的配置。
沒辦法,只能親自跑一遍代碼了。
跑了下atomikos的代碼,在第二階段提交時,把進程殺掉,發現的確是可以自動處理回滾事務,或者再次提交的。那么信息是保存在哪里的?也沒有看到有什么配置文件。
最終,只能下XA的規范下載下來,再一點點慢慢看。
在The XA Specification里的2.3小節:Transaction Completion and Recovery 明確提到TM是要記錄日志的:
In Phase 2, the TM issues all RMs an actual request to commit or roll back the
transaction branch, as the case may be. (Before issuing requests to commit, the TM
stably records the fact that it decided to commit, as well as a list of all involved RMs.)
All RMs commit or roll back changes to shared resources and then return status to the
TM. The TM can then discard its knowledge of the global transaction.
TM是一定要把事務的信息,比如XID,哪個RM已經完成了等保存起來的。只有當全部的RM提交或者回滾完后,才能丟棄這些事務的信息。
于是再查看下atomikos例子運行目錄,果然有一些文件日志文件:
127.0.1.1.tm13.epoch
tmlog13.log
tmlog.lck
tm.out
tm.out.lck
原來atomikos是通過在應用的目錄下生成日志文件來保證,如果失敗,在重啟后可以通過日志來完成未完成的事務。
XA事務的假設條件
從XA的規范里找到了下面的說法:
The X/Open DTP model makes these assumptions:
TMs and RMs have access to stable storage TM和RM都有牢靠的存儲
TMs coordinate and control recovery TM協調和控制恢復流程
RMs provide for their own restart and recovery of their own state. On request, an RM must give a TM a list of XIDs that the RM has prepared for commitment or has heuristically completed. RM在得啟和恢復時,得回應TM的請求,返回一系列的XID,是prepared的,或者是已經啟發式地完成了的
也就是說,XA事務都假定了TM和RM都是有牢靠的存儲的,所以也保證了TM重啟后可以從日志里恢復還沒處理完的事務。
TM可以向RM查詢事務的狀態,RM必須要返回一系列事務的XID,表明事務是prepared狀態,還是已經commit的狀態。
到這里,應該很明了了,XA事務是其限制的,而TM是XA事務的一個單點,TM必須要非常地牢靠。
從XA的接口函數,就可以大概看出協議是怎么工作的(來自XA規范文檔):
[圖片上傳失敗...(image-67987f-1585407318379)]
如何避免XA事務
XA事務的明顯問題是timeout問題,比如當一個RM出問題了,那么整個事務只能處于等待狀態。這樣可以會連鎖反應,導致整個系統都很慢,最終不可用。
避免使用XA事務的方法通常是最終一致性。
舉個例子,比如用戶充值300元,為了減少DB的壓力,先把這個放到消息隊列里,然后后端再從消息隊列里取出消息,更新DB。
那么如何保證,這條消息不會被重復消費?或者重復消費后,仍能保證結果是正確的?
- 在消息里帶上用戶帳號在數據庫里的版本,在更新時比較數據的版本,如果相同則加上300;
- 比如用戶本來有500元,那么消息是更新用戶的錢數為800,而不是加上300;
- 另外建一個消息是否被消費的表,記錄消息ID,在事務里,先判斷消息是否已經消息過,如果沒有,則更新數據庫,加上300,否則說明已經消費過了,丟棄。
前面兩種方法都必須從流程上保證是單方向的,不能插入其它的東東。
其它的一些東東:
貌似一直有人想用zookeeper來實現2pc,或者類似的東東,因為zookeeper是比較可靠的。但是感覺也沒有辦法解決timeout問題。
微軟的XA事務恢復流程的文檔:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms681775(v=vs.85).aspx
There are two forms of XA transaction recovery, as follows:
- Cold recovery. Cold recovery performed if the transaction manager process fails while a connection to an XA resource manager is open. When the transaction manager restarts, it reads the transaction manager log file and re-establishes the connection to the XA resource manager by calling xa_open_entry. It then initiates XA recover by calling xa_recover_entry.
- Hot recovery. Hot recovery is performed if the transaction manager remains up while the connection between the transaction manager and the XA resource manager fails because the XA resource manager or the network fails. After the failure, the transaction manager periodically calls xa_open_entry to reconnect to the XA resource manager. When the connection is reestablished, the transaction manager initiates XA recovery by calling xa_recover_entry.
總結:
XA事務沒有什么神秘的地方,二階段提交也是一個人們很自然的一個處理方式。
只不過,這個是規范,如果有多個資源之間要協調,而且都支持XA事務,那么會比較方便 。
參考:
The XA Specification 可以從這里下載到:http://download.csdn.NET/detail/hengyunabc/6940529
http://en.wikipedia.org/wiki/Two-phase_commit_protocol
http://www.infoq.com/cn/articles/xa-transactions-handle
http://java.sun.com/javaee/technologies/jta/index.jsp
https://github.com/bitronix/btm 一個開源的JTA Transaction Manager
XA接口詳解
XA接口是雙向的系統接口,分布式事務是由一個一個應用程序(Application Program)、一個事務管理器(Transaction Manager)以及一個或多個資源管理器(Resource Manager)之間形成通信橋梁。事務管理器控制著JTA事務,管理事務生命周期,并協調資源。
在JTA中,事務管理器抽象為javax.transaction.TransactionManager接口,并通過底層事務服務(即JTS)實現。資源管理器負責控制和管理實際資源(如數據庫或JMS隊列)。下圖說明了事務管理器、資源管理器,以及典型JTA環境中客戶端應用之間的關系:
XA分布式事務是由一個或者多個Resource Managerd,一個事務管理器Transaction Manager以及一個應用程序 Application Program組成。
資源管理器:提供訪問事務資源的方法,通常一個數據庫就是一個資源管理器。
事務管理器:協調參與全局事務中的各個事務。需要和參與全局事務中的資源管理器進行通信。
應用程序:定義事務的邊界,指定全局事務中的操作。
XA使用場景
許多事務管理器采用這種單階段提交的模式,可以避免單一事務資源下的過度開銷,以及性能的下降,如果在不適合的場景中引入XA數據庫驅動,特別是資源比較局限的情況下使用本地事務模型(Local Transaction Model)。
那究竟什么情況下使用XA事務呢?
一般來說,當你的上下邏輯結構涉及的表或者需要協調的資源(如數據庫,以及消息主題或隊列等)比較多的時候,建議使用XA。
或者對于該系統在未來對整個結構模塊趨于穩定,要求負載、代碼擴展等方面穩定性大于性能,則可選擇XA。
如果這些資源并不在同一個事務中使用,就沒有必要去用XA。
而對于性能要求很高的系統,建議使用 一階段提交(Best Efforts 1PC)或事務補償機制。
二階段提交(The two-phase commit protocol,2PC)
二階段提交是分布式事務的重要的一個關鍵點,二階段提交協議包含了兩個階段:第一階段(也稱準備階段)和第二階段(也稱提交階段)。
引用《Java事務設計策略》一圖
1. 準備階段:準備階段,每個資源管理器都會被輪訓一遍,事務管理器給每個資源管理器發送Prepare消息,每個資源管理器要么直接返回失敗(如權限驗證失敗)或異常,要么在本地執行事務等等,但不Commoit,處于Ready狀態。
2. 提交階段:如果事務管理器收到了資源管理器的失敗信息(如異常、超時等),直接給每個資源管理器發送回滾(Rollback)消息;否則,發送提交(Commit)消息;資源管理器根據事務管理器的指令執行Commit或者Rollback操作,釋放所有事務處理過程中使用的鎖資源。(注意:必須在最后階段釋放鎖資源)
可以看出,二階段提交這么做的就是讓前面都完成了準備工作,才能提交整個事務,若中間由某一環節出現問題,則整個事務回滾。
從兩階段提交的工作方式來看,很顯然,在提交事務的過程中需要在多個節點之間進行協調,而各節點對鎖資源的釋放必須等到事務最終提交時,這樣,比起一階段提交,兩階段提交在執行同樣的事務時會消耗更多時間。事務執行時間的延長意味著鎖資源發生沖突的概率增加,當事務的并發量達到一定數量的時候,就會出現大量事務積壓甚至出現死鎖,系統性能就會嚴重下滑。
二階段提交看起來確實能夠提供原子性的操作,但是不幸的事,二階段提交還是有幾個缺點的:
1、同步阻塞問題。執行過程中,所有參與節點都是事務阻塞型的。當參與者占有公共資源時,其他第三方節點訪問公共資源不得不處于阻塞狀態。
2、單點故障。由于協調者的重要性,一旦協調者發生故障。參與者會一直阻塞下去。尤其在第二階段,協調者發生故障,那么所有的參與者還都處于鎖定事務資源的狀態中,而無法繼續完成事務操作。(如果是協調者掛掉,可以重新選舉一個協調者,但是無法解決因為協調者宕機導致的參與者處于阻塞狀態的問題)
3、數據不一致。在二階段提交的階段二中,當協調者向參與者發送commit請求之后,發生了局部網絡異常或者在發送commit請求過程中協調者發生了故障,這回導致只有一部分參與者接受到了commit請求。而在這部分參與者接到commit請求之后就會執行commit操作。但是其他部分未接到commit請求的機器則無法執行事務提交。于是整個分布式系統便出現了數據部一致性的現象。
4、二階段無法解決的問題:協調者再發出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
事務和兩階段提交,三階段提交協議(有限狀態自動機)
?1 事務的ACID
事務是保證數據庫從一個一致性的狀態永久地變成另外一個一致性狀態的根本,其中,ACID是事務的基本特性。
A是Atomicity,原子性。一個事務往往涉及到許多的子操作,原子性則保證這些子操作要么都做,要么都不做,而不至于出現事務的部分操
作成功,而另外一部分操作沒有成功。如果事務在執行的過程中發生錯誤,那么數據庫將回滾到事務發生之前的狀態。比如銀行的轉賬服務
,這個事務的最終結果一定是:某個賬戶的余額增加了x,而另外一個賬戶的余額減少了x,或者兩個賬戶的余額未發生變化。而不會出現其
他情況。
C是Consistency,一致性。一致性是指事務發生前和發生以后,都不會破壞數據庫的約束關系,保證了數據庫元素的正確性、有效性和完整
性。這種約束關系可以是數據庫內部的約束,比如數據庫元素的值必須在一定的范圍內,也可以是應用帶來的約束,比如轉賬以后銀行賬戶
的余額不能為負數。
I是Isolation,隔離性。一個事務的操作在未提交以前,是不會被并行發生的其他事務訪問到的。也就是說,數據庫操作不會看到某個事務
的中間操作結果,比如轉賬過程中,用戶是不能查詢到一個賬戶余額減少了,而另外一個賬戶余額未發生變化的情況。
D是Durability,持久性。事務完成以后,它對數據庫的影響是永久性的,即使在數據庫系統發生宕機或者其他故障的情況下,這種影響也
會得到保持。
?2 兩階段提交
應用在分布式系統中。
在分布式系統中,事務往往包含有多個參與者的活動,單個參與者上的活動是能夠保證原子性的,而多個參與者之間原子性的保證則需要通
過兩階段提交來實現,兩階段提交是分布式事務實現的關鍵。
很明顯,兩階段提交保證了分布式事務的原子性,這些子事務要么都做,要么都不做。而數據庫的一致性是由數據庫的完整性約束實現的,
持久性則是通過commit日志來實現的,不是由兩階段提交來保證的。至于兩階段提交如何保證隔離性,可以參考Large-scale Incremental
Processing Using Distributed Transactions and Notifications中兩階段提交的具體實現。
兩階段提交的過程涉及到協調者和參與者。協調者可以看做成事務的發起者,同時也是事務的一個參與者。對于一個分布式事務來說,一個
事務是涉及到多個參與者的。具體的兩階段提交的過程如下:
第一階段:
首先,協調者在自身節點的日志中寫入一條的日志記錄,然后所有參與者發送消息prepare T,詢問這些參與者(包括自身),是否能夠提
交這個事務;
參與者在接受到這個prepare T 消息以后,會根據自身的情況,進行事務的預處理,如果參與者能夠提交該事務,則會將日志寫入磁盤,并
返回給協調者一個ready T信息,同時自身進入預提交狀態狀態;如果不能提交該事務,則記錄日志,并返回一個not commit T信息給協調
者,同時撤銷在自身上所做的數據庫改;
參與者能夠推遲發送響應的時間,但最終還是需要發送的。
第二階段:
協調者會收集所有參與者的意見,如果收到參與者發來的not commit T信息,則標識著該事務不能提交,協調者會將Abort T 記錄到日志中
,并向所有參與者發送一個Abort T 信息,讓所有參與者撤銷在自身上所有的預操作;
如果協調者收到所有參與者發來prepare T信息,那么協調者會將Commit T日志寫入磁盤,并向所有參與者發送一個Commit T信息,提交該
事務。若協調者遲遲未收到某個參與者發來的信息,則認為該參與者發送了一個VOTE_ABORT信息,從而取消該事務的執行。
參與者接收到協調者發來的Abort T信息以后,參與者會終止提交,并將Abort T 記錄到日志中;如果參與者收到的是Commit T信息,則會
將事務進行提交,并寫入記錄
一般情況下,兩階段提交機制都能較好的運行,當在事務進行過程中,有參與者宕機時,他重啟以后,可以通過詢問其他參與者或者協調者
,從而知道這個事務到底提交了沒有。當然,這一切的前提都是各個參與者在進行每一步操作時,都會事先寫入日志。
唯一一個兩階段提交不能解決的困境是:當協調者在發出commit T消息后宕機了,而唯一收到這條命令的一個參與者也宕機了,這個時候這
個事務就處于一個未知的狀態,沒有人知道這個事務到底是提交了還是未提交,從而需要數據庫管理員的介入,防止數據庫進入一個不一致
的狀態。當然,如果有一個前提是:所有節點或者網絡的異常最終都會恢復,那么這個問題就不存在了,協調者和參與者最終會重啟,其他
節點也最終也會收到commit T的信息。
?3 日志
數據庫日志保證了事務執行的原子性和持久性,日志類型可以分為redo log,undo log,undo/redo log。關于這幾種日志形式的具體介紹
,可以參照:
http://nosql-wiki.org/foswiki/bin/view/Main/TransactonLog
--------------------
兩階段提交協議(two phase commit protocol,2PC)可以保證數據的強一致性,許多分布式關系型數據管理系統采用此協議來完成分布式
事務。它是協調所有分布式原子事務參與者,并決定提交或取消(回滾)的分布式算法。同時也是解決一致性問題的一致性算法。該算法能
夠解決很多的臨時性系統故障(包括進程、網絡節點、通信等故障),被廣泛地使用。但是,它并不能夠通過配置來解決所有的故障,在某
些情況下它還需要人為的參與才能解決問題。參與者為了能夠從故障中恢復,它們都使用日志來記錄協議的狀態,雖然使用日志降低了性能
但是節點能夠從故障中恢復。
在兩階段提交協議中,系統一般包含兩類機器(或節點):一類為協調者(coordinator),通常一個系統中只有一個;另一類為事務參與
者(participants,cohorts或workers),一般包含多個,在數據存儲系統中可以理解為數據副本的個數。協議中假設每個節點都會記錄寫
前日志(write-ahead log)并持久性存儲,即使節點發生故障日志也不會丟失。協議中同時假設節點不會發生永久性故障而且任意兩個節
點都可以互相通信。
當事務的最后一步完成之后,協調器執行協議,參與者根據本地事務能夠成功完成回復同意提交事務或者回滾事務。
顧名思義,兩階段提交協議由兩個階段組成。在正常的執行下,這兩個階段的執行過程如下所述:
階段1:請求階段(commit-request phase,或稱表決階段,voting phase)
在請求階段,協調者將通知事務參與者準備提交或取消事務,然后進入表決過程。在表決過程中,參與者將告知協調者自己的決策:同意(
事務參與者本地作業執行成功)或取消(本地作業執行故障)。
階段2:提交階段(commit phase)
在該階段,協調者將基于第一個階段的投票結果進行決策:提交或取消。當且僅當所有的參與者同意提交事務協調者才通知所有的參與者提
交事務,否則協調者將通知所有的參與者取消事務。參與者在接收到協調者發來的消息后將執行響應的操作。
注意 兩階段提交協議與兩階段鎖協議不同,兩階段鎖協議為一致性控制協議。
XA
XA是X/Open DTP組織(X/Open DTP group)定義的兩階段提交協議,XA被許多數據庫(如Oracle和DB2)和中間件等工具(如CICS 和
Tuxedo).本地支持 。
X/Open DTP模型(1994)包括應用程序(AP)、事務管理器(TM)、資源管理器(RM)、通信資源管理器(CRM)四部分。在這個模型中,
通常事務管理器(TM)是交易中間件,資源管理器(RM)是數據庫,通信資源管理器(CRM)是消息中間件。
一般情況下,某一數據庫無法知道其它數據庫在做什么,因此,在一個DTP環境中,交易中間件是必需的,由它通知和協調相關數據庫的提
交或回滾。而一個數據庫只將其自己所做的操作(可恢復)影射到全局事務中。
XA就是X/Open DTP定義的交易中間件與數據庫之間的接口規范(即接口函數),交易中間件用它來通知數據庫事務的開始、結束以及提交、
回滾等。XA接口函數由數據庫廠商提供。通常情況下,交易中間件與數據庫通過XA 接口規范,使用兩階段提交來完成一個全局事務,XA規
范的基礎是兩階段提交協議。
MySQL分布式XA事務
XA–eXtended Architecture 在事務中意為分布式事務
XA由協調者(coordinator,一般為transaction manager)和參與者(participants,一般在各個資源上有各自的resource manager)共同完成。在MySQL中,XA事務有兩種。
內部XA事務
mysql本身的插件式架構導致在其內部需要使用XA事務,此時MySQL即是協調者,也是參與者。例如,不同的存儲引擎之間是完全獨立的,因此當一個事務涉及兩個不同的存儲引擎時,就必須使用內部XA事務。需要特別注意的是,如果將二進制日志看做一個獨立的“存儲引擎”,就不難理解為什么即使是一個存儲引擎參與的事務也需要使用XA事務了。在向存儲引擎提交數據時,同時需要將提交的信息寫入二進制日志,這就是一個分布式事務。
下面的SQL就實現了一個簡單的MySQL XA事務:
<pre>mysql> XA START 'xatest';
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO mytable (i) VALUES(10);
Query OK, 1 row affected (0.04 sec)
mysql> XA END 'xatest';
Query OK, 0 rows affected (0.00 sec)
mysql> XA PREPARE 'xatest';
Query OK, 0 rows affected (0.00 sec)
mysql> XA COMMIT 'xatest';
Query OK, 0 rows affected (0.00 sec) </pre>
外部XA事務
MySQL也可以僅作為一個外部XA的參與者。例如在Java程序中實現一個操作多個異構數據庫數據的分布式事務,則分工為:
Transaction Manager
javax.transaction.xa.XAResource, or Java::BitronixTm(aka “Transaction Coordinator”) 也就是程序本身
Resource Managers
MySQL, PostgreSQL, Oracle, Redis, MQueue, MongoDB, etc.(aka “cohorts”) 即各個數據庫。也就是說只要是提供了相應接口的數據庫產品就可以作為XA的參與者
Two-phase commit
XA一般由兩階段完成,稱為two-phase commit(2PC)。
階段一為準備階段,即所有的參與者準備執行事務并鎖住需要的資源。參與者ready時,向transaction manager匯報自己已經準備好。
階段二為提交階段。當transaction manager確認所有參與者都ready后,向所有參與者發送commit命令。
如下圖所示:
[圖片上傳失敗...(image-dd3598-1585407318371)]
XA的性能問題
XA的性能很低。一個數據庫的事務和多個數據庫間的XA事務性能對比可發現,性能差10倍左右。因此要盡量避免XA事務,例如可以將數據寫入本地,用高性能的消息系統分發數據。或使用數據庫復制等技術。
只有在這些都無法實現,且性能不是瓶頸時才應該使用XA。
最后是MySQL的XA官方文檔:
http://dev.mysql.com/doc/refman/5.7/en/xa.html
詳解Mysql分布式事務XA(跨數據庫事務)
在開發中,為了降低單點壓力,通常會根據業務情況進行分表分庫,將表分布在不同的庫中(庫可能分布在不同的機器上)。在這種場景下,事務的提交會變得相對復雜,因為多個節點(庫)的存在,可能存在部分節點提交失敗的情況,即事務的ACID特性需要在各個不同的數據庫實例中保證。比如更新db1庫的A表時,必須同步更新db2庫的B表,兩個更新形成一個事務,要么都成功,要么都失敗。
那么我們如何利用mysql實現分布式數據庫的事務呢?
Mysql 為我們提供了分布式事務解決方案(https://dev.mysql.com/doc/refman/5.7/en/xa.html 這是mysql5.7的文檔)
這里先聲明兩個概念:
- 資源管理器(resource manager):用來管理系統資源,是通向事務資源的途徑。數據庫就是一種資源管理器。資源管理還應該具有管理事務提交或回滾的能力。
-
事務管理器(transaction manager):事務管理器是分布式事務的核心管理者。事務管理器與每個資源管理器(resource
manager)進行通信,協調并完成事務的處理。事務的各個分支由唯一命名進行標識。
mysql在執行分布式事務(外部XA)的時候,mysql服務器相當于xa事務資源管理器,與mysql鏈接的客戶端相當于事務管理器。
分布式事務原理:分段式提交
分布式事務通常采用2PC協議,全稱Two Phase Commitment Protocol。該協議主要為了解決在分布式數據庫場景下,所有節點間數據一致性的問題。分布式事務通過2PC協議將提交分成兩個階段:
- prepare;
- commit/rollback
階段一為準備(prepare)階段。即所有的參與者準備執行事務并鎖住需要的資源。參與者ready時,向transaction manager報告已準備就緒。
階段二為提交階段(commit)。當transaction manager確認所有參與者都ready后,向所有參與者發送commit命令。
如下圖所示:
[圖片上傳失敗...(image-374a7-1585407318371)]
事務協調者transaction manager
因為XA 事務是基于兩階段提交協議的,所以需要有一個事務協調者(transaction manager)來保證所有的事務參與者都完成了準備工作(第一階段)。如果事務協調者(transaction manager)收到所有參與者都準備好的消息,就會通知所有的事務都可以提交了(第二階段)。MySQL 在這個XA事務中扮演的是參與者的角色,而不是事務協調者(transaction manager)。
Mysql的XA事務分為外部XA和內部XA
- 外部XA用于跨多MySQL實例的分布式事務,需要應用層作為協調者,通俗的說就是比如我們在PHP中寫代碼,那么PHP書寫的邏輯就是協調者。應用層負責決定提交還是回滾,崩潰時的懸掛事務。MySQL數據庫外部XA可以用在分布式數據庫代理層,實現對MySQL數據庫的分布式事務支持,例如開源的代理工具:網易的DDB,淘寶的TDDL等等。
- 內部XA事務用于同一實例下跨多引擎事務,由Binlog作為協調者,比如在一個存儲引擎提交時,需要將提交信息寫入二進制日志,這就是一個分布式內部XA事務,只不過二進制日志的參與者是MySQL本身。Binlog作為內部XA的協調者,在binlog中出現的內部xid,在crash recover時,由binlog負責提交。(這是因為,binlog不進行prepare,只進行commit,因此在binlog中出現的內部xid,一定能夠保證其在底層各存儲引擎中已經完成prepare)。
MySQL XA事務基本語法
XA {START|BEGIN} xid [JOIN|RESUME] 啟動xid事務 (xid 必須是一個唯一值; 不支持[JOIN|RESUME]子句)
XA END xid [SUSPEND [FOR MIGRATE]] 結束xid事務 ( 不支持[SUSPEND [FOR MIGRATE]] 子句)
XA PREPARE xid 準備、預提交xid事務
XA COMMIT xid [ONE PHASE] 提交xid事務
XA ROLLBACK xid 回滾xid事務
XA RECOVER 查看處于PREPARE 階段的所有事務
PHP調用MYSQL XA事務示例
1、首先要確保mysql開啟XA事務支持
<pre class="prettyprint">SHOW VARIABLES LIKE '%xa%'</pre>
如果innodb_support_xa的值是ON就說明mysql已經開啟對XA事務的支持了。
如果不是就執行:
<pre class="prettyprint">SET innodb_support_xa = ON</pre>
開啟
2、代碼如下:
<pre class="prettyprint"><!--?PHP $dbtest1 = new mysqli("172.20.101.17","public","public","dbtest1")or die("dbtest1 連接失敗"); $dbtest2 = new mysqli("172.20.101.18","public","public","dbtest2")or die("dbtest2 連接失敗"); //為XA事務指定一個id,xid 必須是一個唯一值。 $xid = uniqid(""); //兩個庫指定同一個事務id,表明這兩個庫的操作處于同一事務中 $dbtest1->query("XA START '$xid'");//準備事務1 $dbtest2->query("XA START '$xid'");//準備事務2 try { //$dbtest1 $return = $dbtest1->query("UPDATE member SET name='唐大麥' WHERE id=1") ; if($return == false) {
throw new Exception("庫dbtest1@172.20.101.17執行update member操作失敗!");
} //$dbtest2 $return = $dbtest2->query("UPDATE memberpoints SET point=point+10 WHERE memberid=1") ; if($return == false) {
throw new Exception("庫dbtest1@172.20.101.18執行update memberpoints操作失敗!");
} //階段1:$dbtest1提交準備就緒 $dbtest1->query("XA END '$xid'"); $dbtest1->query("XA PREPARE '$xid'"); //階段1:$dbtest2提交準備就緒 $dbtest2->query("XA END '$xid'"); $dbtest2->query("XA PREPARE '$xid'"); //階段2:提交兩個庫 $dbtest1->query("XA COMMIT '$xid'"); $dbtest2->query("XA COMMIT '$xid'");
}
catch (Exception $e) { //階段2:回滾 $dbtest1->query("XA ROLLBACK '$xid'"); $dbtest2->query("XA ROLLBACK '$xid'");
die($e->getMessage());
} $dbtest1->close(); $dbtest2->close(); ?></pre>
XA的性能問題
XA的性能很低。一個數據庫的事務和多個數據庫間的XA事務性能對比可發現,性能差10倍左右。因此要盡量避免XA事務,例如可以將數據寫入本地,用高性能的消息系統分發數據。或使用數據庫復制等技術。只有在這些都無法實現,且性能不是瓶頸時才應該使用XA。