前言
在上一篇文章《教你一手如何基于RocketMQ搭建生產(chǎn)級(jí)消息集群》中給大家介紹了基于RocketMQ如何搭建生產(chǎn)級(jí)消息集群。因?yàn)?strong>本系列文章最終的目的是介紹基于RocketMQ的事物消息來(lái)解決分布式系統(tǒng)中的數(shù)據(jù)一致性問(wèn)題,所以先給大家率先介紹了RocketMQ消息集群的搭建。
原本是想著在這篇文章中直接介紹RocketMQ的事務(wù)消息特性,但是在梳理的過(guò)程中作者發(fā)現(xiàn)對(duì)于分布式事務(wù)的概念,可能還會(huì)有很多同學(xué)不理解或者理解得不是很深刻的地方,而跳過(guò)這些基本概念直接去學(xué)習(xí)上層的實(shí)踐可能并不是一件很好的事情,因此在這篇文章中,作者打算重點(diǎn)給大家先介紹下分布式事務(wù)相關(guān)的基本概念,諸如分布式事務(wù)、2PC、3PC、TCC之類(lèi)的基本問(wèn)題,之后再單獨(dú)去介紹RocketMQ事務(wù)消息相關(guān)的實(shí)踐。
數(shù)據(jù)庫(kù)事務(wù)的概念
在講述分布式事務(wù)的概念之前,我們先來(lái)回顧下事務(wù)相關(guān)的一些概念。
事務(wù)的基本概念:
就是一個(gè)程序執(zhí)行單元,里面的操作要么全部執(zhí)行成功,要么全部執(zhí)行失敗,不允許只成功一半另外一半執(zhí)行失敗的事情發(fā)生。例如一段事務(wù)代碼做了兩次數(shù)據(jù)庫(kù)更新操作,那么這兩次數(shù)據(jù)庫(kù)操作要么全部執(zhí)行成功,要么全部回滾。
事務(wù)的基本特性:
我們知道事務(wù)有4個(gè)非常重要的特性,即我們常說(shuō)的(ACID)。
Atomicity(原子性):是說(shuō)事務(wù)是一個(gè)不可分割的整體,所有操作要么全做,要么全不做;只要事務(wù)中有一個(gè)操作出錯(cuò),回滾到事務(wù)開(kāi)始前的狀態(tài)的話,那么之前已經(jīng)執(zhí)行的所有操作都是無(wú)效的,都應(yīng)該回滾到開(kāi)始前的狀態(tài)。
Consistency(一致性):是說(shuō)事務(wù)執(zhí)行前后,數(shù)據(jù)從一個(gè)狀態(tài)到另一個(gè)狀態(tài)必須是一致的,比如A向B轉(zhuǎn)賬( A、B的總金額就是一個(gè)一致性狀態(tài)),不可能出現(xiàn)A扣了錢(qián),B卻沒(méi)收到的情況發(fā)生。
Isolation(隔離性): 多個(gè)并發(fā)事務(wù)之間相互隔離,不能互相干擾。關(guān)于事務(wù)的隔離性,可能不是特別好理解,這里的并發(fā)事務(wù)是指兩個(gè)事務(wù)操作了同一份數(shù)據(jù)的情況;而對(duì)于并發(fā)事務(wù)操作同一份數(shù)據(jù)的隔離性問(wèn)題,則是要求不能出現(xiàn)臟讀、幻讀的情況,即事務(wù)A不能讀取事務(wù)B還沒(méi)有提交的數(shù)據(jù),或者在事務(wù)A讀取數(shù)據(jù)進(jìn)行更新操作時(shí),不允許事務(wù)B率先更新掉這條數(shù)據(jù)。而為了解決這個(gè)問(wèn)題,常用的手段就是加鎖了,對(duì)于數(shù)據(jù)庫(kù)來(lái)說(shuō)就是通過(guò)數(shù)據(jù)庫(kù)的相關(guān)鎖機(jī)制來(lái)保證。
Durablity(持久性):事務(wù)完成后,對(duì)數(shù)據(jù)庫(kù)的更改是永久保存的,不能回滾。
關(guān)于數(shù)據(jù)庫(kù)事務(wù)的基本概念大家可以去網(wǎng)上搜一下,這里只是給大家回顧下事務(wù)的基本概念及特性,諸如事務(wù)并發(fā)問(wèn)題、事務(wù)隔離級(jí)別等大家如有遺忘可以去回顧下(tips:面試經(jīng)常會(huì)問(wèn)到的問(wèn)題哦)。
什么是分布式事務(wù)
以上內(nèi)容我們回顧了下事務(wù)的基本概念,那么分布式事務(wù)又是個(gè)什么概念呢?它與數(shù)據(jù)庫(kù)事務(wù)之間又有什么區(qū)別呢?
其實(shí)分布式事務(wù)從實(shí)質(zhì)上看與數(shù)據(jù)庫(kù)事務(wù)的概念是一致的,既然是事務(wù)也就需要滿足事務(wù)的基本特性(ACID),只是分布式事務(wù)相對(duì)于本地事務(wù)而言其表現(xiàn)形式有很大的不同。舉個(gè)例子,在一個(gè)JVM進(jìn)程中如果需要同時(shí)操作數(shù)據(jù)庫(kù)的多條記錄,而這些操作需要在一個(gè)事務(wù)中,那么我們可以通過(guò)數(shù)據(jù)庫(kù)提供的事務(wù)機(jī)制(一般是數(shù)據(jù)庫(kù)鎖)來(lái)實(shí)現(xiàn)。
而隨著這個(gè)JVM進(jìn)程(應(yīng)用)被拆分成了微服務(wù)架構(gòu),原本一個(gè)本地邏輯執(zhí)行單元被拆分到了多個(gè)獨(dú)立的微服務(wù)中,這些微服務(wù)又分別操作不同的數(shù)據(jù)庫(kù)和表,服務(wù)之間通過(guò)網(wǎng)絡(luò)調(diào)用。
舉個(gè)例子:服務(wù)A收到一筆購(gòu)物下單請(qǐng)求后,需要調(diào)用服務(wù)B去支付,支付成功則處理購(gòu)物訂單為待發(fā)貨狀態(tài),否則就需要將購(gòu)物訂單處理為失敗狀態(tài)。(如圖所示)

