前言
工欲善其事必先利其器,既然我們決定要做一個分布式事務(wù)框架,那首先需要了解一下,分布式事務(wù)是怎么回事,它跟傳統(tǒng)的本地事務(wù)有什么區(qū)別,解決方案有哪些,每種解決方案的對比等等。
本地事務(wù)
在了解分布式事務(wù)之前,先回顧一下本地事務(wù),顧名思義,本地事務(wù)就是在同一個JVM中,一個開啟了事務(wù)的業(yè)務(wù)方法就是本地事務(wù)。而這一個開啟了事務(wù)的業(yè)務(wù)方法里面的操作要么全部執(zhí)行成功,要么全部執(zhí)行失敗,不允許只成功一半另外一半執(zhí)行失敗的事情發(fā)生。例如該業(yè)務(wù)方法中,有兩次數(shù)據(jù)庫更新操作,那么這兩次數(shù)據(jù)庫操作要么全部執(zhí)行成功,要么全部回滾。使用專業(yè)術(shù)語來講的話,就是事務(wù)的4個基本特性:Atomicity(原子性)、Consistency(一致性)、Isolation(隔離性)、Durablity(持久性),統(tǒng)稱ACID,這里簡單的對ACID做一個概念的說明,當(dāng)作是做個筆記:
· Atomicity(原子性)
是指事務(wù)的操作如果成功就必須要完全應(yīng)用到數(shù)據(jù)庫,如果操作失敗則不能對數(shù)據(jù)庫有任何影響。通俗的說,就是所有操作要么全部成功,要么全部失敗回滾。
· Consistency(一致性)
是指事務(wù)執(zhí)行前后,數(shù)據(jù)從一個狀態(tài)到另一個狀態(tài)必須是一致的,比如A向B轉(zhuǎn)賬(A、B的總金額就是一個一致性狀態(tài)),不可能出現(xiàn)A扣了錢,B卻沒收到的情況發(fā)生。
· Isolation(隔離性)
是指當(dāng)多個用戶并發(fā)訪問數(shù)據(jù)庫時,比如操作同一張表時,數(shù)據(jù)庫為每一個用戶開啟的事務(wù),不能被其他事務(wù)的操作所干擾,多個并發(fā)事務(wù)之間要相互隔離。這里涉及到數(shù)據(jù)庫的隔離級別的概念,不是我們討論的主題,不詳細(xì)展開,大家可以自行查閱相關(guān)資料。
· Durablity(持久性)
是指一個事務(wù)一旦被提交了,那么對數(shù)據(jù)庫中的數(shù)據(jù)的改變就是永久性的,即便是在數(shù)據(jù)庫系統(tǒng)遇到故障的情況下也不會丟失提交事務(wù)的操作。
分布式事務(wù)
通過以上的回顧我們知道,本地事務(wù)對于我們來說不是什么問題,因為我們可以直接使用數(shù)據(jù)庫的事務(wù)支持,比如mysql、oracle這些數(shù)據(jù)庫對事務(wù)都有很好的支持。但是,對于分布式應(yīng)用來說的話,事務(wù)就沒有那么簡單了,因為需要開啟事務(wù)的業(yè)務(wù)方法,很可能是分布在不同應(yīng)用程序中,這就說明,大家不在同一個JVM中,事務(wù)空間都不一樣了,那就沒辦法做到要么全部執(zhí)行成功,要么全部執(zhí)行失敗了,我們可以看以下分析圖:

