原创 | 分布式事务科普(终结篇)
點(diǎn)擊上方“朱小廝的博客”,選擇“設(shè)為星標(biāo)”
后臺(tái)回復(fù)"高效Java"領(lǐng)取《Effective Java第三版》
當(dāng)當(dāng)優(yōu)惠碼:V54PW7,可減¥30
歡迎跳轉(zhuǎn)到本文的原文鏈接:https://honeypps.com/architect/introduction-of-distributed-transaction/
《分布式事務(wù)科普》是我在YiQing期間整理的一篇科普型文章,內(nèi)容共計(jì)兩萬五千字左右,應(yīng)該算是涵蓋了這個(gè)領(lǐng)域的大多數(shù)知識(shí)點(diǎn)。篇幅較長(zhǎng),遂分為上下兩篇發(fā)出。上篇為《分布式事務(wù)科普——初識(shí)篇》:ACID、事務(wù)隔離級(jí)別、MySQL事務(wù)實(shí)現(xiàn)原理、CAP、BASE、2PC、3PC等(昨天已經(jīng)發(fā)出,有需要的同學(xué)可以跳轉(zhuǎn))。下篇為《分布式事務(wù)科普——終結(jié)篇》,詳細(xì)講解分布式事務(wù)的解決方案:XA、AT、TCC、Saga、本地消息表、消息事務(wù)、最大努力通知等。
分布式事務(wù)科普
隨著業(yè)務(wù)的快速發(fā)展、業(yè)務(wù)復(fù)雜度越來越高,傳統(tǒng)單體應(yīng)用逐漸暴露出了一些問題,例如開發(fā)效率低、可維護(hù)性差、架構(gòu)擴(kuò)展性差、部署不靈活、健壯性差等等。而微服務(wù)架構(gòu)是將單個(gè)服務(wù)拆分成一系列小服務(wù),且這些小服務(wù)都擁有獨(dú)立的進(jìn)程,彼此獨(dú)立,很好地解決了傳統(tǒng)單體應(yīng)用的上述問題,但是在微服務(wù)架構(gòu)下如何保證事務(wù)的一致性呢?本文首先從事務(wù)的概念出來,帶大家先回顧一下ACID、事務(wù)隔離級(jí)別、CAP、BASE、2PC、3PC等基本理論(參考上篇《分布式事務(wù)科普——初識(shí)篇》),然后再詳細(xì)講解分布式事務(wù)的解決方案:XA、AT、TCC、Saga、本地消息表、消息事務(wù)、最大努力通知等。
分布式事務(wù)解決方案
在引入分布式事務(wù)前,我們最好先明確一下我們是否真的需要分布式事務(wù)。有可能因?yàn)檫^度設(shè)計(jì)致使微服務(wù)過多,從而不得不引入分布式事務(wù),這個(gè)時(shí)候就不建議你采用下面的任何一種方案,而是把需要事務(wù)的微服務(wù)聚合成一個(gè)單機(jī)服務(wù),使用數(shù)據(jù)庫(kù)的本地事務(wù)。因?yàn)椴徽撊魏我环N方案都會(huì)增加你系統(tǒng)的復(fù)雜度,這樣的成本實(shí)在是太高了,千萬不要因?yàn)樽非竽承┰O(shè)計(jì),而引入不必要的成本和復(fù)雜度。
常見的分布式事務(wù)方案有:XA、AT、TCC、Saga、本地消息表、MQ消息事務(wù)、最大努力通知等。
X/Open DTP模型與XA
X/Open,即現(xiàn)在的open group,是一個(gè)獨(dú)立的組織,主要負(fù)責(zé)制定各種行業(yè)技術(shù)標(biāo)準(zhǔn)。官網(wǎng)地址:http://www.opengroup.org/。X/Open組織主要由各大知名公司或者廠商進(jìn)行支持,這些組織不光遵循X/Open組織定義的行業(yè)技術(shù)標(biāo)準(zhǔn),也參與到標(biāo)準(zhǔn)的制定。
DTP全稱是Distributed Transaction Process,即分布式事務(wù)模型。在DTP本地模型實(shí)例中包含3個(gè)部分:AP、TM和RM,如下圖所示。其中,AP 可以和TM 以及 RM 通信,TM 和 RM 互相之間可以通信。
-
AP(Application Program,應(yīng)用程序):AP定義事務(wù)邊界(定義事務(wù)開始和結(jié)束)并訪問事務(wù)邊界內(nèi)的資源。
-
RM(Resource Manager,資源管理器):RM管理著某些共享資源的自治域,比如說一個(gè)MySQL數(shù)據(jù)庫(kù)實(shí)例。在DTP里面還有兩個(gè)要求,一是RM自身必須是支持事務(wù)的,二是RM能夠根據(jù)全局(分布式)事務(wù)標(biāo)識(shí)(GTID之類的)定位到自己內(nèi)部的對(duì)應(yīng)事務(wù)。
-
TM(Transaction Manager,事務(wù)管理器):TM能與AP和RM直接通信,協(xié)調(diào)AP和RM來實(shí)現(xiàn)分布式事務(wù)的完整性。負(fù)責(zé)管理全局事務(wù),分配全局事務(wù)標(biāo)識(shí),監(jiān)控事務(wù)的執(zhí)行進(jìn)度,并負(fù)責(zé)事務(wù)的提交、回滾、失敗恢復(fù)等。
AP和RM之間則通過RM提供的Native API 進(jìn)行資源控制,這個(gè)沒有進(jìn)行約API和規(guī)范,各個(gè)廠商自己實(shí)現(xiàn)自己的資源控制,比如Oracle自己的數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序。
DTP模型里面定義了XA接口,TM 和 RM 通過XA接口進(jìn)行雙向通信(這也是XA的主要作用, 除此之外,XA還對(duì)兩階段提交協(xié)議進(jìn)行了部分優(yōu)化),例如:TM通知RM提交事務(wù)或者回滾事務(wù),RM把提交結(jié)果通知給TM。XA 的全稱是eXtended Architecture,它是一個(gè)分布式事務(wù)協(xié)議,它通過二階段提交協(xié)議保證強(qiáng)一致性。
其過程大致如下:
-
第一階段:TM請(qǐng)求所有RM進(jìn)行準(zhǔn)備,并告知它們各自需要做的局部事務(wù)(Transaction Branch)。RM收到請(qǐng)求后,如果判斷可以完成自己的局部事務(wù),那就持久化局部事務(wù)的工作內(nèi)容,再給TM肯定答復(fù);要是發(fā)生了其他情況,那給TM的都是否定答復(fù)。在發(fā)送了否定答復(fù)并回滾了局部事務(wù)之后,RM才能丟棄持久化了的局部事務(wù)信息。
-
第二階段:TM根據(jù)情況(比如說所有RM Prepare成功,或者,AP通知它要Rollback等),先持久化它對(duì)這個(gè)全局事務(wù)的處理決定和所涉及的RM清單,然后通知所有涉及的RM去提交或者回滾它們的局部事務(wù)。RM們處理完自己的局部事務(wù)后,將返回值告訴TM之后,TM才可以清除掉包括剛才持久化的處理決定和RM清單在內(nèi)的這個(gè)全局事務(wù)的信息。
基于XA協(xié)議實(shí)現(xiàn)的分布式事務(wù)是強(qiáng)一致性的分布式事務(wù),典型應(yīng)用場(chǎng)景如JAVA中有關(guān)分布式事務(wù)的規(guī)范如JTA(Java Transaction API)和JTS(Java Transaction Service)中就涉及到了XA。
XA 協(xié)議通常實(shí)現(xiàn)在數(shù)據(jù)庫(kù)資源層,直接作用于資源管理器上。因此,基于 XA 協(xié)議實(shí)現(xiàn)的分布式事務(wù)產(chǎn)品,無論是分布式數(shù)據(jù)庫(kù)還是分布式事務(wù)框架,對(duì)業(yè)務(wù)幾乎都沒有侵入,就像使用普通數(shù)據(jù)庫(kù)一樣。
不過XA的使用并不廣泛,究其原因主要有以下幾類:
-
性能,如:阻塞性協(xié)議,增加響應(yīng)時(shí)間、鎖時(shí)間、死鎖等因素的存在,在高并發(fā)場(chǎng)景下并不適用。
-
支持程度,并不是所有的資源都支持XA協(xié)議;在數(shù)據(jù)庫(kù)中支持完善度也有待考驗(yàn),比如MySQL 5.7之前都有缺陷(MySQL 5.0版本開始支持XA,只有當(dāng)隔離級(jí)別為SERIALIZABLE的時(shí)候才能使用分布式事務(wù))。
-
運(yùn)維復(fù)雜。
Seata與AT模式
AT(Automatic Transaction)模式是基于XA事務(wù)演進(jìn)而來,核心是對(duì)業(yè)務(wù)無侵入,是一種改進(jìn)后的兩階段提交,需要數(shù)據(jù)庫(kù)支持。AT最早出現(xiàn)在阿里巴巴開源的分布式事務(wù)框架Seata中,我們不妨先簡(jiǎn)單了解下Seata。
Seata簡(jiǎn)介
Seata(Simple Extensible Autonomous Transaction Architecture,一站式分布式事務(wù)解決方案)是 2019 年 1 月份螞蟻金服和阿里巴巴共同開源的分布式事務(wù)解決方案。Seata 的設(shè)計(jì)思路是將一個(gè)分布式事務(wù)可以理解成一個(gè)全局事務(wù),下面掛了若干個(gè)分支事務(wù),而一個(gè)分支事務(wù)是一個(gè)滿足 ACID 的本地事務(wù),因此我們可以操作分布式事務(wù)像操作本地事務(wù)一樣。
Seata 內(nèi)部定義了 3個(gè)模塊來處理全局事務(wù)和分支事務(wù)的關(guān)系和處理過程,如上圖所示,分別是 TM、RM 和 TC。其中 TM 和 RM 是作為 Seata 的客戶端與業(yè)務(wù)系統(tǒng)集成在一起,TC 作為 Seata 的服務(wù)端獨(dú)立部署。? Transaction Coordinator(TC):事務(wù)協(xié)調(diào)器,維護(hù)全局事務(wù)的運(yùn)行狀態(tài),負(fù)責(zé)協(xié)調(diào)并驅(qū)動(dòng)全局事務(wù)的提交或回滾。? Transaction Manager(TM):控制全局事務(wù)的邊界,負(fù)責(zé)開啟一個(gè)全局事務(wù),并最終發(fā)起全局提交或全局回滾的決議。? Resource Manager(RM):控制分支事務(wù),負(fù)責(zé)分支注冊(cè)、狀態(tài)匯報(bào),并接收事務(wù)協(xié)調(diào)器的指令,驅(qū)動(dòng)分支(本地)事務(wù)的提交和回滾
-
Transaction Coordinator(TC):事務(wù)協(xié)調(diào)器,維護(hù)全局事務(wù)的運(yùn)行狀態(tài),負(fù)責(zé)協(xié)調(diào)并驅(qū)動(dòng)全局事務(wù)的提交或回滾。
-
Transaction Manager(TM):控制全局事務(wù)的邊界,負(fù)責(zé)開啟一個(gè)全局事務(wù),并最終發(fā)起全局提交或全局回滾的決議。
-
Resource Manager(RM):控制分支事務(wù),負(fù)責(zé)分支注冊(cè)、狀態(tài)匯報(bào),并接收事務(wù)協(xié)調(diào)器的指令,驅(qū)動(dòng)分支(本地)事務(wù)的提交和回滾。
參照上圖,簡(jiǎn)要概括整個(gè)事務(wù)的處理流程為:
TM 向 TC 申請(qǐng)開啟一個(gè)全局事務(wù),TC 創(chuàng)建全局事務(wù)后返回全局唯一的 XID,XID 會(huì)在全局事務(wù)的上下文中傳播;
RM 向 TC 注冊(cè)分支事務(wù),該分支事務(wù)歸屬于擁有相同 XID 的全局事務(wù);
TM要求TC提交或回滾XID的相應(yīng)全局事務(wù)。
TC在XID的相應(yīng)全局事務(wù)下驅(qū)動(dòng)所有分支事務(wù)以完成分支提交或回滾。
Seata 會(huì)有 4 種分布式事務(wù)解決方案,分別是 AT 模式、TCC 模式、Saga 模式和 XA 模式。這個(gè)小節(jié)我們主要來講述一下AT模式的實(shí)現(xiàn)方式,TCC和Saga模式在后面會(huì)繼續(xù)介紹。
AT模式
Seata 的事務(wù)提交方式跟 XA 協(xié)議的兩段式提交在總體上來說基本是一致的,那它們之間有什么不同呢?
我們都知道 XA 協(xié)議它依賴的是數(shù)據(jù)庫(kù)層面來保障事務(wù)的一致性,也即是說 XA 的各個(gè)分支事務(wù)是在數(shù)據(jù)庫(kù)層面上驅(qū)動(dòng)的,由于 XA 的各個(gè)分支事務(wù)需要有 XA 的驅(qū)動(dòng)程序,一方面會(huì)導(dǎo)致數(shù)據(jù)庫(kù)與 XA 驅(qū)動(dòng)耦合,另一方面它會(huì)導(dǎo)致各個(gè)分支的事務(wù)資源鎖定周期長(zhǎng),這也是它沒有在互聯(lián)網(wǎng)公司流行的重要因素。
基于 XA 協(xié)議以上的問題,Seata 另辟蹊徑,既然在依賴數(shù)據(jù)庫(kù)層會(huì)導(dǎo)致這么多問題,那我們就從應(yīng)用層做手腳,這還得從 Seata 的 RM 模塊說起,前面也說過 RM 的主要作用了,其實(shí) RM 在內(nèi)部做了對(duì)數(shù)據(jù)庫(kù)操作的代理層。如上圖所示,在使用 Seata 時(shí),我們使用的數(shù)據(jù)源實(shí)際上用的是 Seata 自帶的數(shù)據(jù)源代理 DataSourceProxy,Seata 在這層代理中加入了很多邏輯,主要是解析 SQL,把業(yè)務(wù)數(shù)據(jù)在更新前后的數(shù)據(jù)鏡像組織成回滾日志,并將 undo log 日志插入 undo_log 表中,保證每條更新數(shù)據(jù)的業(yè)務(wù) SQL都有對(duì)應(yīng)的回滾日志存在。
這樣做的好處就是,本地事務(wù)執(zhí)行完可以立即釋放本地事務(wù)鎖定的資源,然后向 TC 上報(bào)分支狀態(tài)。當(dāng) TM 決議全局提交時(shí),就不需要同步協(xié)調(diào)處理了,TC 會(huì)異步調(diào)度各個(gè) RM 分支事務(wù)刪除對(duì)應(yīng)的 undo log 日志即可,這個(gè)步驟非??焖俚乜梢酝瓿?#xff1b;當(dāng) TM 決議全局回滾時(shí),RM 收到 TC 發(fā)送的回滾請(qǐng)求,RM 通過 XID 找到對(duì)應(yīng)的 undo log 回滾日志,然后執(zhí)行回滾日志完成回滾操作。
如上圖(左),XA 方案的 RM 是放在數(shù)據(jù)庫(kù)層的,它依賴了數(shù)據(jù)庫(kù)的 XA 驅(qū)動(dòng)程序。而上圖(右),Seata 的 RM 實(shí)際上是已中間件的形式放在應(yīng)用層,不用依賴數(shù)據(jù)庫(kù)對(duì)協(xié)議的支持,完全剝離了分布式事務(wù)方案對(duì)數(shù)據(jù)庫(kù)在協(xié)議支持上的要求。
AT模式下是如何做到對(duì)業(yè)務(wù)無侵入,又是如何執(zhí)行提交和回滾的呢?
第一階段
參照下圖,Seata 的 JDBC 數(shù)據(jù)源代理通過對(duì)業(yè)務(wù) SQL 的解析,把業(yè)務(wù)數(shù)據(jù)在更新前后的數(shù)據(jù)鏡像組織成回滾日志(undo log),利用本地事務(wù)的 ACID 特性,將業(yè)務(wù)數(shù)據(jù)的更新和回滾日志的寫入在同一個(gè)本地事務(wù)中提交。這樣可以保證任何提交的業(yè)務(wù)數(shù)據(jù)的更新一定有相應(yīng)的回滾日志存在,最后對(duì)分支事務(wù)狀態(tài)向 TC 進(jìn)行上報(bào)。基于這樣的機(jī)制,分支的本地事務(wù)便可以在全局事務(wù)的第一階段提交,馬上釋放本地事務(wù)鎖定的資源。
第二階段
如果決議是全局提交,此時(shí)分支事務(wù)此時(shí)已經(jīng)完成提交,不需要同步協(xié)調(diào)處理(只需要異步清理回滾日志),第二階段可以非??焖俚亟Y(jié)束,參考下圖。
如果決議是全局回滾,RM收到協(xié)調(diào)器發(fā)來的回滾請(qǐng)求,通過XID和Branch ID找到相應(yīng)的回滾日志記錄,通過回滾記錄生成反向的更新SQL并執(zhí)行,以完成分支的回滾,參考下圖。
講到這里,關(guān)于AT模式大部分問題我們應(yīng)該都清楚了,但總結(jié)起來,核心也只解決了一件事情,就是ACID中最基本、最重要的 A(原子性)。但是,光解決A顯然是不夠的:既然本地事務(wù)已經(jīng)提交,那么如果數(shù)據(jù)在全局事務(wù)結(jié)束前被修改了,回滾時(shí)怎么處理?ACID 的 I(隔離性)在Seata的AT模式是如何處理的呢?
Seata AT 模式引入全局鎖機(jī)制來實(shí)現(xiàn)隔離。全局鎖是由 Seata 的 TC 維護(hù)的,事務(wù)中涉及的數(shù)據(jù)的鎖。
寫隔離
參考官網(wǎng)(https://seata.io/en-us/docs/overview/what-is-seata.html)的資料,寫隔離的要領(lǐng)如下:
-
第一階段本地事務(wù)提交前,需要確保先拿到全局鎖 。
-
拿不到全局鎖,不能提交本地事務(wù)。
-
拿全局鎖的嘗試被限制在一定范圍內(nèi),超出范圍將放棄,并回滾本地事務(wù),釋放本地鎖。
以一個(gè)示例來說明。兩個(gè)全局事務(wù)tx1和tx2,分別對(duì)a表的m字段進(jìn)行更新操作,m的初始值1000。tx1先開始,開啟本地事務(wù)拿到本地鎖,更新操作 m = 1000 - 100 = 900。本地事務(wù)提交前,先拿到該記錄的全局鎖,本地提交釋放本地鎖。tx2后開始,開啟本地事務(wù)拿到本地鎖,更新操作 m = 900 - 100 = 800。本地事務(wù)提交前,嘗試拿該記錄的全局鎖,tx1全局提交前,該記錄的全局鎖被 tx1持有,tx2需要重試等待全局鎖 。
tx1 第二階段全局提交,釋放全局鎖 。tx2拿到全局鎖提交本地事務(wù)。
如果tx1的第二階段全局回滾,則tx1需要重新獲取該數(shù)據(jù)的本地鎖,進(jìn)行反向補(bǔ)償?shù)母虏僮?#xff0c;實(shí)現(xiàn)分支的回滾。
參考下圖,此時(shí)如果tx2仍在等待該數(shù)據(jù)的全局鎖,同時(shí)持有本地鎖,則tx1的分支回滾會(huì)失敗。分支的回滾會(huì)一直重試,直到tx2的全局鎖等鎖超時(shí),放棄全局鎖并回滾本地事務(wù)釋放本地鎖,tx1 的分支回滾最終成功。
因?yàn)檎麄€(gè)過程全局鎖在tx1結(jié)束前一直是被tx1持有的,所以不會(huì)發(fā)生臟寫的問題。
讀隔離
在數(shù)據(jù)庫(kù)本地事務(wù)隔離級(jí)別為讀已提交(READ COMMITTED)或以上的基礎(chǔ)上,Seata(AT模式)的默認(rèn)全局隔離級(jí)別是讀未提交(READ UNCOMMITTED)。如果應(yīng)用在特定場(chǎng)景下,必需要求全局的讀已提交,目前Seata的方式是通過SELECT FOR UPDATE語句的代理。
SELECT FOR UPDATE語句的執(zhí)行會(huì)申請(qǐng)全局鎖 ,如果全局鎖被其他事務(wù)持有,則釋放本地鎖(回滾SELECT FOR UPDATE語句的本地執(zhí)行)并重試。這個(gè)過程中,查詢是被阻塞 住的,直到全局鎖拿到,即讀取的相關(guān)數(shù)據(jù)是已提交的,才返回。
全局鎖是由 TC 也就是服務(wù)端來集中維護(hù),而不是在數(shù)據(jù)庫(kù)維護(hù)的。這樣做有兩點(diǎn)好處:一方面,鎖的釋放非???#xff0c;尤其是在全局提交的情況下收到全局提交的請(qǐng)求,鎖馬上就釋放掉了,不需要與 RM 或數(shù)據(jù)庫(kù)進(jìn)行一輪交互;另外一方面,因?yàn)殒i不是數(shù)據(jù)庫(kù)維護(hù)的,從數(shù)據(jù)庫(kù)層面看數(shù)據(jù)沒有鎖定。這也就是給極端情況下,業(yè)務(wù)降級(jí)提供了方便,事務(wù)協(xié)調(diào)器異常導(dǎo)致的一部分異常事務(wù),不會(huì)阻塞后面業(yè)務(wù)的繼續(xù)進(jìn)行。
AT模式基于本地事務(wù)的特性,通過攔截并解析 SQL 的方式,記錄自定義的回滾日志,從而打破 XA 協(xié)議阻塞性的制約,在一致性、性能、易用性三個(gè)方面取得一定的平衡:在達(dá)到確定一致性(非最終一致)的前提下,即保障一定的性能,又能完全不侵入業(yè)務(wù)。在很多應(yīng)用場(chǎng)景下,Seata的AT模式都能很好地發(fā)揮作用,把應(yīng)用的分布式事務(wù)支持成本降到極低的水平。
不過AT模式也并非銀彈,在使用之前最好權(quán)衡好以下幾個(gè)方面:
-
隔離性。隔離性不高,目前只能支持到接近讀已提交的程度,更高的隔離級(jí)別,實(shí)現(xiàn)成本將非常高。
-
性能損耗。一條Update的SQL,則需要全局事務(wù)XID獲取(與TC通訊)、before image(解析SQL,查詢一次數(shù)據(jù)庫(kù))、after image(查詢一次數(shù)據(jù)庫(kù))、insert undo log(寫一次數(shù)據(jù)庫(kù))、before commit(與TC通訊,判斷鎖沖突),這些操作都需要一次遠(yuǎn)程通訊RPC,而且是同步的。另外undo log寫入時(shí)blob字段的插入性能也是不高的。每條寫SQL都會(huì)增加這么多開銷,粗略估計(jì)會(huì)增加5倍響應(yīng)時(shí)間(二階段雖然是異步的,但其實(shí)也會(huì)占用系統(tǒng)資源,網(wǎng)絡(luò)、線程、數(shù)據(jù)庫(kù))。
-
全局鎖。Seata在每個(gè)分支事務(wù)中會(huì)攜帶對(duì)應(yīng)的鎖信息,在before commit階段會(huì)依次獲取鎖(因?yàn)樾枰獙⑺蠸QL執(zhí)行完才能拿到所有鎖信息,所以放在commit前判斷)。相比XA,Seata 雖然在一階段成功后會(huì)釋放數(shù)據(jù)庫(kù)鎖,但一階段在commit前全局鎖的判定也拉長(zhǎng)了對(duì)數(shù)據(jù)鎖的占有時(shí)間,這個(gè)開銷比XA的prepare低多少需要根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景進(jìn)行測(cè)試。全局鎖的引入實(shí)現(xiàn)了隔離性,但帶來的問題就是阻塞,降低并發(fā)性,尤其是熱點(diǎn)數(shù)據(jù),這個(gè)問題會(huì)更加嚴(yán)重。Seata在回滾時(shí),需要先刪除各節(jié)點(diǎn)的undo log,然后才能釋放TC內(nèi)存中的鎖,所以如果第二階段是回滾,釋放鎖的時(shí)間會(huì)更長(zhǎng)。Seata的引入全局鎖會(huì)額外增加死鎖的風(fēng)險(xiǎn),但如果實(shí)現(xiàn)死鎖,會(huì)不斷進(jìn)行重試,最后靠等待全局鎖超時(shí),這種方式并不優(yōu)雅,也延長(zhǎng)了對(duì)數(shù)據(jù)庫(kù)鎖的占有時(shí)間。
TCC
關(guān)于TCC(Try-Confirm-Cancel)的概念,最早是由Pat Helland于2007年發(fā)表的一篇名為《Life beyond Distributed Transactions:an Apostate’s Opinion》的論文提出。在該論文中,TCC還是以Tentative-Confirmation-Cancellation命名。正式以Try-Confirm-Cancel作為名稱的是Atomikos公司,其注冊(cè)了TCC商標(biāo)。
TCC分布式事務(wù)模型相對(duì)于 XA 等傳統(tǒng)模型,其特征在于它不依賴資源管理器(RM)對(duì)分布式事務(wù)的支持,而是通過對(duì)業(yè)務(wù)邏輯的分解來實(shí)現(xiàn)分布式事務(wù)。
TCC 模型認(rèn)為對(duì)于業(yè)務(wù)系統(tǒng)中一個(gè)特定的業(yè)務(wù)邏輯,其對(duì)外提供服務(wù)時(shí)必須接受一些不確定性,即對(duì)業(yè)務(wù)邏輯初步操作的調(diào)用僅是一個(gè)臨時(shí)性操作,調(diào)用它的主業(yè)務(wù)服務(wù)保留了后續(xù)的取消權(quán)。如果主業(yè)務(wù)服務(wù)認(rèn)為全局事務(wù)應(yīng)該回滾,它會(huì)要求取消之前的臨時(shí)性操作,這就對(duì)應(yīng)從業(yè)務(wù)服務(wù)的取消操作。而當(dāng)主業(yè)務(wù)服務(wù)認(rèn)為全局事務(wù)應(yīng)該提交時(shí),它會(huì)放棄之前臨時(shí)性操作的取消權(quán),這對(duì)應(yīng)從業(yè)務(wù)服務(wù)的確認(rèn)操作。每一個(gè)初步操作,最終都會(huì)被確認(rèn)或取消。
因此,針對(duì)一個(gè)具體的業(yè)務(wù)服務(wù),TCC 分布式事務(wù)模型需要業(yè)務(wù)系統(tǒng)提供三段業(yè)務(wù)邏輯:
Try:完成所有業(yè)務(wù)檢查,預(yù)留必須的業(yè)務(wù)資源。
Confirm:真正執(zhí)行的業(yè)務(wù)邏輯,不作任何業(yè)務(wù)檢查,只使用 Try 階段預(yù)留的業(yè)務(wù)資源。因此,只要Try操作成功,Confirm必須能成功。另外,Confirm操作需滿足冪等性,保證分布式事務(wù)有且只能成功一次。
Cancel:釋放 Try 階段預(yù)留的業(yè)務(wù)資源。同樣的,Cancel 操作也需要滿足冪等性。
TCC分布式事務(wù)模型包括三部分:
-
主業(yè)務(wù)服務(wù)(Main Server):主業(yè)務(wù)服務(wù)為整個(gè)業(yè)務(wù)活動(dòng)的發(fā)起方、服務(wù)的編排者,負(fù)責(zé)發(fā)起并完成整個(gè)業(yè)務(wù)活動(dòng)。
-
從業(yè)務(wù)服務(wù)(Service):從業(yè)務(wù)服務(wù)是整個(gè)業(yè)務(wù)活動(dòng)的參與方,負(fù)責(zé)提供TCC業(yè)務(wù)操作,實(shí)現(xiàn)Try、Confirm、Cancel三個(gè)接口,供主業(yè)務(wù)服務(wù)調(diào)用。
-
事務(wù)管理器(Transaction Manager):事務(wù)管理器管理控制整個(gè)業(yè)務(wù)活動(dòng),包括記錄維護(hù)TCC全局事務(wù)的事務(wù)狀態(tài)和每個(gè)從業(yè)務(wù)服務(wù)的子事務(wù)狀態(tài),并在業(yè)務(wù)活動(dòng)提交時(shí)調(diào)用所有從業(yè)務(wù)服務(wù)的Confirm操作,在業(yè)務(wù)活動(dòng)取消時(shí)調(diào)用所有從業(yè)務(wù)服務(wù)的Cancel操作。
上圖所展示的是TCC事務(wù)模型與DTP事務(wù)模型的對(duì)比圖,看上去這兩者差別很大。聰明的讀者應(yīng)該可以從圖中的著色上猜出些端倪,其實(shí)這兩者基本一致:TCC模型中的主業(yè)務(wù)服務(wù)相當(dāng)于DTP模型中AP,從業(yè)務(wù)服務(wù)相當(dāng)于DTP模型中的RM,兩者也都有一個(gè)事務(wù)管理器;TCC模型中從業(yè)務(wù)服務(wù)器所提供的Try/Commit/Cancel接口相當(dāng)于DTP模型中RM提供的Prepare/Commit/Rollback接口。
所不同的是DTP模型中Prepare/Commit/Rollback都是由事務(wù)管理器調(diào)用,TCC模型中的Try接口是由主業(yè)務(wù)服務(wù)調(diào)用的,二階段的Commit/Cancel才是由事務(wù)管理器調(diào)用。這就是TCC事務(wù)模型的二階段異步化功能,從業(yè)務(wù)服務(wù)的第一階段執(zhí)行成功,主業(yè)務(wù)服務(wù)就可以提交完成,然后再由事務(wù)管理器框架異步的執(zhí)行各從業(yè)務(wù)服務(wù)的第二階段。這里犧牲了一定的隔離性和一致性的,但是提高了長(zhǎng)事務(wù)的可用性。
下面我們?cè)賮砹私庖幌乱粋€(gè)完整的TCC分布式事務(wù)流程:
主業(yè)務(wù)服務(wù)首先開啟本地事務(wù)。
主業(yè)務(wù)服務(wù)向事務(wù)管理器申請(qǐng)啟動(dòng)分布式事務(wù)主業(yè)務(wù)活動(dòng)。
然后針對(duì)要調(diào)用的從業(yè)務(wù)服務(wù),主業(yè)務(wù)活動(dòng)先向事務(wù)管理器注冊(cè)從業(yè)務(wù)活動(dòng),然后調(diào)用從業(yè)務(wù)服務(wù)的 Try 接口。
當(dāng)所有從業(yè)務(wù)服務(wù)的 Try 接口調(diào)用成功,主業(yè)務(wù)服務(wù)提交本地事務(wù);若調(diào)用失敗,主業(yè)務(wù)服務(wù)回滾本地事務(wù)。
若主業(yè)務(wù)服務(wù)提交本地事務(wù),則TCC模型分別調(diào)用所有從業(yè)務(wù)服務(wù)的Confirm接口;若主業(yè)務(wù)服務(wù)回滾本地事務(wù),則分別調(diào)用 Cancel 接口;
所有從業(yè)務(wù)服務(wù)的Confirm或Cancel操作完成后,全局事務(wù)結(jié)束。
用戶接入TCC,最重要的是考慮如何將自己的業(yè)務(wù)模型拆成兩階段來實(shí)現(xiàn)。下面,我們從一個(gè)簡(jiǎn)答的例子來熟悉一下TCC的具體用法。
以“扣錢”場(chǎng)景為例,在接入TCC前,對(duì)A賬戶的扣錢,只需一條更新賬戶余額的 SQL 便能完成;但是在接入TCC之后,用戶就需要考慮如何將原來一步就能完成的扣錢操作拆成兩階段,實(shí)現(xiàn)成三個(gè)方法,并且保證Try成功Confirm一定能成功。
如下圖所示,一階段Try方法需要做資源的檢查和預(yù)留。在扣錢場(chǎng)景下,Try要做的事情是就是檢查賬戶余額是否充足,預(yù)留轉(zhuǎn)賬資金,預(yù)留的方式就是凍結(jié)A賬戶的轉(zhuǎn)賬資金。Try方法執(zhí)行之后,賬號(hào)A余額雖然還是100,但是其中30元已經(jīng)被凍結(jié)了,不能被其他事務(wù)使用。
二階段Confirm執(zhí)行真正的扣錢操作。Confirm會(huì)使用Try階段凍結(jié)的資金,執(zhí)行賬號(hào)扣款。Confirm執(zhí)行之后,賬號(hào)A在一階段中凍結(jié)的30元已經(jīng)被扣除,賬號(hào)A余額變成 70 元 。
如果二階段是回滾的話,就需要在Cancel方法內(nèi)釋放一階段Try凍結(jié)的30元,使賬號(hào)A的回到初始狀態(tài),100元全部可用。
在TCC模型中,事務(wù)的隔離交給業(yè)務(wù)邏輯來實(shí)現(xiàn)。其隔離性思想就是通過業(yè)務(wù)的改造,在第一階段結(jié)束之后,從底層數(shù)據(jù)庫(kù)資源層面的加鎖過渡為上層業(yè)務(wù)層面的加鎖,從而釋放底層數(shù)據(jù)庫(kù)鎖資源,放寬分布式事務(wù)鎖協(xié)議,將鎖的粒度降到最低,以最大限度提高業(yè)務(wù)并發(fā)性能。
以上面的例子舉例,賬戶A上有100元,事務(wù)tx1要扣除其中的30元,事務(wù)tx2也要扣除30元,出現(xiàn)并發(fā)。在第一階段的Try操作中,需要先利用數(shù)據(jù)庫(kù)資源層面的加鎖,檢查賬戶可用余額,如果余額充足,則預(yù)留業(yè)務(wù)資源,扣除本次交易金額。一階段結(jié)束后,雖然數(shù)據(jù)庫(kù)層面資源鎖被釋放了,但這筆資金被業(yè)務(wù)隔離,不允許除本事務(wù)之外的其它并發(fā)事務(wù)動(dòng)用。
補(bǔ)償性事務(wù)
TCC第一階段的Try或者第二階段的Confirm/Cancel在執(zhí)行過程中,一般都會(huì)開啟各自的本地事務(wù),來保證方法內(nèi)部業(yè)務(wù)邏輯的ACID特性。這里Confirm/Cancel執(zhí)行的本地事務(wù)是補(bǔ)償性事務(wù)。
補(bǔ)償性事務(wù)是一個(gè)獨(dú)立的支持ACID特性的本地事務(wù),用于在邏輯上取消服務(wù)提供者上一個(gè)ACID事務(wù)造成的影響,對(duì)于一個(gè)長(zhǎng)事務(wù)(long-running transaction),與其實(shí)現(xiàn)一個(gè)巨大的分布式ACID事務(wù),不如使用基于補(bǔ)償性的方案,把每一次服務(wù)調(diào)用當(dāng)做一個(gè)較短的本地ACID事務(wù)來處理,執(zhí)行完就立即提交。
TCC第二階段Confirm/Cancel執(zhí)行的補(bǔ)償性事務(wù)用于取消Try階段本地事務(wù)造成的影響。因?yàn)榈谝浑A段Try只是預(yù)留資源,之后必須要明確的告訴服務(wù)提供者,這個(gè)資源到底要還需不需要。下一節(jié)中所要講述的Saga也是一種補(bǔ)償性的事務(wù)。
TCC異常控制
在有了一套完備的 TCC 接口之后,是不是就真的高枕無憂了呢?答案是否定的。在微服務(wù)架構(gòu)下,很有可能出現(xiàn)網(wǎng)絡(luò)超時(shí)、重發(fā),機(jī)器宕機(jī)等一系列的異常情況。一旦遇到這些 情況,就會(huì)導(dǎo)致我們的分布式事務(wù)執(zhí)行過程出現(xiàn)異常,最常見的主要是空回滾、冪等、懸掛。因此,在TCC接口設(shè)計(jì)中還需要處理好這三個(gè)問題。
Cancel接口設(shè)計(jì)時(shí)需要允許空回滾。在Try接口因?yàn)閬G包時(shí)沒有收到,事務(wù)管理器會(huì)觸發(fā)回滾,這時(shí)會(huì)觸發(fā)Cancel接口,這時(shí)Cancel執(zhí)行時(shí)發(fā)現(xiàn)沒有對(duì)應(yīng)的事務(wù) XID或主鍵時(shí),需要返回回滾成功。讓事務(wù)服務(wù)管理器認(rèn)為已回滾,否則會(huì)不斷重試,而Cancel又沒有對(duì)應(yīng)的業(yè)務(wù)數(shù)據(jù)可以進(jìn)行回滾。
冪等性的意思是對(duì)同一個(gè)系統(tǒng)使用同樣的條件,一次請(qǐng)求和重復(fù)的多次請(qǐng)求對(duì)系統(tǒng)資源的影響是一致的。因?yàn)榫W(wǎng)絡(luò)抖動(dòng)或擁堵可能會(huì)超時(shí),事務(wù)管理器會(huì)對(duì)資源進(jìn)行重試操作,所以很可能一個(gè)業(yè)務(wù)操作會(huì)被重復(fù)調(diào)用,為了不因?yàn)橹貜?fù)調(diào)用而多次占用資源,需要對(duì)服務(wù)設(shè)計(jì)時(shí)進(jìn)行冪等控制,通常我們可以用事務(wù)XID或業(yè)務(wù)主鍵判重來控制。
懸掛的意思是Cancel比Try接口先執(zhí)行,出現(xiàn)的原因是Try由于網(wǎng)絡(luò)擁堵而超時(shí),事務(wù)管理器生成回滾,觸發(fā)Cancel接口,而最終又收到了Try接口調(diào)用,但是Cancel比Try先到。按照前面允許空回滾的邏輯,回滾會(huì)返回成功,事務(wù)管理器認(rèn)為事務(wù)已回滾成功,則此時(shí)的Try接口不應(yīng)該執(zhí)行,否則會(huì)產(chǎn)生數(shù)據(jù)不一致,所以我們?cè)贑ancel空回滾返回成功之前先記錄該條事務(wù) XID或業(yè)務(wù)主鍵,標(biāo)識(shí)這條記錄已經(jīng)回滾過,Try接口先檢查這條事務(wù)XID或業(yè)務(wù)主鍵如果已經(jīng)標(biāo)記為回滾成功過,則不執(zhí)行Try的業(yè)務(wù)操作。
總結(jié)
XA兩階段提交是資源層面的,而TCC實(shí)際上把資源層面二階段提交上提到了業(yè)務(wù)層面來實(shí)現(xiàn),有效了的避免了XA兩階段提交占用資源鎖時(shí)間過長(zhǎng)導(dǎo)致的性能低下問題。TCC也沒有AT模式中的全局行鎖,所以性能也會(huì)比AT模式高很多。不過,TCC模式對(duì)業(yè)務(wù)代碼有很大的侵入性,主業(yè)務(wù)服務(wù)和從業(yè)務(wù)服務(wù)都需要進(jìn)行改造,從業(yè)務(wù)方改造成本更高。
Saga
Saga 算法(https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf)于 1987 年提出,是一種異步的分布式事務(wù)解決方案。其理論基礎(chǔ)在于,其假設(shè)所有事件按照順序推進(jìn),總能達(dá)到系統(tǒng)的最終一致性,因此 Saga需要服務(wù)分別定義提交接口以及補(bǔ)償接口,當(dāng)某個(gè)事務(wù)分支失敗時(shí),調(diào)用其它的分支的補(bǔ)償接口來進(jìn)行回滾。
在Saga模式下,分布式事務(wù)內(nèi)有多個(gè)參與者,每一個(gè)參與者都是一個(gè)沖正補(bǔ)償服務(wù),需要用戶根據(jù)業(yè)務(wù)場(chǎng)景實(shí)現(xiàn)其正向操作和逆向回滾操作。
分布式事務(wù)執(zhí)行過程中,依次執(zhí)行各參與者的正向操作,如果所有正向操作均執(zhí)行成功,那么分布式事務(wù)提交。如果任何一個(gè)正向操作執(zhí)行失敗,那么分布式事務(wù)會(huì)去退回去執(zhí)行前面各參與者的逆向回滾操作,回滾已提交的參與者,使分布式事務(wù)回到初始狀態(tài)。
Saga模式下分布式事務(wù)通常是由事件驅(qū)動(dòng)的,各個(gè)參與者之間是異步執(zhí)行的,Saga 模式是一種長(zhǎng)事務(wù)解決方案。
Saga模式不保證事務(wù)的隔離性,在極端情況下可能出現(xiàn)臟寫。比如在分布式事務(wù)未提交的情況下,前一個(gè)服務(wù)的數(shù)據(jù)被修改了,而后面的服務(wù)發(fā)生了異常需要進(jìn)行回滾,可能由于前面服務(wù)的數(shù)據(jù)被修改后無法進(jìn)行補(bǔ)償操作。一種處理辦法可以是“重試”繼續(xù)往前完成這個(gè)分布式事務(wù)。由于整個(gè)業(yè)務(wù)流程是由狀態(tài)機(jī)編排的,即使是事后恢復(fù)也可以繼續(xù)往前重試。所以用戶可以根據(jù)業(yè)務(wù)特點(diǎn)配置該流程的事務(wù)處理策略是優(yōu)先“回滾”還是“重試”,當(dāng)事務(wù)超時(shí)的時(shí)候,服務(wù)端會(huì)根據(jù)這個(gè)策略不斷進(jìn)行重試。
由于Saga不保證隔離性,所以我們?cè)跇I(yè)務(wù)設(shè)計(jì)的時(shí)候需要做到“寧可長(zhǎng)款,不可短款”的原則,長(zhǎng)款是指在出現(xiàn)差錯(cuò)的時(shí)候站在我方的角度錢多了的情況,錢少了則是短款,因?yàn)槿绻L(zhǎng)款可以給客戶退款,而短款則可能錢追不回來了,也就是說在業(yè)務(wù)設(shè)計(jì)的時(shí)候,一定是先扣客戶帳再入帳,如果因?yàn)楦綦x性問題造成覆蓋更新,也不會(huì)出現(xiàn)錢少了的情況。
Saga模式適用于業(yè)務(wù)流程長(zhǎng)且需要保證事務(wù)最終一致性的業(yè)務(wù)系統(tǒng),Saga模式一階段就會(huì)提交本地事務(wù),無鎖、長(zhǎng)流程情況下可以保證性能。事務(wù)參與者可能是其它公司的服務(wù)或者是遺留系統(tǒng)的服務(wù),無法進(jìn)行改造和提供TCC要求的接口,也可以使用Saga模式。
Saga模式所具備的優(yōu)勢(shì)有:一階段提交本地?cái)?shù)據(jù)庫(kù)事務(wù),無鎖,高性能;參與者可以采用事務(wù)驅(qū)動(dòng)異步執(zhí)行,高吞吐;補(bǔ)償服務(wù)即正向服務(wù)的“反向”,易于理解、易于實(shí)現(xiàn);不過,Saga 模式由于一階段已經(jīng)提交本地?cái)?shù)據(jù)庫(kù)事務(wù),且沒有進(jìn)行“預(yù)留”動(dòng)作,所以不能保證隔離性。
一個(gè)好的分布式事務(wù)應(yīng)用應(yīng)該盡可能滿足:
-
提高易用性、即降低業(yè)務(wù)改造成本。
-
性能損耗低。
-
隔離性保證完整。但如同CAP,這三個(gè)特性是相互制衡的,往往只能滿足其中兩個(gè),我們可以搭配AT、TCC和Saga來畫一個(gè)三角約束:
本地消息表
本地消息表最初是由eBay架構(gòu)師Dan Pritchett在一篇解釋 BASE 原理的論文《Base:An Acid Alternative》(https://queue.acm.org/detail.cfm?id=1394128)中提及的,業(yè)界目前使用這種方案是比較多的,其核心思想是將分布式事務(wù)拆分成本地事務(wù)進(jìn)行處理。
方案通過在事務(wù)主動(dòng)發(fā)起方額外新建事務(wù)消息表,事務(wù)發(fā)起方處理業(yè)務(wù)和記錄事務(wù)消息在本地事務(wù)中完成,輪詢事務(wù)消息表的數(shù)據(jù)發(fā)送事務(wù)消息,事務(wù)被動(dòng)方基于消息中間件消費(fèi)事務(wù)消息表中的事務(wù)。
下面把分布式事務(wù)最先開始處理的事務(wù)方稱為事務(wù)主動(dòng)方,在事務(wù)主動(dòng)方之后處理的業(yè)務(wù)內(nèi)的其他事務(wù)稱為事務(wù)被動(dòng)方。事務(wù)的主動(dòng)方需要額外新建事務(wù)消息表,用于記錄分布式事務(wù)的消息的發(fā)生、處理狀態(tài)。
參考上圖,我們不妨來聊一聊本地消息表的事務(wù)處理流程。
事務(wù)主動(dòng)方處理好相關(guān)的業(yè)務(wù)邏輯之后,先將業(yè)務(wù)數(shù)據(jù)寫入數(shù)據(jù)庫(kù)中的業(yè)務(wù)表(圖中步驟1),然后將所要發(fā)送的消息寫入到數(shù)據(jù)庫(kù)中的消息表(步驟2)。注意:寫入業(yè)務(wù)表的邏輯和寫入消息表的邏輯在同一個(gè)事務(wù)中,這樣通過本地事務(wù)保證了一致性。
之后,事務(wù)主動(dòng)方將所要發(fā)送的消息發(fā)送到消息中間件中(步驟3)。消息在發(fā)送過程中丟失了怎么辦?這里就體現(xiàn)出消息表的用處了。在上一步中,在消息表中記錄的消息狀態(tài)是“發(fā)送中”,事務(wù)主動(dòng)方可以定時(shí)掃描消息表,然后將其中狀態(tài)為“發(fā)送中”的消息重新投遞到消息中間件即可。只有當(dāng)最后事務(wù)被動(dòng)方消費(fèi)完之后,消息的狀態(tài)才會(huì)被設(shè)置為“已完成”。
重新投遞的過程中也可能會(huì)再次失敗,此時(shí)我們一般會(huì)指定最大重試次數(shù),重試間隔時(shí)間根據(jù)重試次數(shù)而指數(shù)或者線性增長(zhǎng)。若達(dá)到最大重試次數(shù)后記錄日志,我們可以根據(jù)記錄的日志來通過郵件或短信來發(fā)送告警通知,接收到告警通知后及時(shí)介入人工處理即可。
前面3個(gè)步驟可以避免“業(yè)務(wù)處理成功,消息發(fā)送失敗”或者“消息發(fā)送成功,業(yè)務(wù)處理失敗”這種棘手情況的出現(xiàn),并且也可以保證消息不會(huì)丟失。
事務(wù)被動(dòng)方監(jiān)聽并消費(fèi)消息中間件中的消息(步驟4),然后處理相應(yīng)的業(yè)務(wù)邏輯,并把業(yè)務(wù)數(shù)據(jù)寫入到自己的業(yè)務(wù)表中(步驟5),隨后將處理結(jié)果返回給消息中間件(步驟6)。
步驟4-6中可能會(huì)出現(xiàn)各種異常情況,事務(wù)被動(dòng)方可以在處理完步驟6之后再向消息中間件ACK在步驟4中讀取的消息。這樣,如果步驟4-6中間出現(xiàn)任何異常了都可以重試消費(fèi)消息中間件中的那條消息。這里不可避免的會(huì)出現(xiàn)重復(fù)消費(fèi)的現(xiàn)象,并且在前面的步驟3中也會(huì)出現(xiàn)重復(fù)投遞的現(xiàn)象,因此事務(wù)被動(dòng)方的業(yè)務(wù)邏輯需要能夠保證冪等性。
最后事務(wù)主動(dòng)方也會(huì)監(jiān)聽并讀取消息中間件中的消息(步驟7)來更新消息表中消息的狀態(tài)(步驟8)。
步驟6和步驟7是為了將事務(wù)被動(dòng)方的處理結(jié)果反饋給事務(wù)主動(dòng)方,這里也可以使用RPC的方式代替。如果在事務(wù)被動(dòng)方處理業(yè)務(wù)邏輯的過程中發(fā)現(xiàn)整個(gè)業(yè)務(wù)流程失敗,那么事務(wù)被動(dòng)方也可以發(fā)送消息(或者RPC)來通知事務(wù)主動(dòng)方進(jìn)行回滾。
基于本地消息表的分布式事務(wù)方案就介紹到這里了,本地消息表的方案的優(yōu)點(diǎn)是建設(shè)成本比較低,其雖然實(shí)現(xiàn)了可靠消息的傳遞確保了分布式事務(wù)的最終一致性,其實(shí)它也有一些缺陷:
本地消息表與業(yè)務(wù)耦合在一起,難以做成通用性,不可獨(dú)立伸縮。
本地消息表是基于數(shù)據(jù)庫(kù)來做的,而數(shù)據(jù)庫(kù)是要讀寫磁盤IO的,因此在高并發(fā)下是有性能瓶頸的。
(歡迎關(guān)注公眾號(hào):朱小廝的博客)
消息事務(wù)
消息事務(wù)作為一種異步確保型事務(wù),其核心原理是將兩個(gè)事務(wù)通過消息中間件進(jìn)行異步解耦。
消息事務(wù)的一種實(shí)現(xiàn)思路是通過保證多條消息的同時(shí)可見性來保證事務(wù)一致性。但是此類消息事務(wù)實(shí)現(xiàn)機(jī)制更多的是用在 consume-transform-produce(Kafka支持)場(chǎng)景中,其本質(zhì)上還是用來保證消息自身事務(wù),并沒有把外部事務(wù)包含進(jìn)來。
還有一種思路是依賴于 AMQP 協(xié)議(RabbitMQ支持)來確保消息發(fā)送成功。AMQP需要在發(fā)送事務(wù)消息時(shí)進(jìn)行兩階段提交,首先進(jìn)行 tx_select 開啟事務(wù),然后再進(jìn)行消息發(fā)送,最后執(zhí)行 tx_commit 或tx_rollback。這個(gè)過程可以保證在消息發(fā)送成功的同時(shí),本地事務(wù)也一定成功執(zhí)行。但事務(wù)粒度不好控制,而且會(huì)導(dǎo)致性能急劇下降,同時(shí)也無法解決本地事務(wù)執(zhí)行與消息發(fā)送的原子性問題。
不過,RocketMQ事務(wù)消息設(shè)計(jì)解決了上述的本地事務(wù)執(zhí)行與消息發(fā)送的原子性問題。在RocketMQ的設(shè)計(jì)中,broker和producer的雙向通信能力使得broker天生可以作為一個(gè)事務(wù)協(xié)調(diào)者存在。而RocketMQ本身提供的存儲(chǔ)機(jī)制,則為事務(wù)消息提供了持久化能力。RocketMQ 的高可用機(jī)制以及可靠消息設(shè)計(jì),則為事務(wù)消息在系統(tǒng)在發(fā)生異常時(shí),依然能夠保證事務(wù)的最終一致性達(dá)成。
RocketMQ 事務(wù)消息的設(shè)計(jì)流程同樣借鑒了兩階段提交理論,整體交互流程如下圖所示:
下面我們來了解一下這個(gè)設(shè)計(jì)的整體流程。
首先,事務(wù)發(fā)起方發(fā)送一個(gè)Prepare消息到MQ Server中(對(duì)應(yīng)于上圖中Step 1和Step 2),如果這個(gè)Prepare消息發(fā)送失敗,那么就直接取消操作,后續(xù)的操作也都不再執(zhí)行。如果這個(gè)Prepare消息發(fā)送成功了,那么接著執(zhí)行自身的本地事務(wù)(Step 3)。
如果本地事務(wù)執(zhí)行失敗,那么通知MQ Server回滾(Step 4 - Rollback),后續(xù)操作都不再執(zhí)行。如果本地事務(wù)執(zhí)行成功,就通知MQ Server發(fā)送確認(rèn)消息(Step 4 - Commit)。
倘若 Step 4中的Commit/Rollback消息遲遲未送達(dá)到MQ Server中呢?MQ Server會(huì)自動(dòng)定時(shí)輪詢所有的 Prepare 消息,然后調(diào)用事務(wù)發(fā)起方事先提供的接口(Step 5),通過這個(gè)接口反查事務(wù)發(fā)起方的上次本地事務(wù)是否執(zhí)行成功(Step 6)。
如果成功,就發(fā)送確認(rèn)消息給 MQ Server;失敗則告訴 MQ Server回滾消息(Step 7)。
事務(wù)被動(dòng)方會(huì)接收到確認(rèn)消息,然后執(zhí)行本地的事務(wù),如果本地事務(wù)執(zhí)行成功則事務(wù)正常完成。如果事務(wù)被動(dòng)方本地事務(wù)執(zhí)行失敗了咋辦?基于 MQ 來進(jìn)行不斷重試,如果實(shí)在是不行,可以發(fā)送報(bào)警由人工來手工回滾和補(bǔ)償。
上圖是采用本地消息表方案和采用RocketMQ事務(wù)消息方案的對(duì)比圖,其實(shí),我們不難發(fā)現(xiàn)RocketMQ的這種事務(wù)方案就是對(duì)本地消息表的封裝,其MQ內(nèi)部實(shí)現(xiàn)了本地消息表的功能,其他方面的協(xié)議基本與本地消息表一致。
RocketMQ 事務(wù)消息較好的解決了事務(wù)的最終一致性問題,事務(wù)發(fā)起方僅需要關(guān)注本地事務(wù)執(zhí)行以及實(shí)現(xiàn)回查接口給出事務(wù)狀態(tài)判定等實(shí)現(xiàn),而且在上游事務(wù)峰值高時(shí),可以通過消息隊(duì)列,避免對(duì)下游服務(wù)產(chǎn)生過大壓力。
事務(wù)消息不僅適用于上游事務(wù)對(duì)下游事務(wù)無依賴的場(chǎng)景,還可以與一些傳統(tǒng)分布式事務(wù)架構(gòu)相結(jié)合,而 MQ 的服務(wù)端作為天生的具有高可用能力的協(xié)調(diào)者,使得我們未來可以基于MQ提供一站式輕量級(jí)分布式事務(wù)解決方案,用以滿足各種場(chǎng)景下的分布式事務(wù)需求。
最大努力通知
最大努力通知型(Best-effort Delivery)是最簡(jiǎn)單的一種柔性事務(wù),適用于一些最終一致性時(shí)間敏感度低的業(yè)務(wù),且被動(dòng)方處理結(jié)果不影響主動(dòng)方的處理結(jié)果。典型的使用場(chǎng)景:如支付通知、短信通知等。
以支付通知為例,業(yè)務(wù)系統(tǒng)調(diào)用支付平臺(tái)進(jìn)行支付,支付平臺(tái)進(jìn)行支付,進(jìn)行操作支付之后支付平臺(tái)會(huì)盡量去通知業(yè)務(wù)系統(tǒng)支付操作是否成功,但是會(huì)有一個(gè)最大通知次數(shù)。如果超過這個(gè)次數(shù)后還是通知失敗,就不再通知,業(yè)務(wù)系統(tǒng)自行調(diào)用支付平臺(tái)提供一個(gè)查詢接口,供業(yè)務(wù)系統(tǒng)進(jìn)行查詢支付操作是否成功。
最大努力通知方案可以借助MQ(消息中間件)來實(shí)現(xiàn),參考下圖。
發(fā)起通知方將通知發(fā)給MQ,接收通知方監(jiān)聽 MQ 消息。接收通知方收到消息后,處理完業(yè)務(wù)回應(yīng)ACK。接收通知方若沒有回應(yīng)ACK,則 MQ 會(huì)間隔 1min、5min、10min 等重復(fù)通知。接受通知方可調(diào)用消息校對(duì)接口,保證消息的一致性。
分布式事務(wù)的取舍
嚴(yán)格的ACID事務(wù)對(duì)隔離性的要求很高,在事務(wù)執(zhí)行中必須將所有的資源鎖定,對(duì)于長(zhǎng)事務(wù)來說,整個(gè)事務(wù)期間對(duì)數(shù)據(jù)的獨(dú)占,將嚴(yán)重影響系統(tǒng)并發(fā)性能。因此,在高并發(fā)場(chǎng)景中,對(duì)ACID的部分特性進(jìn)行放松從而提高性能,這便產(chǎn)生了BASE柔性事務(wù)。柔性事務(wù)的理念則是通過業(yè)務(wù)邏輯將互斥鎖操作從資源層面上移至業(yè)務(wù)層面。通過放寬對(duì)強(qiáng)一致性要求,來換取系統(tǒng)吞吐量的提升。另外提供自動(dòng)的異常恢復(fù)機(jī)制,可以在發(fā)生異常后也能確保事務(wù)的最終一致。
柔性事務(wù)需要應(yīng)用層進(jìn)行參與,因此這類分布式事務(wù)框架一個(gè)首要的功能就是怎么最大程度降低業(yè)務(wù)改造成本,然后就是盡可能提高性能(響應(yīng)時(shí)間、吞吐),最好是保證隔離性。
當(dāng)然如果我們要自己設(shè)計(jì)一個(gè)分布式事務(wù)框架,還需要考慮很多其它特性,在明確目標(biāo)場(chǎng)景偏好后進(jìn)行權(quán)衡取舍,這些特性包括但不限于以下:
-
業(yè)務(wù)侵入性(基于注解、XML,補(bǔ)償邏輯);
-
隔離性(寫隔離/讀隔離/讀未提交,業(yè)務(wù)隔離/技術(shù)隔離);
-
TM/TC部署形態(tài)(單獨(dú)部署、與應(yīng)用部署一起);
-
錯(cuò)誤恢復(fù)(自動(dòng)恢復(fù)、手動(dòng)恢復(fù));
-
性能(回滾的概率、付出的代價(jià),響應(yīng)時(shí)間、吞吐);
-
高可用(注冊(cè)中心、數(shù)據(jù)庫(kù));
-
持久化(數(shù)據(jù)庫(kù)、文件、多副本一致算法);
-
同步/異步(2PC執(zhí)行方式);
-
日志清理(自動(dòng)、手動(dòng));
-
......
分布式事務(wù)一直是業(yè)界難題,難在于CAP定理,在于分布式系統(tǒng)8大錯(cuò)誤假設(shè) ,在于FLP不可能原理 ,在于我們習(xí)慣于單機(jī)事務(wù)ACID做對(duì)比。無論是數(shù)據(jù)庫(kù)領(lǐng)域XA,還是微服務(wù)下AT、TCC、Saga、本地消息表、事務(wù)消息、最大努力通知等方案,都沒有完美解決分布式事務(wù)問題,它們不過是各自在性能、一致性、可用性等方面做取舍,尋求某些場(chǎng)景偏好下的權(quán)衡。
歡迎跳轉(zhuǎn)到本文的原文鏈接:https://honeypps.com/architect/introduction-of-distributed-transaction/
想知道更多?掃描下面的二維碼關(guān)注我
后臺(tái)回復(fù)”加群“獲取公眾號(hào)專屬群聊入口
當(dāng)當(dāng)優(yōu)惠碼福利來一波!當(dāng)當(dāng)全場(chǎng)自營(yíng)圖書5折,用優(yōu)惠碼:V54PW7(長(zhǎng)按復(fù)制),滿200(原價(jià)400)再減30,相當(dāng)于170=400,四折多一點(diǎn)。使用渠道:當(dāng)當(dāng)小程序或當(dāng)當(dāng)APP。使用時(shí)間:4/10-4/23。
【精彩推薦】
-
Paxos、Raft不是一致性算法嘛?
-
越說越迷糊的CAP
-
面試官居然問我Raft為什么會(huì)叫做Raft!
-
面試官給我挖坑:URI中的//有什么用
-
網(wǎng)關(guān)Zuul科普
-
網(wǎng)關(guān)Spring Cloud?Gateway科普
-
分布式事務(wù)科普——初識(shí)篇
>>>?字節(jié)跳動(dòng)社招內(nèi)推入口?<<<
>>> 字節(jié)跳動(dòng)校招內(nèi)推入口 <<<
朕已閱?
總結(jié)
以上是生活随笔為你收集整理的原创 | 分布式事务科普(终结篇)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分布式事务科普(初识篇)
- 下一篇: 面试官给我挖坑:a[i][j] 和 a[