在上面這個(gè)例子中會(huì)不會(huì)出現(xiàn)服務(wù)B支付成功了,但是由于網(wǎng)絡(luò)調(diào)用的問(wèn)題沒(méi)有通知到服務(wù)A,導(dǎo)致用戶付了錢(qián),但是購(gòu)物訂單無(wú)法顯示支付成功的狀態(tài)呢?
答案是這種情況是普遍存在的,因?yàn)榉?wù)B在處理成功后需要向服務(wù)A發(fā)送網(wǎng)絡(luò)請(qǐng)求,而這個(gè)過(guò)程是極有可能失敗的。那么如何確保“服務(wù)A->服務(wù)B”這個(gè)過(guò)程能夠組成一個(gè)事務(wù),要么全部成功、要么全部失敗呢?而這就是典型的需要通過(guò)分布式事務(wù)解決的問(wèn)題。
分布式事務(wù)是為了解決微服務(wù)架構(gòu)(形式都是分布式系統(tǒng))中不同節(jié)點(diǎn)之間的數(shù)據(jù)一致性問(wèn)題。這個(gè)一致性問(wèn)題本質(zhì)上解決的也是傳統(tǒng)事務(wù)需要解決的問(wèn)題,即一個(gè)請(qǐng)求在多個(gè)微服務(wù)調(diào)用鏈中,所有服務(wù)的數(shù)據(jù)處理要么全部成功,要么全部回滾。當(dāng)然分布式事務(wù)問(wèn)題的形式可能與傳統(tǒng)事務(wù)會(huì)有比較大的差異,但是問(wèn)題本質(zhì)是一致的,都是要求解決數(shù)據(jù)的一致性問(wèn)題。
而分布式事務(wù)的實(shí)現(xiàn)方式有很多種,最具有代表性的是由Oracle Tuxedo系統(tǒng)提出的XA分布式事務(wù)協(xié)議。XA協(xié)議包括兩階段提交(2PC)和三階段提交(3PC)兩種實(shí)現(xiàn),接下來(lái)我們分別來(lái)介紹下這兩種實(shí)現(xiàn)方式的原理。
兩階段提交(2PC)
兩階段提交又稱2PC(two-phase commit protocol),2pc是一個(gè)非常經(jīng)典的強(qiáng)一致、中心化的原子提交協(xié)議。這里所說(shuō)的中心化是指協(xié)議中有兩類(lèi)節(jié)點(diǎn):一個(gè)是中心化協(xié)調(diào)者節(jié)點(diǎn)(coordinator)和N個(gè)參與者節(jié)點(diǎn)(partcipant)。
下面我們就以一個(gè)盡量貼近實(shí)際業(yè)務(wù)場(chǎng)景的操作來(lái)舉例:"假設(shè)在一個(gè)分布式架構(gòu)的系統(tǒng)中事務(wù)的發(fā)起者通過(guò)分布式事務(wù)協(xié)調(diào)者(如RocketMQ,在早期RocketMQ版本不提供事務(wù)消息特性時(shí),有些公司會(huì)自己研發(fā)一個(gè)基于MQ的可靠消息服務(wù)來(lái)實(shí)現(xiàn)一定的分布式事務(wù)的特性)分別向應(yīng)用服務(wù)A、應(yīng)用服務(wù)B發(fā)起處理請(qǐng)求,二者在處理的過(guò)程中會(huì)分別操作自身服務(wù)的數(shù)據(jù)庫(kù),現(xiàn)在要求應(yīng)用服務(wù)A、應(yīng)用服務(wù)B的數(shù)據(jù)處理操作要在一個(gè)事務(wù)里"?
在上面這個(gè)例子中如果采用兩階段提交來(lái)實(shí)現(xiàn)分布式事務(wù),那么其運(yùn)行原理應(yīng)該是個(gè)什么樣的呢?(如??):
第一階段:請(qǐng)求/表決階段(點(diǎn)擊放大)