如上圖所示:這是一個分布式應(yīng)用,完成一個付款業(yè)務(wù)的操作需要有4個微服務(wù)參與,大家都獨立運行在自己的JVM中,其中,訂單系統(tǒng)、商品系統(tǒng)和會員系統(tǒng)是服務(wù)提供者,有自己對應(yīng)的數(shù)據(jù)庫。
??客戶端應(yīng)用在發(fā)起了付款請求時,調(diào)用了訂單系統(tǒng)的支付業(yè)務(wù)makePayment,而makePayment方法中,又調(diào)用了遠(yuǎn)程商品系統(tǒng)的decrease方法和遠(yuǎn)程會員系統(tǒng)的payment方法,分別做減庫存和扣余額的操作,那現(xiàn)在就有兩種情況了:
??第一種情況是流程正常執(zhí)行,各個業(yè)務(wù)參與者都沒有異常,萬事大吉。
??第二種情況是其中有一個業(yè)務(wù)參與者出現(xiàn)異常了,按照我們上面對本地事務(wù)的理解,它們應(yīng)該要做到要么全部執(zhí)行成功,要么全部執(zhí)行失敗了,這樣才能確保數(shù)據(jù)一致性。但現(xiàn)在這個不是單體應(yīng)用,而是分布式應(yīng)用,原本一個本地邏輯執(zhí)行單元被拆分到了多個獨立的微服務(wù)中,這些微服務(wù)又分別操作不同的數(shù)據(jù)庫和表,服務(wù)之間通過網(wǎng)絡(luò)調(diào)用。所以這就沒辦法確保要么全部執(zhí)行成功,要么全部執(zhí)行失敗了,因為我怎么知道遠(yuǎn)程的服務(wù)是否執(zhí)行成功呢。
??所以,現(xiàn)在的問題就出現(xiàn)了,我們不能再用單體應(yīng)用的那種事務(wù)方式,套用在分布式應(yīng)用中了,必須要思考用什么樣的解決方案來控制分布式應(yīng)用的事務(wù)問題。這就是“分布式事務(wù)”。
分布式事務(wù)的解決方案
分布式事務(wù)的解決方案有很多,但可以具體歸納為兩種:
● 強一致性事務(wù)
強一致性事務(wù)的代表就是XA事務(wù)協(xié)議了,它由Oracle Tuxedo提出,把分散到各個JVM中的事務(wù)資源,又整合為全局事務(wù)統(tǒng)一管理了。
● 柔性事務(wù)
而柔性事務(wù)并沒有像強一致性事務(wù)那樣整合為全局事務(wù),但其目的 都是確保分布式事務(wù)最終一致性的。柔性事務(wù)又可以細(xì)分為TCC事務(wù)和可靠消息事務(wù),這兩種分布式事務(wù)處理方式不一樣,接下來我們具體分析一下這幾種分布式事務(wù)解決方案的實現(xiàn)原理。
XA事務(wù)協(xié)議
XA是一個分布式事務(wù)協(xié)議,XA中大致分為兩部分:事務(wù)管理器和本地資源管理器。其中本地資源管理器往往由數(shù)據(jù)庫實現(xiàn),比如Oracle、DB2這些商業(yè)數(shù)據(jù)庫都實現(xiàn)了XA接口,MySQL5.7之后也支持XA分布式事務(wù)。而事務(wù)管理器作為全局的調(diào)度者,負(fù)責(zé)各個本地資源的提交和回滾。
??XA分布式事務(wù)協(xié)議的工作原理是:把各個微服務(wù)中的本地資源交給一個統(tǒng)一的事務(wù)管理器管理,事務(wù)管理器可以看做是事務(wù)協(xié)調(diào)者的角色(coordinator),各個本地資源管理器可以看做是事務(wù)參與者的角色(partcipant)。各個事務(wù)參與者之間不能直接通訊,而是通過事務(wù)協(xié)調(diào)者間接通訊,通俗來說,服務(wù)A怎么知道服務(wù)B是否執(zhí)行成功?就是由事務(wù)協(xié)調(diào)者轉(zhuǎn)告各個事務(wù)參與者了。通過以下分析圖,看看XA實現(xiàn)分布式事務(wù)的流程:


