一文理解分布式事務(wù)的解決方案
單體數(shù)據(jù)庫不涉及網(wǎng)絡(luò)交互,所以在多表之間實(shí)現(xiàn)事務(wù)是比較簡單的,這種事務(wù)稱之為本地事務(wù)。
但是單體數(shù)據(jù)庫的性能達(dá)到瓶頸的時(shí)候,就需要分庫,就會出現(xiàn)跨庫(數(shù)據(jù)庫實(shí)例)的事務(wù)需求;隨著企業(yè)應(yīng)用的規(guī)模越來越大,企業(yè)會進(jìn)一步進(jìn)行服務(wù)化改造,以滿足業(yè)務(wù)增長的需求;當(dāng)前微服務(wù)架構(gòu)越來越流行,跨服務(wù)的事務(wù)場景也會越來越多。
這些都屬于分布式事務(wù)。分布式事務(wù)是指是指事務(wù)的發(fā)起者、參與者、數(shù)據(jù)資源服務(wù)器以及事務(wù)管理器分別位于分布式系統(tǒng)的不同節(jié)點(diǎn)之上。
概括起來,分布式事務(wù)有三種場景:
-
跨數(shù)據(jù)庫分布式事務(wù)
-
跨服務(wù)分布式事務(wù)
-
混合式分布式事務(wù)
本文將介紹分布式事務(wù)常見的解決方案:
-
2PC
-
3PC
-
TCC
-
Saga事務(wù)
-
基于本地消息表機(jī)制
-
基于事務(wù)消息機(jī)制
-
最大努力通知機(jī)制
常見解決方案
分布式事務(wù)是由多個(gè)本地事務(wù)組成的,分布式事務(wù)跨越了多設(shè)備,之間又經(jīng)歷的復(fù)雜的網(wǎng)絡(luò),可想而知想要實(shí)現(xiàn)嚴(yán)格的事務(wù)道路阻且長。
2PC
二階段提交(Two-phase Commit,簡稱2PC),是指為了使基于分布式系統(tǒng)架構(gòu)下的所有節(jié)點(diǎn)在進(jìn)行事務(wù)提交時(shí)保持一致性而設(shè)計(jì)的一種算法(Algorithm)。
整體分為兩個(gè)階段,如圖所示。
2PC的優(yōu)缺點(diǎn)
2PC的優(yōu)點(diǎn)是能利用參與者(RM)自身的功能進(jìn)行本地事務(wù)的提交和回滾,對業(yè)務(wù)邏輯零侵入(相對TCC解決方案)。
但2PC也存在三大缺點(diǎn):同步阻塞、單點(diǎn)故障和數(shù)據(jù)不一致問題。
-
同步阻塞
由于參與者(RM)在執(zhí)行操作時(shí)都是同步阻塞的,所以在2PC過程中其他節(jié)點(diǎn)訪問加鎖資源不得不處于阻塞狀態(tài)。
-
單點(diǎn)故障
協(xié)調(diào)者(TM)是單點(diǎn),一旦協(xié)調(diào)者發(fā)生故障,參與者會一直阻塞。如果是某個(gè)熱點(diǎn)資源阻塞,可能會導(dǎo)致整個(gè)系統(tǒng)的雪崩。
-
數(shù)據(jù)不一致問題
在第二階段中,因?yàn)榫W(wǎng)絡(luò)原因?qū)е虏糠謪⑴c者(RM)沒有接收到協(xié)調(diào)者(TM)的信息,或者部分參與者進(jìn)行提交/回滾操作時(shí),發(fā)生異常。這都會導(dǎo)致數(shù)據(jù)的不一致性問題。
2PC的優(yōu)化
Percolator是Google的上一代分布式事務(wù)解決方案,構(gòu)建在BigTable之上,在Google內(nèi)部用于網(wǎng)頁索引更新的業(yè)務(wù),原始的論文見于參考文檔4。
TiDB的事務(wù)模型沿用了Percolator的事務(wù)模型,之后講解分布式數(shù)據(jù)庫相關(guān)博客進(jìn)行講解。
3PC
3PC相對2PC增加了三階段模式以及超時(shí)機(jī)制。
超時(shí)機(jī)制:第三階段中,當(dāng)參與者長時(shí)間沒有得到協(xié)調(diào)者的響應(yīng),在默認(rèn)情況下,參與者會自動將超時(shí)的事務(wù)進(jìn)行提交(即使是協(xié)調(diào)者發(fā)送的可能是rollback命令,這里就造成了數(shù)據(jù)的不一致)。解決2PC同步阻塞的情況.
同時(shí)3PC增加的第一階段的詢問通知,降低2PC中的數(shù)據(jù)不一致問題的概率。
但2PC中的單點(diǎn)故障問題,3PC并沒有解決。
3PC的三個(gè)階段,如圖所示:
-
第一階段,CanCommit
-
第二階段,PreCommit
-
第三階段,DoCommit
總結(jié)
2PC還是3PC都是協(xié)議,是一種指導(dǎo)思想,與項(xiàng)目中真正的落地方案還是有差別的。
但2PC和3PC對于大型分布式系統(tǒng)很少會使用,因?yàn)樵谑聞?wù)處理過程中,協(xié)調(diào)者需要同時(shí)連接多個(gè)數(shù)據(jù)庫(RM)。
通常微服務(wù)都是連接各自領(lǐng)域的數(shù)據(jù)庫,微服務(wù)想要修改另一個(gè)領(lǐng)域的數(shù)據(jù),都是要通過RPC接口來實(shí)現(xiàn)的,并不會多數(shù)據(jù)源訪問。如果存在過多協(xié)調(diào)者進(jìn)行多數(shù)據(jù)源的鏈接,勢必會增加服務(wù)治理的難度并可能導(dǎo)致數(shù)據(jù)的錯(cuò)亂。
TCC
不管是2PC還是3PC都是依賴于數(shù)據(jù)庫的事務(wù)提交和回滾。
但有時(shí)一些業(yè)務(wù)不僅僅涉及到數(shù)據(jù)庫,例如發(fā)送一條短信、上傳一張圖片等業(yè)務(wù)層的邏輯。
所以事務(wù)的提交和回滾就得提升到業(yè)務(wù)層面而不是數(shù)據(jù)庫層面了,而TCC就是一種業(yè)務(wù)層面的兩階段提交。
TCC (Try、Commit、Cancel) 是一種補(bǔ)償型事務(wù)。該模型要求應(yīng)用的每個(gè)服務(wù)提供try、confirm、cancel三個(gè)接口,核心思想是首先對資源的進(jìn)行預(yù)留評估,如果事務(wù)可以提交,則完成對預(yù)留資源的確認(rèn);如果事務(wù)要回滾,則釋放預(yù)留的資源。
TCC其本質(zhì)是一個(gè)應(yīng)用層面上的2PC,同樣分為兩個(gè)階段,如下圖所示:
比如:一個(gè)扣款服務(wù)使用TCC的話,需要寫Try方法,用來扣款資金;還需要一個(gè)Confirm方法來執(zhí)行真正的扣款;最后還需要提供Cancel方法用于進(jìn)行扣款操作的回滾。
可以看到原本的一個(gè)方法,需要膨脹成三個(gè)方法,所以說TCC對業(yè)務(wù)有很大的侵入。
雖說對業(yè)務(wù)有侵入,但是TCC沒有資源的阻塞,每一個(gè)方法都是直接提交事務(wù)的,如果出錯(cuò)是通過業(yè)務(wù)層面的Cancel來進(jìn)行補(bǔ)償,所以TCC屬于補(bǔ)償型事務(wù)。
對于2PC中出現(xiàn)單點(diǎn)故障問題或超時(shí)問題,TCC的解決方案是不停重試:不停地重試沒有收到響應(yīng)的Confirm/Cancel接口直到成功為止,如果重試策略失敗就通過記錄和報(bào)警進(jìn)行人工介入。
但這種重試機(jī)制,造成了TCC的冪等問題與空回滾問題。
TCC需要注意的問題
-
冪等問題
由于有重調(diào)機(jī)制,因此對于Try、Confirm、Cancel三個(gè)方法都需要冪等實(shí)現(xiàn),避免重復(fù)執(zhí)行產(chǎn)生錯(cuò)誤。
-
空回滾問題
參與者(RM)的Try接口響應(yīng)由于網(wǎng)絡(luò)問題沒有讓協(xié)調(diào)者(TM)成功接收到,此時(shí)協(xié)調(diào)者(TM)就會發(fā)出Cancel命令。那么Cancel接口就需要在未執(zhí)行Try的情況下能正常的Cancel。
-
懸掛問題
事務(wù)協(xié)調(diào)器在調(diào)用TCC服務(wù)的一階段Try操作時(shí),可能會出現(xiàn)因網(wǎng)絡(luò)擁堵而導(dǎo)致的超時(shí),此時(shí)事務(wù)協(xié)調(diào)器會觸發(fā)二階段回滾,調(diào)用TCC服務(wù)的Cancel操作,Cancel調(diào)用未超時(shí);在此之后,擁堵在網(wǎng)絡(luò)上的一階段Try數(shù)據(jù)包被TCC服務(wù)收到,出現(xiàn)了二階段Cancel請求比一階段Try請求先執(zhí)行的情況,此TCC服務(wù)在執(zhí)行晚到的Try之后,將永遠(yuǎn)不會再收到二階段的Confirm或者Cancel,造成TCC服務(wù)懸掛。
所以在實(shí)現(xiàn)TCC服務(wù)時(shí),要允許空回滾,但也要拒絕執(zhí)行空回滾之后Try請求,要避免出現(xiàn)懸掛。
補(bǔ)償型TCC
上面講解的是通用型TCC,它需要對分布式事務(wù)相關(guān)的所有業(yè)務(wù)有掌控權(quán)。但有時(shí)候(例如調(diào)用的是別的公司的接口),通用型TCC行不通。
因此存在補(bǔ)償型TCC,可以理解為沒有Try的TCC形式。由于未提供Try接口,可以認(rèn)為是Saga機(jī)制的另一種形式。
比如坐飛機(jī)需要換乘,換乘的飛機(jī)又是不同的航空公司,比如從A飛到B,再從B飛到C,只有A-B和B-C都買到票了才有意義。
這時(shí)候就直接接調(diào)用航空公司的買票操作,當(dāng)兩個(gè)航空公司都買成功了那就直接成功了,如果某個(gè)公司買失敗了,那就需要調(diào)用取消訂票接口。
相當(dāng)于直接執(zhí)行TCC的第二階段,需要重點(diǎn)關(guān)注回滾操作。如果回滾失敗得有記錄報(bào)警和人工介入等。
總結(jié)
TCC事務(wù)將分布式事務(wù)從資源層提到業(yè)務(wù)層來實(shí)現(xiàn),可以讓業(yè)務(wù)靈活選擇資源的鎖定粒度,并且全局事務(wù)執(zhí)行過程中不會一直持有鎖,所以系統(tǒng)的吞吐量比2PC模式要高很多。
由于TCC事務(wù)的帶來的工程復(fù)雜度、網(wǎng)絡(luò)延遲和服務(wù)治理難度的提高,所以除非是與支付交易相關(guān)的核心業(yè)務(wù)場景(對一致性要求很高),其他業(yè)務(wù)場景不要使用TCC事務(wù)。
Saga事務(wù)
Saga事務(wù),也是一種補(bǔ)償事務(wù)。同補(bǔ)償型TCC一樣,沒有Try階段,而是把分布式事務(wù)看作一組本地事務(wù)構(gòu)成的事務(wù)鏈。
Saga事務(wù)基本協(xié)議如下:
-
每個(gè)Saga事務(wù)由一系列冪等的有序子事務(wù)(sub-transaction,擔(dān)任參與者的身份) Ti 組成。
-
每個(gè)Ti 都有對應(yīng)的冪等補(bǔ)償動作Ci,補(bǔ)償動作用于撤銷Ti造成的結(jié)果。
事務(wù)鏈中如果包含有業(yè)務(wù)順序的邏輯,一定要合理安排事務(wù)鏈的順序(以項(xiàng)目利益為優(yōu)先,如果出現(xiàn)紕漏可以人工補(bǔ)齊的原則,例如先扣款后發(fā)貨;先退貨再退款)
由于Saga模型中沒有Prepare階段,因此事務(wù)間不能保證隔離性,當(dāng)多個(gè)Saga事務(wù)操作同一資源時(shí),就會產(chǎn)生更新丟失、臟數(shù)據(jù)讀取等問題,這時(shí)需要在業(yè)務(wù)層控制并發(fā),例如:在應(yīng)用層面加鎖,或者應(yīng)用層面預(yù)先凍結(jié)資源。
下面以下單流程為例,整個(gè)操作包括:扣減庫存(庫存服務(wù))、創(chuàng)建訂單(訂單服務(wù))、支付(支付服務(wù))等等。
事務(wù)正常執(zhí)行完成T1, T2, T3, ...,Tn,例如:扣減庫存(T1),創(chuàng)建訂單(T2),支付(T3),依次有序進(jìn)行,但支付服務(wù)出現(xiàn)報(bào)錯(cuò),此時(shí)Saga有兩種策略可以使用。
Saga事務(wù)的恢復(fù)策略
Saga定義了兩種恢復(fù)策略。
-
向前恢復(fù)(forward recovery)
適用于必須要成功的場景,發(fā)生失敗進(jìn)行重試,執(zhí)行順序是類似于這樣的:T1, T2, ..., Tj(失敗), Tj(重試),..., Tn,其中j是發(fā)生錯(cuò)誤的子事務(wù)(sub-transaction)。
顯然,向前恢復(fù)沒有必要提供補(bǔ)償事務(wù),如果業(yè)務(wù)中子事務(wù)(最終)會成功,或補(bǔ)償事務(wù)難以定義或不可能,向前恢復(fù)更符合需求。
過度積極的重試策略(例如間隔太短或重試次數(shù)過多)會對下游服務(wù)造成不利影響,所以需要使用一個(gè)比較合理的重試機(jī)制。
-
向后恢復(fù)(backward recovery)
這種做法的效果是撤銷掉之前所有成功的子事務(wù),使得整個(gè)Saga的執(zhí)行結(jié)果撤銷。
下面講解的示例均為向后恢復(fù)策略。
Saga事務(wù)協(xié)調(diào)模式
Saga執(zhí)行事務(wù)的順序稱為Saga的協(xié)調(diào)邏輯。這種協(xié)調(diào)邏輯有兩種模式,協(xié)調(diào)(Orchestration)和事件編排(Event Choreography)分別如下:
-
協(xié)調(diào)(Orchestration):Saga提供一個(gè)控制類,方便子事務(wù)的協(xié)調(diào)工作。事務(wù)執(zhí)行的命令從控制類發(fā)起,按照邏輯順序請求Saga的子事務(wù),從子事務(wù)那里接受到反饋以后,控制類再發(fā)起向其他子事務(wù)的調(diào)用。所有Saga的子事務(wù)都圍繞這個(gè)控制類進(jìn)行溝通和協(xié)調(diào)工作。
以電商訂單的例子為例:
-
事務(wù)發(fā)起方的主業(yè)務(wù)邏輯請求控制類開啟訂單事務(wù)
-
控制類向庫存服務(wù)請求扣減庫存,庫存服務(wù)回復(fù)處理結(jié)果。
-
控制類向訂單服務(wù)請求創(chuàng)建訂單,訂單服務(wù)回復(fù)創(chuàng)建結(jié)果。
-
控制類向支付服務(wù)請求支付,支付服務(wù)回復(fù)處理結(jié)果。
-
主業(yè)務(wù)邏輯接收并處理事務(wù)處理結(jié)果回復(fù)。
控制類必須事先知道執(zhí)行整個(gè)訂單事務(wù)所需的流程。如果有任何失敗,它負(fù)責(zé)通過向每個(gè)子事務(wù)發(fā)送命令來撤銷之前的操作來協(xié)調(diào)分布式的回滾?;诳刂祁悈f(xié)調(diào)一切時(shí),回滾要容易得多,因?yàn)榭刂祁惸J(rèn)是執(zhí)行正向流程,回滾時(shí)只要執(zhí)行反向流程即可。
-
事件編排(Event Choreography):子事務(wù)之間的調(diào)用、分配、決策和排序,通過交換事件進(jìn)行進(jìn)行。是一種去中心化的模式,子事務(wù)之間通過消息機(jī)制進(jìn)行溝通,通過監(jiān)聽器的方式監(jiān)聽其他子事務(wù)發(fā)出的消息,從而執(zhí)行后續(xù)的邏輯處理。由于沒有中間協(xié)調(diào)點(diǎn),靠子事務(wù)進(jìn)行相互協(xié)調(diào)。
以電商訂單的例子為例:
-
事務(wù)發(fā)起方的主業(yè)務(wù)邏輯發(fā)布開始訂單事件
-
庫存服務(wù)監(jiān)聽開始訂單事件,扣減庫存,并發(fā)布庫存已扣減事件
-
訂單服務(wù)監(jiān)聽庫存已扣減事件,創(chuàng)建訂單,并發(fā)布訂單已創(chuàng)建事件
-
支付服務(wù)監(jiān)聽訂單已創(chuàng)建事件,進(jìn)行支付,并發(fā)布訂單已支付事件
-
主業(yè)務(wù)邏輯監(jiān)聽訂單已支付事件并處理。
實(shí)現(xiàn)方式對比
基于協(xié)調(diào)的Saga的優(yōu)點(diǎn)如下:
-
服務(wù)之間關(guān)系簡單,避免子事務(wù)之間的循環(huán)依賴關(guān)系,因?yàn)镾aga控制類會調(diào)用Saga子事務(wù),但子事務(wù)不會調(diào)用控制類。
-
程序開發(fā)簡單,子事務(wù)只需要完成自身的任務(wù),不用考慮處理消息的方式,降低子事務(wù)接入的復(fù)雜性。
-
易維護(hù)擴(kuò)展,如果事務(wù)需要添加新步驟,只需修改控制類,保持事務(wù)復(fù)雜性保持線性,回滾更容易管理。
-
容易測試,測試工作集中在集中在控制類上,其他服務(wù)單獨(dú)測試功能即可。
基于協(xié)調(diào)的Saga的缺點(diǎn)如下:
-
控制類集中太多邏輯的風(fēng)險(xiǎn),導(dǎo)致難以維護(hù)。
-
控制類存在單點(diǎn)故障風(fēng)險(xiǎn)。
基于事件編排的Saga的優(yōu)點(diǎn)如下:
-
避免控制類單點(diǎn)故障風(fēng)險(xiǎn)。
-
子事務(wù)之間通過訂閱時(shí)間溝通,組合會更靈活。
基于事件編排的Saga的缺點(diǎn)如下:
-
服務(wù)之間存在循環(huán)依賴的風(fēng)險(xiǎn)。
-
當(dāng)涉及的步驟較多,服務(wù)間關(guān)系混亂。如果沒有完善的文檔支撐,了解整個(gè)事務(wù)的執(zhí)行過程只能通過閱讀代碼完成。增加開發(fā)人員理解和維護(hù)代碼的難度。
基于本地消息表機(jī)制
本地消息表機(jī)制會在數(shù)據(jù)庫中存放一個(gè)本地事務(wù)消息表,在進(jìn)行本地事務(wù)操作的同時(shí)將操作狀態(tài)插入到本地事務(wù)消息表。消息插入成功后再調(diào)用其他服務(wù),如果調(diào)用成功就修改這條本地消息的狀態(tài);如果調(diào)用失敗則不停重試,下游接口需要保證冪等性。
本地消息表機(jī)制是一種最大努力通知思想。
這里以支付服務(wù)和會計(jì)服務(wù)為例展開介紹本地消息表方案。大概流程:用戶在支付服務(wù)完成了支付訂單支付成功后,此時(shí)會調(diào)用會計(jì)服務(wù)的接口生成一條原始的會計(jì)憑證到數(shù)據(jù)庫中。整體流程如圖:
完整流程:
-
在支付庫中加入一張消息表來記錄支付消息,即用戶支付成功后往這張消息表插入一條支付成功的消息,狀態(tài)為“發(fā)送中”。這里要保證了本地事務(wù)的強(qiáng)一致性(支付邏輯和插入消息表的消息組成了一個(gè)強(qiáng)一致性的事務(wù),要么同時(shí)成功,要么同時(shí)失?。?/p>
-
完成第1步的邏輯后,再向mq的PAY_QUEUE隊(duì)列中投遞一條支付消息,這條支付消息的內(nèi)容跟保存在支付庫消息表的消息內(nèi)容一致。
-
會計(jì)服務(wù)監(jiān)聽到這條消息了,會計(jì)服務(wù)處理消費(fèi)邏輯開始生成會計(jì)憑證。
-
會計(jì)憑證生成后,再反向向mq投遞一條消費(fèi)成功的消息到ACC_QUEUE隊(duì)列。
-
支付服務(wù)監(jiān)聽到會計(jì)服務(wù)消費(fèi)成功的消息,將本地消息表的消息狀態(tài)改為“已發(fā)送”。
-
消息恢復(fù)系統(tǒng)每隔一段時(shí)間去本地消息表中撈取狀態(tài)為“發(fā)送中”的消息,然后重新投遞到mq的PAY_QUEUE隊(duì)列中。
如果消息恢復(fù)系統(tǒng)重新投遞同一條消息達(dá)到一定閾值,則記錄報(bào)警和通知人工處理。
總結(jié)
本地消息表機(jī)制的優(yōu)點(diǎn)是建設(shè)成本比較低,但也存在兩個(gè)缺點(diǎn):
-
本地消息表與業(yè)務(wù)耦合在一起,很難實(shí)現(xiàn)通用性,無法單獨(dú)運(yùn)維管理。
-
本地消息表是基于數(shù)據(jù)庫來做的,在高并發(fā)下是有性能瓶頸的。
基于事務(wù)消息機(jī)制
無論是2PC&3PC還是TCC、本地消息事務(wù),基本都遵守XA協(xié)議的思想。即這些方案本質(zhì)上都是事務(wù)協(xié)調(diào)者協(xié)調(diào)各個(gè)事務(wù)參與者的本地事務(wù)的進(jìn)度,使所有本地事務(wù)共同提交或回滾,最終達(dá)成全局已執(zhí)行的特性。在協(xié)調(diào)的過程中,協(xié)調(diào)者需要收集各個(gè)本地事務(wù)的當(dāng)前狀態(tài),并根據(jù)這些狀態(tài)發(fā)出下一階段的操作指令。
但這些全局事務(wù)方案由于操作繁瑣、時(shí)間跨度大,或者在全局事務(wù)期間會排他地鎖住相關(guān)資源,使得整個(gè)分布式系統(tǒng)的全局事務(wù)的并發(fā)度不會太高。這很難滿足電商等高并發(fā)場景對事務(wù)吞吐量的要求,因此互聯(lián)網(wǎng)服務(wù)提供商探索出了很多與XA協(xié)議背道而馳的分布式事務(wù)解決方案。其中利用消息中間件實(shí)現(xiàn)的最終一致性全局事務(wù)(事務(wù)消息事務(wù))就是一個(gè)經(jīng)典方案。
基于事務(wù)消息機(jī)制
普通消息是無法解決本地事務(wù)執(zhí)行和消息發(fā)送的一致性問題的。因?yàn)橄l(fā)送是一個(gè)網(wǎng)絡(luò)通信的過程,發(fā)送消息的過程就有可能出現(xiàn)發(fā)送失敗、或者超時(shí)的情況。超時(shí)有可能發(fā)送成功了,有可能發(fā)送失敗了,消息的發(fā)送方是無法確定的,所以此時(shí)消息發(fā)送方無論是提交事務(wù)還是回滾事務(wù),都有可能不一致性出現(xiàn)。
解決這個(gè)問題,需要引入事務(wù)消息,事務(wù)消息和普通消息的區(qū)別在于事務(wù)消息發(fā)送成功后,處于prepared狀態(tài),不能被訂閱者消費(fèi),等到事務(wù)消息的狀態(tài)更改為可消費(fèi)狀態(tài)后,下游訂閱者才可以監(jiān)聽到次消息。
事務(wù)消息的發(fā)送處理流程如下:
-
事務(wù)發(fā)起者預(yù)先發(fā)送一個(gè)事務(wù)消息。MQ系統(tǒng)收到事務(wù)消息后,將消息持久化,消息的狀態(tài)是“待發(fā)送”,并給發(fā)送者一個(gè)ACK消息。
-
事務(wù)發(fā)起者如果沒有收到ACK消息,則取消本地事務(wù)的執(zhí)行;如果收到了ACK消息,則執(zhí)行本地事務(wù)。
-
執(zhí)行本地事務(wù)后,根據(jù)結(jié)果發(fā)送給MQ系統(tǒng)提交或回滾請求。
本地事務(wù)執(zhí)行完畢后,發(fā)給MQ的通知消息有可能丟失。所以支持事務(wù)消息的MQ系統(tǒng)有一個(gè)定時(shí)掃描邏輯,掃描出狀態(tài)仍然是“待發(fā)送”狀態(tài)的消息,并向消息的發(fā)送方發(fā)起詢問,詢問這條事務(wù)消息的最終狀態(tài)如何并根據(jù)結(jié)果更新事務(wù)消息的狀態(tài)。因此事務(wù)的發(fā)起方需要給MQ系統(tǒng)提供一個(gè)事務(wù)消息狀態(tài)查詢接口。
-
MQ系統(tǒng)收到消息通知后,如果提交請求,則將消息更改為“可消費(fèi)”供訂閱者消費(fèi);如果事務(wù)執(zhí)行回滾,則刪除該事務(wù)消息。
如果事務(wù)消息的狀態(tài)是“可發(fā)送”,則MQ系統(tǒng)向下游參與者推送消息,推送失敗會不停重試。
-
下游參與者收到消息后,執(zhí)行本地事務(wù),本地事務(wù)如果執(zhí)行成功,則給MQ系統(tǒng)發(fā)送ACK消息;如果執(zhí)行失敗或給MQ發(fā)送ACK的消息丟失,則MQ系統(tǒng)會持續(xù)推送給消息。
總結(jié)
基于事務(wù)消息機(jī)制實(shí)現(xiàn)了最終一致性,適用于異步更新的場景,并且對數(shù)據(jù)實(shí)時(shí)性要求不高的地方。
對比本地消息表實(shí)現(xiàn)方案,不需要?jiǎng)?chuàng)建本地消息表,也不需要依賴本地?cái)?shù)據(jù)庫事務(wù)了,所以這種方案更適用于高并發(fā)的場景。
RocketMQ可以直接支持生產(chǎn)環(huán)境使用基于事務(wù)消息機(jī)制,其他消息中間件(例如Kafka,RabbitMQ等)需要自研封裝一個(gè)可靠消息服務(wù)。
RocketMQ事務(wù)消息解決的是本地事務(wù)的執(zhí)行和發(fā)消息這兩個(gè)動作滿足事務(wù)的約束。Kafka事務(wù)消息則是用在一次事務(wù)中需要發(fā)送多個(gè)消息的情況,保證多個(gè)消息之間的事務(wù)約束,即多條消息要么都發(fā)送成功,要么都發(fā)送失敗。
最大努力通知機(jī)制
最大努力通知機(jī)制本質(zhì)是通過引入定期校驗(yàn)機(jī)制來對最終一致性做兜底,對業(yè)務(wù)侵入性較低、對MQ系統(tǒng)要求較低,實(shí)現(xiàn)比較簡單,適合于對最終一致性敏感度比較低、業(yè)務(wù)鏈路較短的場景,比如跨平臺、跨企業(yè)的系統(tǒng)間的業(yè)務(wù)交互。
適用場景
小明通過聯(lián)通網(wǎng)上營業(yè)廳為手機(jī)充話費(fèi)。整個(gè)操作的流程如下:
-
小明選擇充值金額“50元”,支付方式“支付寶”。
-
聯(lián)通網(wǎng)上營業(yè)廳創(chuàng)建一個(gè)充值訂單,狀態(tài)為“支付中”,并跳轉(zhuǎn)到支付寶的支付頁面。
-
支付寶驗(yàn)明確認(rèn)小明的支付后,從小明的賬戶中扣除50元,并向聯(lián)通的賬戶中增加50元。執(zhí)行完畢后向MQ系統(tǒng)發(fā)送一條消息,消息的內(nèi)容標(biāo)識支付是否成功,消息發(fā)送允許失敗。
-
如果消息發(fā)送成功,那么支付寶的通知服務(wù)會訂閱到該消息,并調(diào)用聯(lián)通的接口通知本次支付的結(jié)果。如果此時(shí)聯(lián)通的服務(wù)掛掉了,導(dǎo)致通知失敗了,則會按照5min、10min、30min、1h、...、24h等遞增的時(shí)間間隔,間隔性重復(fù)調(diào)用聯(lián)通的接口,直到調(diào)用成功或者達(dá)到預(yù)定的時(shí)間窗口上限后,則不再通知。這就是最大努力通知的含義。
-
如果聯(lián)通服務(wù)恢復(fù)正常,收到了支付寶的通知,給賬戶充值(聯(lián)通的充值接口需要保證冪等性)
-
如果聯(lián)通服務(wù)故障時(shí)間很久,恢復(fù)正常后,已超出支付寶通知服務(wù)的時(shí)間窗口,則聯(lián)通掃描“支付中”的訂單,主動向支付寶發(fā)起請求,核驗(yàn)訂單的支付結(jié)果。
技術(shù)選型
2PC&3PC
2PC&3PC強(qiáng)依賴數(shù)據(jù)庫,能夠很好的提供強(qiáng)一致性和強(qiáng)事務(wù)性,但相對來說延遲比較高,比較適合傳統(tǒng)的單體應(yīng)用,在同一個(gè)方法中存在跨庫操作的情況,不適合大型分布式、高并發(fā)和高性能要求的場景。
TCC
TCC適用于執(zhí)行時(shí)間確定且較短,實(shí)時(shí)性要求高,對數(shù)據(jù)一致性要求高,比如互聯(lián)網(wǎng)金融企業(yè)最核心的三個(gè)服務(wù):交易、支付、賬務(wù)。
Saga事務(wù)
Saga事務(wù)適用于業(yè)務(wù)流程長、業(yè)務(wù)流程多的業(yè)務(wù)且并發(fā)操作同一資源較少的情況。在銀行業(yè)金融機(jī)構(gòu)使用廣泛,比如互聯(lián)網(wǎng)微貸、渠道整合場景、金融機(jī)構(gòu)對接系統(tǒng)(需要對接外部系統(tǒng))等
基于本地消息表機(jī)制&基于事務(wù)消息機(jī)制
兩者都適用于事務(wù)中參與方支持操作冪等,對一致性要求不高,業(yè)務(wù)上能容忍數(shù)據(jù)不一致,直到兜底機(jī)制完成最終一致性。事務(wù)涉及的參與方、參與環(huán)節(jié)較少,業(yè)務(wù)上有對賬/校驗(yàn)系統(tǒng)兜底。
最大努力通知機(jī)制
最大努力通知機(jī)制適合于對最終一致性敏感度比較低、業(yè)務(wù)鏈路較短的場景。
前置知識
對于文中提到名詞的補(bǔ)充解釋。
DTP模型
DTP(Distributed Transaction Process)是一個(gè)分布式事務(wù)模型。在這個(gè)模型里面,有三個(gè)角色:
-
AP: Application,事務(wù)的發(fā)起者,也就是業(yè)務(wù)層。哪些操作屬于一個(gè)事務(wù),就是AP定義的。
-
TM: Transaction Manager,事務(wù)管理器,也稱協(xié)調(diào)者。接收AP的事務(wù)請求,對全局事務(wù)進(jìn)行管理,管理事務(wù)分支狀態(tài),協(xié)調(diào)RM的處理,通知RM哪些操作屬于哪些全局事務(wù)以及事務(wù)分支等等。是整個(gè)事務(wù)調(diào)度模型的核心部分。
-
RM:Resource Manager,資源管理器,也稱參與者。一般是數(shù)據(jù)庫,也可以是其他的資源管理器,如消息隊(duì)列(如JMS數(shù)據(jù)源),文件系統(tǒng)等。
DTP模型上定義了三個(gè)角色,但實(shí)際實(shí)現(xiàn)上可以由一個(gè)角色同時(shí)擔(dān)當(dāng)兩個(gè)功能。比如:AP和TM合并,TM沒必要單獨(dú)部署組件。
XA協(xié)議(XA Specification)
XA是一種分布式事務(wù)處理規(guī)范。XA規(guī)范了TM與RM之間的通信接口(如下圖所示的函數(shù)),在TM與多個(gè)RM之間形成一個(gè)雙向通信橋梁,從而在多個(gè)數(shù)據(jù)庫資源下保證強(qiáng)一致性。目前知名的數(shù)據(jù)庫,如Oracle、DB2、MySQL等,都是實(shí)現(xiàn)了XA接口的,都可以作為RM。
在整個(gè)事務(wù)處理過程中,數(shù)據(jù)一直處于鎖住狀態(tài),即從prepare到commit、rollback的整個(gè)過程中,TM一直持有數(shù)據(jù)庫的鎖,如果有其他事務(wù)要修改數(shù)據(jù)庫的該條數(shù)據(jù),就必須等待鎖的釋放。