既然稱為兩階段提交,說(shuō)明在這個(gè)過(guò)程中是大致存在兩個(gè)階段的處理流程。第一個(gè)階段如??圖所示,這個(gè)階段被稱之為請(qǐng)求/表決階段。是個(gè)什么意思呢?
就是在分布式事務(wù)的發(fā)起方在向分布式事務(wù)協(xié)調(diào)者(Coordinator)發(fā)送請(qǐng)求時(shí),Coordinator首先會(huì)分別向參與者(Partcipant)節(jié)點(diǎn)A、參與這節(jié)點(diǎn)(Partcipant)節(jié)點(diǎn)B分別發(fā)送事務(wù)預(yù)處理請(qǐng)求,稱之為Prepare,有些資料也叫"Vote Request"。
說(shuō)的直白點(diǎn)就是問(wèn)一下這些參與節(jié)點(diǎn)"這件事你們能不能處理成功了",此時(shí)這些參與者節(jié)點(diǎn)一般來(lái)說(shuō)就會(huì)打開(kāi)本地?cái)?shù)據(jù)庫(kù)事務(wù),然后開(kāi)始執(zhí)行數(shù)據(jù)庫(kù)本地事務(wù),但在執(zhí)行完成后并不會(huì)立馬提交數(shù)據(jù)庫(kù)本地事務(wù),而是先向Coordinator報(bào)告說(shuō):“我這邊可以處理了/我這邊不能處理”。
如果所有的參與這節(jié)點(diǎn)都向協(xié)調(diào)者作了“Vote Commit”的反饋的話,那么此時(shí)流程就會(huì)進(jìn)入第二個(gè)階段了。
第二階段:提交/執(zhí)行階段(正常流程)