以上就是XA分布式事務(wù)執(zhí)行流程,加入了全局的事務(wù)管理器作為協(xié)調(diào)者,在接收到發(fā)起帶事務(wù)的業(yè)務(wù)方法后,發(fā)送prepare到各個事務(wù)參與者,各個事務(wù)參與者接收到prepare后,開啟本地事務(wù)being,并執(zhí)行本地業(yè)務(wù)流程,如果流程正常運行,則返回ready結(jié)果給事務(wù)協(xié)調(diào)者,告知準(zhǔn)備就緒了,這時,如果各個事務(wù)參與者返回的結(jié)果都是ready,那么事務(wù)協(xié)調(diào)者就會再次發(fā)送一個全局事務(wù)提交global_commit的消息到各個事務(wù)參與者,最后,各個事務(wù)參與者受到global_commit后,提交本地事務(wù)commit。
??這是業(yè)務(wù)流程正常執(zhí)行的情況,那如果因為流程有異常就走如下流程:事務(wù)協(xié)調(diào)者還是發(fā)送prepare到各個事務(wù)參與者,事務(wù)參與者接收到prepare后,開啟本地事務(wù)begin,接著執(zhí)行業(yè)務(wù)流程,此時,如果有某個事務(wù)參與者執(zhí)行業(yè)務(wù)報錯,返回異常abort給事務(wù)協(xié)調(diào)者,事務(wù)協(xié)調(diào)者會再次發(fā)送全局回滾global_rollback給各個事務(wù)參與者,事務(wù)參與者接受到global_rollback后,開始回滾本地事務(wù)rollback,即便是流程正常執(zhí)行的也要回滾掉,這樣就能確保要么一起成功,要么一起失敗。
??總的來說,XA協(xié)議比較簡單,而且一旦商業(yè)數(shù)據(jù)庫實現(xiàn)了XA協(xié)議,使用分布式事務(wù)的成本也比較低。但是,XA也有致命的缺點,那就是性能不理想,特別是在并發(fā)量很高的情況下,會帶來性能瓶頸,因為根據(jù)以上執(zhí)行流程圖的分析可知,在全局事務(wù)管理器向各個事務(wù)參與者發(fā)送prepare時,是需要鎖住資源的,也就是此時,所有相關(guān)連的微服務(wù)都處于阻塞狀態(tài),需要等到所有事務(wù)參與者返回最終處理結(jié)構(gòu),才能釋放鎖,所以XA無法滿足高并發(fā)場景。其次,XA目前在數(shù)據(jù)庫的支持上不太理想,mysql5.7之前是不支持的,并且還有許多nosql也沒有支持XA,而大多數(shù)新型的互聯(lián)網(wǎng)微服務(wù)應(yīng)用都會使用各種nosql數(shù)據(jù)庫,所以這就導(dǎo)致了XA的應(yīng)用場景變得非常狹隘。
TCC事務(wù)
TCC是Try-Confirm-Cancel的簡稱。其核心思想是:每個需要開啟分布式事務(wù)的業(yè)務(wù)方法,都要注冊一個與其對應(yīng)的檢測、確認(rèn)和撤銷的操作,如下:
● Try階段:主要是對業(yè)務(wù)系統(tǒng)做檢測的操作,沒有問題就調(diào)用確認(rèn)操作,有問題則調(diào)用取消操作。
● Confirm階段:確認(rèn)執(zhí)行業(yè)務(wù)操作。
● Cancel階段:取消執(zhí)行業(yè)務(wù)操作。
具體執(zhí)行流程如下:


通過以上的流程,我們可以發(fā)現(xiàn),TCC的分布式事務(wù)處理與XA的分布式事務(wù)處理流程是非常相似的, 調(diào)用try接口檢查業(yè)務(wù)是否有異常的操作,類似于XA的prepare預(yù)提交,如果接口返回正常,則調(diào)用confirm確認(rèn)執(zhí)行業(yè)務(wù),操作數(shù)據(jù)。
??那如果其中有一個事務(wù)參與者在調(diào)用了try接口檢測后,返回了異常給事務(wù)協(xié)調(diào)者,但是之前很可能已經(jīng)有其他事務(wù)參與者調(diào)用了confirm接口,執(zhí)行業(yè)務(wù)流程操作了數(shù)據(jù),那這時,事務(wù)協(xié)調(diào)者就需要調(diào)用事務(wù)參與者的cancel接口,撤銷之前修改的數(shù)據(jù),達到類似回滾的效果。
??不過XA是在跨庫的DB層面,而TCC是應(yīng)用層面,需要通過業(yè)務(wù)邏輯來實現(xiàn)分布式事務(wù)。TCC的實現(xiàn)方式優(yōu)勢在于:沒有項目XA協(xié)議那樣,把分布的資源統(tǒng)一管理,這就使得分布的資源不會被加鎖,從而提高整體的吞吐量,所以這種分布式事務(wù)的解決方案,在性能和吞吐量要求高的應(yīng)用使用的還是比較多的。而不足之處則在于對應(yīng)用的侵入性非常強,業(yè)務(wù)邏輯的每個分支都需要實現(xiàn)try、confirm、cancel三個操作。此外,其實現(xiàn)難度也比較大,需要按照網(wǎng)絡(luò)狀態(tài)、系統(tǒng)故障等不同的失敗原因?qū)崿F(xiàn)不同的回滾策略。同時,confirm和cancel接口還要考慮冪等性的問題,因為confirm和cancel有可能會被多次調(diào)用。
可靠消息事務(wù)
可靠消息事務(wù)全稱叫做可靠消息事務(wù)最終一致性,它通常沒有像前面兩種分布式事務(wù)解決方案那樣有回滾或撤銷數(shù)據(jù)的操作,而更多的是強調(diào)事務(wù)的補償和重試。這里的可靠消息指的是消息隊列中間件,消息隊列其中一個特點就是消息可靠性,而該解決方案最終能達到事務(wù)一致,依靠的核心就是消息隊列。先粗略的看看它執(zhí)行流程:

