這一篇內容還是避免不了俗套,主要的範圍無非是XA、2PC、3PC、TCC,再最後到Seata。
但是,我認為這東西,只是適用於面試和理論的瞭解,你真要說這些方案實際生產中有人用嗎?
有,但是會實現的更簡單,不會套用理論來實現,大廠有大廠的解決方案,中小公司用框架或者壓根就不存在分散式事務的問題。
那,為什麼還要寫這個?
為了你面試八股文啊,小可愛。
事務
要說分散式事務,首先還是從事務的基本特徵說起。
A原子性:在事務的執行過程中,要麼全部執行成功,要麼都不成功。
C一致性:事務在執行前後,不能破壞資料的完整性。一致性更多的說的是通過AID來達到目的,資料應該符合預先的定義和約束,由應用層面來保證,還有的說法是C是強行為了ACID湊出來的。
I隔離性:多個事務之間是互相隔離的,事務之間不能互相干擾,涉及到不同事務的隔離級別的問題。
D永續性:一旦事務提交,資料庫中資料的狀態就應該是永久性的。
XA
XA(eXtended Architecture)是指由X/Open 組織提出的分散式事務處理的規範,他是一個規範或者說是協議,定義了事務管理器TM(Transaction Manager),資源管理器RM(Resource Manager),和應用程式。
事務管理器TM就是事務的協調者,資源管理器RM可以認為就是一個資料庫。
2PC
XA定義了規範,那麼2PC和3PC就是他的具體實現方式。
2PC叫做二階段提交,分為投票階段和執行階段兩個階段。
投票階段
TM向所有的參與者傳送prepare請求,詢問是否可以執行事務,等待各個參與者的響應。
這個階段可以認為只是執行了事務的SQL語句,但是還沒有提交。
如果都執行成功了就返回YES,否則返回NO。
執行階段
執行階段就是真正的事務提交的階段,但是要考慮到失敗的情況。
如果所有的參與者都返回YES,那麼就執行傳送commit命令,參與者收到之後執行提交事務。
反之,只要有任意一個參與者返回的是NO的話,就傳送rollback命令,然後執行回滾的操作。
2PC的缺陷
-
同步阻塞,可以看到,在執行事務的過程當中,所有資料庫的資源都被鎖定,如果這時候有其他人來訪問這些資源,將會被阻塞,這是一個很大的效能問題。
-
TM單點問題,只要一個TM,一旦TM當機,那麼整個流程無法繼續完成。
-
資料不一致,如果在執行階段,參與者腦裂或者其他故障導致沒有收到commit請求,部分提交事務,部分未提交,那麼資料不一致的問題就產生了。
3PC
既然2PC有這麼多問題,所以就衍生出了3PC的概念,也叫做三階段提交,他把整個流程分成了CanCommit、PreCommit、DoCommit三個步驟,相比2PC,增加的就是CanCommit階段。
CanCommit
這個階段就是先詢問資料庫是否執行事務,傳送一個canCommit的請求去詢問,如果可以的話就返回YES,反之返回NO。
PreCommit
這個階段就等同於2PC的投票階段了,傳送preCommit命令,然後去執行SQL事務,成功就返回YES,反之返回NO。
但是,這個地方的區別在於參與者有了超時機制,如果參與者超時未收到doCommit命令的話,將會預設去提交事務。
DoCommit
DoCommit階段對應到2PC的執行階段,如果上一個階段都是收到YES的話,那麼就傳送doCommit命令去提交事務,反之則會傳送abort命令去中斷事務的執行。
相比2PC的改進
對於2PC的同步阻塞的問題,我們可以看到因為3PC加入了參與者的超時機制,所以原來2PC的如果某個參與者故障導致的同步阻塞的問題時間縮短了,這是一個優化,但是並沒有完全避免。
第二個單點故障的問題,同樣因為超時機制的引入,一定程度上也算是優化了。
但是資料不一致的問題,這個始終沒有得到解決。
舉個例子:
在PreCommit階段,某個參與者發生腦裂,無法收到TM的請求,這時候其他參與者執行abort事務回滾,而腦裂的參與者超時之後繼續提交事務,還是有可能發生資料不一致的問題。
那麼,為什麼要加入DoCommit這個階段呢?就是為了引入超時機制,事先我們先確認資料庫是否都可以執行事務,如果都OK,那麼才會進入後面的步驟,所以既然都可以執行,那麼超時之後說明發生了問題,就自動提交事務。
TCC
TCC的模式叫做Try、Confirm、Cancel,實際上也就是2PC的一個變種而已。
實現這個模式,一個事務的介面需要拆分成3個,也就是Try預佔、Confirm確認提交、最後Cancel回滾。
對於TCC來說,實際生產我基本上就沒看見過有人用,考慮到原因,首先是程式設計師的本身素質參差不齊,多個團隊協作你很難去約束別人按照你的規則來實現,另外一點就是太過於複雜。
如果說有簡單的應用的話,庫存的應用或許可以算做是一個。
一般庫存的操作,很多實現方案裡面都會會在下單的時候先預佔庫存,下單成功之後再實際去扣減庫存,最終如果發生了異常再回退。
凍結、預佔庫存就是2PC的準備階段,真正下單成功去扣減庫存就是2PC的提交階段,回滾就是某個發生異常的回滾操作,只不過在應用層面來實現了2PC的機制而已。
SAGA
Saga源於1987 年普林斯頓大學的 Hecto 和 Kenneth 發表的如何處理 long lived transaction(長活事務)論文。
主要思想就是將長事務拆分成多個本地短事務。
如果全部執行成功,就正常完成了,反之,則會按照相反的順序依次呼叫補償。
SAGA模式有兩種恢復策略:
-
向前恢復,這個模式偏向於一定要成功的場景,失敗則會進行重試
-
向後恢復,也就是發生異常的子事務依次回滾補償
由於這個模式在國內基本沒看見有誰用的,不在贅述。
訊息佇列
基於訊息佇列來實現最終一致性的方案,這個相比前面的我個人認為還稍微靠譜一點,那些都是理論啊,正常生產的實現很少看見應用。
基於訊息佇列的可能真正在應用的還稍微多一點。
一般來說有兩種方式,基於本地訊息表和依賴MQ本身的事務訊息。
本地訊息表的這個方案其實更復雜,實際上我也沒看到過真正誰來用。這裡我以RocketMQ的事務訊息來舉例,這個方式相比本地訊息表則更完全依賴MQ本身的特性做了解耦,釋放了業務開發的複雜工作量。
-
業務發起方,呼叫遠端介面,向MQ傳送一條半事務訊息,MQ收到訊息之後會返回給生產者一個ACK
-
生產者收到ACK之後,去執行事務,但是事務還沒有提交。
-
生產者會根據事務的執行結果來決定傳送commit提交或者rollback回滾到MQ
-
這一點是發生異常的情況,比如生產者當機或者其他異常導致MQ長時間沒有收到commit或者rollback的訊息,這時候MQ會發起狀態回查。
-
MQ如果收到的是commit的話就會去投遞訊息,消費者正常消費訊息即可。如果是rollback的話,則會在設定的固定時間期限內去刪除訊息。
這個方案基於MQ來保證訊息事務的最終一致性,還算是一個比較合理的解決方案,只要保證MQ的可靠性就可以正常實施應用,業務消費方根據本身的訊息重試達到最終一致性。
框架
以上說的都是理論和自己實現的方式,那麼分散式事務就沒有框架來解決我們的問題嗎?
有,其實還不少,但是沒有能扛旗者出現,要說有,阿里的開源框架Seata還有阿里雲的GTS。
GTS(Global Transaction Service 全域性事務服務)是阿里雲的中介軟體產品,只要你用阿里雲,付錢就可以用GTS。
Seata(Simple Extensible Autonomous Transaction Architecture)則是開源的分散式事務框架,提供了對TCC、XA、Saga以及AT模式的支援。
那麼,GTS和Seata有什麼關係呢?
實際上最開始的時候他們都是基於阿里內部的TXC(Taobao Transaction Constructor)分散式中介軟體產品,然後TXC經過改造上了阿里雲就叫做GTS。
之後阿里的中介軟體團隊基於TXC和GTS做出了開源的Seata,其中AT(Automatic Transaction)模式就是GTS原創的方案。
至於現在的版本,可以大致認為他們就是一樣的就行了,到2020年,GTS已經全面相容了Seata的 GA 版本。
整個GTS或者Seata包含以下幾個核心元件:
-
Transaction Coordinator(TC):事務協調器,維護全域性事務的執行狀態,負責協調並驅動全域性事務的提交或回滾。
-
Transaction Manager(TM):控制全域性事務的邊界,負責開啟一個全域性事務,並最終發起全域性提交或全域性回滾的決議。
-
Resource Manager(RM):控制分支事務,負責分支註冊、狀態彙報,並接收事務協調器的指令,驅動分支(本地)事務的提交和回滾。
無論對於TCC還是原創的AT模式的支援,整個分散式事務的原理其實相對來說還是比較容易理解。
-
事務開啟時,TM向TC註冊全域性事務,並且獲得全域性事務XID
-
這時候多個微服務的介面發生呼叫,XID就會傳播到各個微服務中,每個微服務執行事務也會向TC註冊分支事務。
-
之後TM就可以管理針對每個XID的事務全域性提交和回滾,RM完成分支的提交或者回滾。
AT模式
原創的AT模式相比起TCC的方案來說,無需自己實現多個介面,通過代理資料來源的形式生成更新前後的UNDO_LOG,依靠UNDO_LOG來實現回滾的操作。
執行的流程如下:
-
TM向TC註冊全域性事務,獲得XID
-
RM則會去代理JDBC資料來源,生成映象的SQL,形成UNDO_LOG,然後向TC註冊分支事務,把資料更新和UNDO_LOG在本地事務中一起提交
-
TC如果收到commit請求,則會非同步去刪除對應分支的UNDO_LOG,如果是rollback,就去查詢對應分支的UNDO_LOG,通過UNDO_LOG來執行回滾
TCC模式
相比AT模式代理JDBC資料來源生成UNDO_LOG來生成逆向SQL回滾的方式,TCC就更簡單一點了。
-
TM向TC註冊全域性事務,獲得XID
-
RM向TC註冊分支事務,然後執行Try方法,同時上報Try方法執行情況
-
然後如果收到TC的commit請求就執行Confirm方法,收到rollback則執行Cancel
XA模式
-
TM向TC註冊全域性事務,獲得XID
-
RM向TC註冊分支事務,XA Start,執行SQL,XA END,XA Prepare,然後上報分支執行情況
-
然後如果收到TC的commit請求就執行Confirm方法,收到rollback則執行Cancel
SAGA模式
-
TM向TC註冊全域性事務,獲得XID
-
RM向TC註冊分支事務,然後執行業務方法,並且上報分支執行情況
-
RM收到分支回滾,執行對應的業務回滾方法
總結
這裡從事務的ACID開始,向大家先說了XA是分散式事務處理的規範,之後談到2PC和3PC,2PC有同步阻塞、單點故障和資料不一致的問題,3PC在一定程度上解決了同步阻塞和單點故障的問題,但是還是沒有完全解決資料不一致的問題。
之後說到TCC、SAGA、訊息佇列的最終一致性的方案,TCC由於實現過於麻煩和複雜,業務很少應用,SAGA瞭解即可,國內也很少有應用到的,訊息佇列提供瞭解耦的實現方式,對於中小公司來說可能是較為低成本的實現方式。