如果所有參與者節(jié)點(diǎn)都向協(xié)調(diào)者報(bào)告說(shuō)“我這邊可以處理”,那么此時(shí)協(xié)調(diào)者就會(huì)向所有參與者節(jié)點(diǎn)發(fā)送“全局提交確認(rèn)通知(global_commit)”,即你們都可以進(jìn)行本地事務(wù)提交了,此時(shí)參與者節(jié)點(diǎn)就會(huì)完成自身本地?cái)?shù)據(jù)庫(kù)事務(wù)的提交,并最終將提交結(jié)果回復(fù)“ack”消息給Coordinator,然后Coordinator就會(huì)向調(diào)用方返回分布式事務(wù)處理完成的結(jié)果。
第二階段:提交/執(zhí)行階段(異常流程)

相反,在第二階段除了所有的參與者節(jié)點(diǎn)都反饋“我這邊可以處理了”的情況外,也會(huì)有節(jié)點(diǎn)反饋說(shuō)“我這邊不能處理”的情況發(fā)生,此時(shí)參與者節(jié)點(diǎn)就會(huì)向協(xié)調(diào)者節(jié)點(diǎn)反饋“Vote_Abort”的消息。此時(shí)分布式事務(wù)協(xié)調(diào)者節(jié)點(diǎn)就會(huì)向所有的參與者節(jié)點(diǎn)發(fā)起事務(wù)回滾的消息(“global_rollback”),此時(shí)各個(gè)參與者節(jié)點(diǎn)就會(huì)回滾本地事務(wù),釋放資源,并且向協(xié)調(diào)者節(jié)點(diǎn)發(fā)送“ack”確認(rèn)消息,協(xié)調(diào)者節(jié)點(diǎn)就會(huì)向調(diào)用方返回分布式事務(wù)處理失敗的結(jié)果。
以上就是兩階段提交的基本過(guò)程了,那么按照這個(gè)兩階段提交協(xié)議,分布式系統(tǒng)的數(shù)據(jù)一致性問(wèn)題就能得到滿足嗎?
實(shí)際上分布式事務(wù)是一件非常復(fù)雜的事情,兩階段提交只是通過(guò)增加了事務(wù)協(xié)調(diào)者(Coordinator)的角色來(lái)通過(guò)2個(gè)階段的處理流程來(lái)解決分布式系統(tǒng)中一個(gè)事務(wù)需要跨多個(gè)服務(wù)節(jié)點(diǎn)的數(shù)據(jù)一致性問(wèn)題。但是從異常情況上考慮,這個(gè)流程也并不是那么的無(wú)懈可擊。
假設(shè)如果在第二個(gè)階段中Coordinator在接收到Partcipant的"Vote_Request"后掛掉了或者網(wǎng)絡(luò)出現(xiàn)了異常,那么此時(shí)Partcipant節(jié)點(diǎn)就會(huì)一直處于本地事務(wù)掛起的狀態(tài),從而長(zhǎng)時(shí)間地占用資源。當(dāng)然這種情況只會(huì)出現(xiàn)在極端情況下,然而作為一套健壯的軟件系統(tǒng)而言,異常Case的處理才是真正考驗(yàn)方案正確性的地方。
以下幾點(diǎn)是XA-兩階段提交協(xié)議中會(huì)遇到的一些問(wèn)題:
性能問(wèn)題。從流程上我們可以看得出,其最大缺點(diǎn)就在于它的執(zhí)行過(guò)程中間,節(jié)點(diǎn)都處于阻塞狀態(tài)。各個(gè)操作數(shù)據(jù)庫(kù)的節(jié)點(diǎn)此時(shí)都占用著數(shù)據(jù)庫(kù)資源,只有當(dāng)所有節(jié)點(diǎn)準(zhǔn)備完畢,事務(wù)協(xié)調(diào)者才會(huì)通知進(jìn)行全局提交,參與者進(jìn)行本地事務(wù)提交后才會(huì)釋放資源。這樣的過(guò)程會(huì)比較漫長(zhǎng),對(duì)性能影響比較大。
協(xié)調(diào)者單點(diǎn)故障問(wèn)題。事務(wù)協(xié)調(diào)者是整個(gè)XA模型的核心,一旦事務(wù)協(xié)調(diào)者節(jié)點(diǎn)掛掉,會(huì)導(dǎo)致參與者收不到提交或回滾的通知,從而導(dǎo)致參與者節(jié)點(diǎn)始終處于事務(wù)無(wú)法完成的中間狀態(tài)。
丟失消息導(dǎo)致的數(shù)據(jù)不一致問(wèn)題。在第二個(gè)階段,如果發(fā)生局部網(wǎng)絡(luò)問(wèn)題,一部分事務(wù)參與者收到了提交消息,另一部分事務(wù)參與者沒(méi)收到提交消息,那么就會(huì)導(dǎo)致節(jié)點(diǎn)間數(shù)據(jù)的不一致問(wèn)題。
既然兩階段提交有以上問(wèn)題,那么有沒(méi)有其他的方案來(lái)解決呢?
三階段提交(3PC)
三階段提交又稱3PC,其在兩階段提交的基礎(chǔ)上增加了CanCommit階段,并引入了超時(shí)機(jī)制。一旦事務(wù)參與者遲遲沒(méi)有收到協(xié)調(diào)者的Commit請(qǐng)求,就會(huì)自動(dòng)進(jìn)行本地commit,這樣相對(duì)有效地解決了協(xié)調(diào)者單點(diǎn)故障的問(wèn)題。
但是性能問(wèn)題和不一致問(wèn)題仍然沒(méi)有根本解決。下面我們還是一起看下三階段流程的是什么樣的?
第一階段:CanCommit階段