從以上的執(zhí)行流程可以發(fā)現(xiàn),各個事務(wù)參與者都是相對獨立的,不管在執(zhí)行業(yè)務(wù)方法的過程中是否有異常,整體的業(yè)務(wù)流程都要先跑完,這個是該解決方案的前提。然后在調(diào)用事務(wù)參與者的業(yè)務(wù)方法的同時,往消息隊列發(fā)送事務(wù)相關(guān)的消息,這樣的話,出現(xiàn)異常的事務(wù)參與者再從消息隊列中獲取消息,重新執(zhí)行本地的業(yè)務(wù),達到補償和重試的效果,整個事務(wù)中,不管中間有哪些參與者出錯,但是最終還是事務(wù)一致的。
??這種解決方案是所有解決方案中最柔性的,并且靈活度非常高,可以根據(jù)自己具體的業(yè)務(wù)場景做改變,同時,對比TCC來說,性能和吞吐量更高,并且對應(yīng)用的侵入性更低。性能的提高體現(xiàn)在:沒有了業(yè)務(wù)檢測的環(huán)節(jié),跟原本一樣,該怎么調(diào)用遠(yuǎn)程方法就怎么調(diào)用,只是增加了一個往MQ發(fā)送消息的操作,但是該操作是異步的,而且MQ也是具備高吞吐量的特性。而侵入性更低體現(xiàn)在:MQ是中間件,只需要通過網(wǎng)絡(luò)來調(diào)用即可,我們的業(yè)務(wù)方法并不會由于分布式事務(wù)解決方案的加入而有太多的改造,加入的代碼更多是以外圍擴展,或者組件的方式加入。
??可靠消息事務(wù)最終一致性的解決方案優(yōu)點很明顯,但缺點也不是沒有的,首先該解決方案設(shè)計過于復(fù)雜,組件很多,需要考慮的情況繁雜,實現(xiàn)起來比較困難。其次,雖然它是最柔性,最靈活和性能最高的,但是事務(wù)的原子性和一致性是最弱的,因為這種解決方案是以大家都不出錯為前提的,如果其中有一個出錯,自己通過補償機制重新執(zhí)行本地事務(wù),但重試的過程本身就是不確定性的,比如說:A轉(zhuǎn)錢給B,A賬戶扣錢了,但是B賬戶加錢時出錯,在該機制下,A賬戶不會回滾,而是讓B賬戶重新嘗試加錢,那這就產(chǎn)生時間上的延遲了,很可能等了很久,都沒見B賬戶把錢加上去,或者不斷重試都是失敗的,最終導(dǎo)致整個事務(wù)不一致,需要人工處理。
??總的來說,可靠消息事務(wù)最終一致性由于它的事務(wù)原子性和一致性比較弱,所以決定了它在一些事務(wù)ACID要求非常強的應(yīng)用中是不能使用的,否則會造成很多安全性的問題,但是對于大多數(shù)互聯(lián)網(wǎng)應(yīng)用來說,都是一個不錯的解決方案。而具體使用哪一種,還是取決于項目的業(yè)務(wù)場景,然后做全面的對比、考量和取舍。