這個(gè)階段類(lèi)似于2PC中的第二個(gè)階段中的Ready階段,是一種事務(wù)詢問(wèn)操作,事務(wù)的協(xié)調(diào)者向所有參與者詢問(wèn)“你們是否可以完成本次事務(wù)?”,如果參與者節(jié)點(diǎn)認(rèn)為自身可以完成事務(wù)就返回“YES”,否則“NO”。而在實(shí)際的場(chǎng)景中參與者節(jié)點(diǎn)會(huì)對(duì)自身邏輯進(jìn)行事務(wù)嘗試,其實(shí)說(shuō)白了就是檢查下自身狀態(tài)的健康性,看有沒(méi)有能力進(jìn)行事務(wù)操作。
第二階段:PreCommit階段

在階段一中,如果所有的參與者都返回Yes的話,那么就會(huì)進(jìn)入PreCommit階段進(jìn)行事務(wù)預(yù)提交。此時(shí)分布式事務(wù)協(xié)調(diào)者會(huì)向所有的參與者節(jié)點(diǎn)發(fā)送PreCommit請(qǐng)求,參與者收到后開(kāi)始執(zhí)行事務(wù)操作,并將Undo和Redo信息記錄到事務(wù)日志中。參與者執(zhí)行完事務(wù)操作后(此時(shí)屬于未提交事務(wù)的狀態(tài)),就會(huì)向協(xié)調(diào)者反饋“Ack”表示我已經(jīng)準(zhǔn)備好提交了,并等待協(xié)調(diào)者的下一步指令。
否則,如果階段一中有任何一個(gè)參與者節(jié)點(diǎn)返回的結(jié)果是No響應(yīng),或者協(xié)調(diào)者在等待參與者節(jié)點(diǎn)反饋的過(guò)程中超時(shí)(2PC中只有協(xié)調(diào)者可以超時(shí),參與者沒(méi)有超時(shí)機(jī)制)。整個(gè)分布式事務(wù)就會(huì)中斷,協(xié)調(diào)者就會(huì)向所有的參與者發(fā)送“abort”請(qǐng)求。
第三階段:DoCommit階段

在階段二中如果所有的參與者節(jié)點(diǎn)都可以進(jìn)行PreCommit提交,那么協(xié)調(diào)者就會(huì)從“預(yù)提交狀態(tài)”-》“提交狀態(tài)”。然后向所有的參與者節(jié)點(diǎn)發(fā)送"doCommit"請(qǐng)求,參與者節(jié)點(diǎn)在收到提交請(qǐng)求后就會(huì)各自執(zhí)行事務(wù)提交操作,并向協(xié)調(diào)者節(jié)點(diǎn)反饋“Ack”消息,協(xié)調(diào)者收到所有參與者的Ack消息后完成事務(wù)。
相反,如果有一個(gè)參與者節(jié)點(diǎn)未完成PreCommit的反饋或者反饋超時(shí),那么協(xié)調(diào)者都會(huì)向所有的參與者節(jié)點(diǎn)發(fā)送abort請(qǐng)求,從而中斷事務(wù)。
看到這里,你是不是會(huì)疑惑"3PC相對(duì)于2PC而言到底優(yōu)化了什么地方呢?"
相比較2PC而言,3PC對(duì)于協(xié)調(diào)者(Coordinator)和參與者(Partcipant)都設(shè)置了超時(shí)時(shí)間,而2PC只有協(xié)調(diào)者才擁有超時(shí)機(jī)制。這解決了一個(gè)什么問(wèn)題呢?這個(gè)優(yōu)化點(diǎn),主要是避免了參與者在長(zhǎng)時(shí)間無(wú)法與協(xié)調(diào)者節(jié)點(diǎn)通訊(協(xié)調(diào)者掛掉了)的情況下,無(wú)法釋放資源的問(wèn)題,因?yàn)閰⑴c者自身?yè)碛谐瑫r(shí)機(jī)制會(huì)在超時(shí)后,自動(dòng)進(jìn)行本地commit從而進(jìn)行釋放資源。而這種機(jī)制也側(cè)面降低了整個(gè)事務(wù)的阻塞時(shí)間和范圍。
另外,通過(guò)CanCommit、PreCommit、DoCommit三個(gè)階段的設(shè)計(jì),相較于2PC而言,多設(shè)置了一個(gè)緩沖階段保證了在最后提交階段之前各參與節(jié)點(diǎn)的狀態(tài)是一致的。
以上就是3PC相對(duì)于2PC的一個(gè)提高(相對(duì)緩解了2PC中的前兩個(gè)問(wèn)題),但是3PC依然沒(méi)有完全解決數(shù)據(jù)不一致的問(wèn)題。
補(bǔ)償事務(wù)(TCC)
說(shuō)起分布式事務(wù)的概念,不少人都會(huì)搞混淆,似乎好像分布式事務(wù)就是TCC。實(shí)際上TCC與2PC、3PC一樣,只是分布式事務(wù)的一種實(shí)現(xiàn)方案而已。
TCC(Try-Confirm-Cancel)又稱補(bǔ)償事務(wù)。其核心思想是:"針對(duì)每個(gè)操作都要注冊(cè)一個(gè)與其對(duì)應(yīng)的確認(rèn)和補(bǔ)償(撤銷(xiāo)操作)"。它分為三個(gè)操作:
Try階段:主要是對(duì)業(yè)務(wù)系統(tǒng)做檢測(cè)及資源預(yù)留。
Confirm階段:確認(rèn)執(zhí)行業(yè)務(wù)操作。
Cancel階段:取消執(zhí)行業(yè)務(wù)操作。
TCC事務(wù)的處理流程與2PC兩階段提交類(lèi)似,不過(guò)2PC通常都是在跨庫(kù)的DB層面,而TCC本質(zhì)上就是一個(gè)應(yīng)用層面的2PC,需要通過(guò)業(yè)務(wù)邏輯來(lái)實(shí)現(xiàn)。這種分布式事務(wù)的實(shí)現(xiàn)方式的優(yōu)勢(shì)在于,可以讓應(yīng)用自己定義數(shù)據(jù)庫(kù)操作的粒度,使得降低鎖沖突、提高吞吐量成為可能。
而不足之處則在于對(duì)應(yīng)用的侵入性非常強(qiáng),業(yè)務(wù)邏輯的每個(gè)分支都需要實(shí)現(xiàn)try、confirm、cancel三個(gè)操作。此外,其實(shí)現(xiàn)難度也比較大,需要按照網(wǎng)絡(luò)狀態(tài)、系統(tǒng)故障等不同的失敗原因?qū)崿F(xiàn)不同的回滾策略。為了滿足一致性的要求,confirm和cancel接口還必須實(shí)現(xiàn)冪等。
TCC的具體原理圖如??:
