探索分布式事務(wù)解決方案:八種方案解析
探索分布式事務(wù)解決方案:八種方案解析
前面已經(jīng)學(xué)習(xí)了分布式事務(wù)的基礎(chǔ)理論CAP 理論和 BASE 理論,以理論為基礎(chǔ),針對(duì)不同的分布式場(chǎng)景業(yè)界常見的解決方案有2PC、TCC、可靠消息最終一致性、最大努力通知等方案,**以下 總結(jié)8 種常見的解決方案,取名 八奇技**。幫助大家在實(shí)際的分布式系統(tǒng)中更好地運(yùn)用事務(wù)。。
1.2PC
二階段提交協(xié)議(Two-phase commit protocol),簡(jiǎn)稱 2PC。2PC是將整個(gè)事務(wù)流程分為兩個(gè)階段:
- 1.準(zhǔn)備階段(Prepare phase)
- 2.提交階段(commitphase)
2是指兩個(gè)階段,P是指準(zhǔn)備階段,C是指提交階段
在計(jì)算機(jī)中部分關(guān)系數(shù)據(jù)庫(kù)如Oracle、MySQL支持兩階段提交協(xié)議,如下圖:
- 準(zhǔn)備階段(Prepare phase):事務(wù)管理器給每個(gè)參與者發(fā)送Prepare消息,每個(gè)數(shù)據(jù)庫(kù)參與者在本地執(zhí)行事務(wù),并寫本地的Undo/Redo日志,此時(shí)事務(wù)沒有提交。(Undo日志是記錄修改前的數(shù)據(jù),用于數(shù)據(jù)庫(kù)回滾,Redo日志是記錄修改后的數(shù)據(jù),用于提交事務(wù)后寫入據(jù)文件)
- 提交階段(commit phase):如果事務(wù)管理器收到了參與者的執(zhí)行失敗或者超時(shí)消息時(shí),直接給每個(gè)參與者發(fā)送回滾(Rollback)消息;否則,發(fā)送提交(Commit)消息;參與者根據(jù)事務(wù)管理器的指令執(zhí)行提交或者回滾操作,并釋放事務(wù)處理過程中使用的鎖資源。
注意:必須在最后階段釋放鎖資源
下圖展示了2PC的兩個(gè)階段,分成功和失敗兩個(gè)情況說(shuō)明:
- 成功情況:
圖片
- 異常情況:
圖片
2PC優(yōu)缺點(diǎn):
優(yōu)點(diǎn)
- 簡(jiǎn)單直觀:邏輯清晰,易于理解和實(shí)現(xiàn)。
- 原子性保證:能夠保證跨多個(gè)分布式節(jié)點(diǎn)的事務(wù)的原子性。
缺點(diǎn):
- 同步阻塞:因?yàn)橐浑A段需要鎖定數(shù)據(jù)庫(kù)資源,等待二階段結(jié)束才釋放,性能較差,在高并發(fā)場(chǎng)景下不適用
- 單點(diǎn)故障問題,如果協(xié)調(diào)者在第二階段崩潰,參與者可能會(huì)無(wú)限期地等待指令,因?yàn)樗鼈儾恢缿?yīng)該提交還是回滾。這使得整個(gè)系統(tǒng)容易受到單點(diǎn)故障的影響
- 數(shù)據(jù)不一致問題,如果在第二階段中協(xié)調(diào)者向某些參與者發(fā)送了提交指令,而其他參與者因?yàn)榫W(wǎng)絡(luò)問題沒有收到指令,那么這些沒有收到指令的參與者可能會(huì)選擇回滾,導(dǎo)致數(shù)據(jù)不一致
2.3PC
3PC,即Three-Phase Commit,是一種分布式事務(wù)協(xié)議,用于在分布式系統(tǒng)中確保多個(gè)參與者之間的事務(wù)操作的一致性和可靠性。它是在兩階段提交(2PC)協(xié)議的基礎(chǔ)上發(fā)展而來(lái),解決了2PC協(xié)議可能出現(xiàn)的懸掛事務(wù)問題。
3PC協(xié)議將提交操作分為三個(gè)階段,分別是準(zhǔn)備階段、提交準(zhǔn)備階段和提交階段,每個(gè)階段都有對(duì)應(yīng)的操作和協(xié)議。
準(zhǔn)備階段(CanCommit):
- 協(xié)調(diào)者:向所有參與者發(fā)送CanCommit準(zhǔn)備請(qǐng)求,詢問它們是否可以提交事務(wù)。
- 參與者:執(zhí)行本地事務(wù),檢查是否能夠執(zhí)行,如果可以執(zhí)行則返回可以提交,否則返回不可以提交。
提交準(zhǔn)備階段(PreCommit):
- 協(xié)調(diào)者: 根據(jù)參與者的反饋情況決定是否進(jìn)行提交準(zhǔn)備
- 如果所有參與者都返回“可以提交”,協(xié)調(diào)者向所有參與者發(fā)送提交請(qǐng)求,告知它們可以進(jìn)行提交準(zhǔn)備。
- 如果有任何參與者返回“不可以提交”或者超時(shí)未響應(yīng),則協(xié)調(diào)者向所有參與者發(fā)送中止請(qǐng)求,取消事務(wù)。
提交階段(DoCommit/DoAbort):
- 如果協(xié)調(diào)者 接收到所有參與者的確認(rèn)提交消息,則向所有參與者發(fā)送最終的提交請(qǐng)求,提交事務(wù)。
- 如果協(xié)調(diào)者接收到任何參與者的中止請(qǐng)求,或者在提交準(zhǔn)備階段超時(shí)未收到所有參與者的響應(yīng),則向所有參與者發(fā)送中止請(qǐng)求,取消事務(wù)
3PC協(xié)議相對(duì)于2PC協(xié)議的改進(jìn)在于增加了一個(gè)準(zhǔn)備階段,使得參與者在準(zhǔn)備階段就能夠知道是否可以提交事務(wù),從而避免了懸掛事務(wù)問題。然而,3PC協(xié)議仍然存在著協(xié)調(diào)者單點(diǎn)故障、消息丟失等問題,因此在實(shí)際應(yīng)用中并不常見,一般更多地使用2PC、Saga等分布式事務(wù)解決方案
3.TCC
TCC是Try、Confirm、Cancel三個(gè)詞語(yǔ)的縮寫,TCC要求每個(gè)分支事務(wù)實(shí)現(xiàn)三個(gè)操作:預(yù)處理Try、確認(rèn)Confirm、撤銷Cancel。Try操作業(yè)務(wù)檢查及資源預(yù)留,Confirm做業(yè)務(wù)確認(rèn)操作,Cancel實(shí)現(xiàn)一個(gè)與Try相反的操作即回滾操作。TM首先發(fā)起所有的分支事務(wù)的try操作,任何一個(gè)分支事務(wù)的try操作執(zhí)行失敗,TM將會(huì)發(fā)起所有分支事務(wù)的Cancel操作,若try操作全部成功,TM將會(huì)發(fā)起所有分支事務(wù)的Confirm操作,其中Confirm/Cancel操作若執(zhí)行失敗,TM會(huì)進(jìn)行重試。
- 分支事務(wù)成功情況:
圖片
- 分支事務(wù)失敗的情況:
圖片
TCC分為三個(gè)階段
- Try 階段:是做業(yè)務(wù)檢查(一致性)及資源預(yù)留(隔離),此階段僅是一個(gè)初步操作,它和后續(xù)的Confirm 一起才能真正構(gòu)成一個(gè)完整的業(yè)務(wù)邏輯。
- Confirm 階段:是做確認(rèn)提交,Try階段所有分支事務(wù)執(zhí)行成功后開始執(zhí)行 Confirm。通常情況下,采用TCC則認(rèn)為 Confirm階段是不會(huì)出錯(cuò)的。即:只要Try成功,Confirm一定成功。若Confirm階段真的出錯(cuò)了,需引入重試機(jī)制或人工處理。。
- Cancel 階段:是在業(yè)務(wù)執(zhí)行錯(cuò)誤需要回滾的狀態(tài)下執(zhí)行分支事務(wù)的業(yè)務(wù)取消,預(yù)留資源釋放。通常情況下,采用TCC則認(rèn)為Cancel階段也是一定成功的。若Cancel階段真的出錯(cuò)了,需引入重試機(jī)制或人工處理
TCC需要注意三種異常處理
空回滾
在沒有調(diào)用 TCC 資源 Try 方法的情況下,調(diào)用了二階段的 Cancel 方法,Cancel 方法需要識(shí)別出這是一個(gè)空回滾,然后直接返回成功。
出現(xiàn)原因:是當(dāng)一個(gè)分支事務(wù)所在服務(wù)宕機(jī)或網(wǎng)絡(luò)異常,分支事務(wù)調(diào)用記錄為失敗,這個(gè)時(shí)候其實(shí)是沒有執(zhí)行Try階段,當(dāng)故障恢復(fù)后,分布式事務(wù)進(jìn)行回滾則會(huì)調(diào)用二階段的Cancel方法,從而形成空回滾。
解決思路是
關(guān)鍵就是要識(shí)別出這個(gè)空回滾。思路很簡(jiǎn)單就是需要知道一階段是否執(zhí)行,如果執(zhí)行了,那就是正?;貪L;如果沒執(zhí)行,那就是空回滾。
冪等
TCC二階段提交重試機(jī)制不會(huì)引發(fā)數(shù)據(jù)不一致,要求 TCC 的二階段 Try、Confirm 和 Cancel 接口保證冪等,這樣不會(huì)重復(fù)使用或者釋放資源。如果冪等控制沒有做好,很有可能導(dǎo)致數(shù)據(jù)不一致等嚴(yán)重問題。
解決思路 在上述“分支事務(wù)記錄”中增加執(zhí)行狀態(tài),每次執(zhí)行前都查詢?cè)摖顟B(tài)。
懸掛
懸掛就是對(duì)于一個(gè)分布式事務(wù),其二階段 Cancel 接口比 Try 接口先執(zhí)行。
出現(xiàn)原因: 在 RPC 調(diào)用分支事務(wù)try時(shí),先注冊(cè)分支事務(wù),再執(zhí)行RPC調(diào)用,如果此時(shí) RPC 調(diào)用的網(wǎng)絡(luò)發(fā)生擁堵,通常 RPC 調(diào)用是有超時(shí)時(shí)間的,RPC 超時(shí)以后,TM就會(huì)通知RM回滾該分布式事務(wù),可能回滾完成后,RPC 請(qǐng)求才到達(dá)參與者真正執(zhí)行,而一個(gè) Try 方法預(yù)留的業(yè)務(wù)資源,只有該分布式事務(wù)才能使用,該分布式事務(wù)第一階段預(yù)留的業(yè)務(wù)資源就再也沒有人能夠處理了,對(duì)于這種情況,我們就稱為懸掛,即業(yè)務(wù)資源預(yù)留后沒法繼續(xù)處理。
解決思路:如果二階段執(zhí)行完成,那一階段就不能再繼續(xù)執(zhí)行。在執(zhí)行一階段事務(wù)時(shí)判斷在該全局事務(wù)下,“分支事務(wù)記錄”表中是否已經(jīng)有二階段事務(wù)記錄,如果有則不執(zhí)行Try。
TCC優(yōu)缺點(diǎn):
TCC的優(yōu)點(diǎn):
- 一階段完成直接提交事務(wù),釋放數(shù)據(jù)庫(kù)資源,性能好
- 無(wú)需使用全局鎖,性能最強(qiáng)
- 不依賴數(shù)據(jù)庫(kù)事務(wù),而是依賴補(bǔ)償操作,可以用于非事務(wù)型數(shù)據(jù)庫(kù)
TCC的缺點(diǎn)
- 有代碼侵入,需要人為編寫try、Confirm和Cancel接口,太麻煩
- 軟狀態(tài),事務(wù)是最終一致
- 需要考慮Confirm和Cancel的失敗情況,做好冪等處理
4. 分布式補(bǔ)償事務(wù)(Saga)
Saga是一種長(zhǎng)事務(wù)的解決方案,它將一個(gè)大的分布式事務(wù)拆分成多個(gè)較小的本地事務(wù),并通過異步消息傳遞來(lái)串聯(lián)這些本地事務(wù)。每個(gè)本地事務(wù)執(zhí)行成功后,會(huì)發(fā)送消息觸發(fā)下一個(gè)事務(wù)的執(zhí)行。如果某個(gè)本地事務(wù)失敗,Saga會(huì)執(zhí)行一系列補(bǔ)償操作,保持?jǐn)?shù)據(jù)的一致性。
分布式補(bǔ)償事務(wù)(Saga) 優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 靈活性: 允許每個(gè)小事務(wù)獨(dú)立管理,提高了系統(tǒng)的靈活性。
- 減少資源鎖定: 不需要持續(xù)占用資源,提高了系統(tǒng)的并發(fā)能力。
- 容錯(cuò)性: 通過定義補(bǔ)償操作來(lái)處理失敗,增強(qiáng)了系統(tǒng)的容錯(cuò)能力。
- 適用于微服務(wù)架構(gòu): 可以跨服務(wù)邊界管理事務(wù),每個(gè)服務(wù)都可以獨(dú)立處理自己的事務(wù)和補(bǔ)償邏輯。
缺點(diǎn)
- 復(fù)雜性: 實(shí)現(xiàn)Saga需要定義每個(gè)小事務(wù)的補(bǔ)償操作,增加了系統(tǒng)的復(fù)雜性。
- 數(shù)據(jù)一致性: 不能提供即時(shí)一致性保證,只能保證最終一致性。
- 補(bǔ)償操作的難度: 在某些情況下,補(bǔ)償操作可能很難實(shí)現(xiàn),特別是當(dāng)事務(wù)有副作用時(shí)。
- 測(cè)試和調(diào)試: 涉及多個(gè)服務(wù)和補(bǔ)償邏輯,測(cè)試和調(diào)試可能會(huì)更加困難。
在選擇使用Saga模式時(shí),需要仔細(xì)考慮業(yè)務(wù)場(chǎng)景是否適合最終一致性,以及是否能夠有效地實(shí)現(xiàn)和管理補(bǔ)償邏輯。對(duì)于需要高度一致性保證的場(chǎng)景,可能需要考慮其他事務(wù)管理機(jī)制。Saga模式在適當(dāng)?shù)那闆r下可以為分布式系統(tǒng)帶來(lái)靈活性和容錯(cuò)性,但需要慎重考慮其復(fù)雜性和實(shí)現(xiàn)難度。
5. 可靠消息最終一致性
可靠消息最終一致性方案:是指當(dāng)事務(wù)發(fā)起方執(zhí)行完成本地事務(wù)后并發(fā)出一條消息,事務(wù)參與方(消息消費(fèi)者)一定能夠接收消息并處理事務(wù)成功,此方案強(qiáng)調(diào)的是只要消息發(fā)給事務(wù)參與方最終事務(wù)要達(dá)到一致。
此方案是利用消息中間件完成,如下圖:
圖片
事務(wù)發(fā)起方(消息生產(chǎn)方)將消息發(fā)給消息中間件,事務(wù)參與方從消息中間件接收消息,事務(wù)發(fā)起方和消息中間件之間,事務(wù)參與方(消息消費(fèi)方)和消息中間件之間都是通過網(wǎng)絡(luò)通信,由于網(wǎng)絡(luò)通信的不確定性會(huì)導(dǎo)致分布式事務(wù)問題。
可靠消息最終一致性方案要解決以下幾個(gè)問題
1. 本地事務(wù)與消息發(fā)送的原子性問題
本地事務(wù)與消息發(fā)送的原子性問題即:事務(wù)發(fā)起方在本地事務(wù)執(zhí)行成功后消息必須發(fā)出去,否則就丟棄消息。即實(shí)現(xiàn)本地事務(wù)和消息發(fā)送的原子性,要么都成功,要么都失敗。本地事務(wù)與消息發(fā)送的原子性問題是實(shí)現(xiàn)可靠消息最終一致性方案的關(guān)鍵問題。 先來(lái)嘗試下這種操作,先發(fā)送消息,再操作數(shù)據(jù)庫(kù):
begin transaction;
//1.發(fā)送MQ
//2.數(shù)據(jù)庫(kù)操作
commit transation;
這種情況下無(wú)法保證數(shù)據(jù)庫(kù)操作與發(fā)送消息的一致性,因?yàn)榭赡馨l(fā)送消息成功,數(shù)據(jù)庫(kù)操作失敗立馬想到第二種方案,先進(jìn)行數(shù)據(jù)庫(kù)操作,再發(fā)送消息:
begin transaction;
//1.數(shù)據(jù)庫(kù)操作
//2.發(fā)送MQ
commit transation;
這種情況下貌似沒有問題,如果發(fā)送MQ消息失敗,就會(huì)拋出異常,導(dǎo)致數(shù)據(jù)庫(kù)事務(wù)回滾。但如果是超時(shí)異常,數(shù)據(jù)庫(kù)回滾,但MQ其實(shí)已經(jīng)正常發(fā)送了,同樣會(huì)導(dǎo)致不一致。
2. 事務(wù)參與方接收消息的可靠性
事務(wù)參與方必須能夠從消息隊(duì)列接收到消息,如果接收消息失敗可以重復(fù)接收消息。
3. 消息重復(fù)消費(fèi)的問題
由于網(wǎng)絡(luò)2的存在,若某一個(gè)消費(fèi)節(jié)點(diǎn)超時(shí)但是消費(fèi)成功,此時(shí)消息中間件會(huì)重復(fù)投遞此消息,就導(dǎo)致了消息的重復(fù)消費(fèi)。要解決消息重復(fù)消費(fèi)的問題就要實(shí)現(xiàn)事務(wù)參與方的方法冪等性
6. 本地消息表方案
本地消息表這個(gè)方案最初是eBay提出的,此方案的核心是通過本地事務(wù)保證數(shù)據(jù)業(yè)務(wù)操作和消息的一致性,然后通過定時(shí)任務(wù)將消息發(fā)送至消息中間件,待確認(rèn)消息發(fā)送給消費(fèi)方成功再將消息刪除。
下面以注冊(cè)送積分為例來(lái)說(shuō)明: 共有兩個(gè)微服務(wù)交互,用戶服務(wù)和積分服務(wù),用戶服務(wù)負(fù)責(zé)添加用戶,積分服務(wù)負(fù)責(zé)增加積分。
圖片
交互流程
- 用戶注冊(cè) 用戶服務(wù)在本地事務(wù)新增用戶和增加 ”積分消息日志“。(用戶表和消息表通過本地事務(wù)保證一致)
begin transaction;
//1.新增用戶
//2.存儲(chǔ)積分消息日志
commit transation;
這種情況下,本地?cái)?shù)據(jù)庫(kù)操作與存儲(chǔ)積分消息日志處于同一個(gè)事務(wù)中,本地?cái)?shù)據(jù)庫(kù)操作與記錄消息日志操作具備原子性。
- 定時(shí)任務(wù)掃描日志
思考:如何保證將消息發(fā)送給消息隊(duì)列呢?
經(jīng)過第一步消息已經(jīng)寫到消息日志表中,可以啟動(dòng)獨(dú)立的線程,定時(shí)對(duì)消息日志表中的消息進(jìn)行掃描并發(fā)送至消息中間件,在消息中間件反饋發(fā)送成功后刪除該消息日志,否則等待定時(shí)任務(wù)下一周期重試。
- 消費(fèi)消息
如何保證消費(fèi)者一定能消費(fèi)到消息呢?
這里可以使用MQ的ack(即消息確認(rèn))機(jī)制,消費(fèi)者監(jiān)聽MQ,如果消費(fèi)者接收到消息并且業(yè)務(wù)處理完成后向MQ發(fā)送ack(即消息確認(rèn)),此時(shí)說(shuō)明消費(fèi)者正常消費(fèi)消息完成,MQ將不再向消費(fèi)者推送消息,否則消費(fèi)者會(huì)不斷重試向消費(fèi)者來(lái)發(fā)送消息。
積分服務(wù)接收到”增加積分“消息,開始增加積分,積分增加成功后向消息中間件回應(yīng)ack,否則消息中間件將重復(fù) 投遞此消息。由于消息會(huì)重復(fù)投遞,積分服務(wù)的”增加積分“功能需要實(shí)現(xiàn)冪等性
7.最大努力通知原則
最大努力通知也是一種基于消息的分布式事務(wù)解決方案,但它不保證 100% 的消息傳遞成功。它的工作原理是:
- 在本地事務(wù)執(zhí)行成功后,系統(tǒng)會(huì)嘗試通知其他的參與者或服務(wù)。
- 通知操作會(huì)盡最大努力去執(zhí)行,但如果失敗,系統(tǒng)不會(huì)無(wú)限重試。
- 該方案通常結(jié)合人工干預(yù),例如,如果通知失敗,系統(tǒng)可能會(huì)記錄日志、發(fā)送報(bào)警、或者提供管理界面供操作人員手動(dòng)處理。思考:最大努力通知與可靠消息一致性有什么不同?
解決方案思想不同
可靠消息一致性,發(fā)起通知方需要保證將消息發(fā)出去,并且將消息發(fā)到接收通知方,消息的可靠性關(guān)鍵由發(fā)起通知方來(lái)保證。
- 最大努力通知,發(fā)起通知方盡最大的努力將業(yè)務(wù)處理結(jié)果通知為接收通知方,但是可能消息接收不到,此時(shí)需要接 收通知方主動(dòng)調(diào)用發(fā)起通知方的接口查詢業(yè)務(wù)處理結(jié)果,通知的可靠性關(guān)鍵在接收通知方。
兩者的業(yè)務(wù)應(yīng)用場(chǎng)景不同
- 可靠消息一致性:關(guān)注的是交易過程的事務(wù)一致,以異步的方式完成交易。
- 最大努力通知:關(guān)注的是交易后的通知事務(wù),即將交易結(jié)果可靠的通知出去。
技術(shù)解決方向不同
- 可靠消息一致性:要解決消息從發(fā)出到接收的一致性,即消息發(fā)出并且被接收到。
- 最大努力通知:無(wú)法保證消息從發(fā)出到接收的一致性,只提供消息接收的可靠性機(jī)制??煽繖C(jī)制是,最大努力的將消息通知給接收方,當(dāng)消息無(wú)法被接收方接收時(shí),由接收方主動(dòng)查詢消息(業(yè)務(wù)處理結(jié)果)。
8. 分布式鎖
在某些業(yè)務(wù)場(chǎng)景,使用分布式鎖是確保多個(gè)分布式節(jié)點(diǎn)不會(huì)同時(shí)操作同一資源的有效方法。這一機(jī)制可以通過使用像Redis、ZooKeeper等分布式協(xié)調(diào)服務(wù)來(lái)實(shí)現(xiàn)
應(yīng)用場(chǎng)景: 在電商秒殺活動(dòng)中,為了防止超賣現(xiàn)象,需要確保同一時(shí)間只有一個(gè)請(qǐng)求能夠?qū)?kù)存數(shù)量進(jìn)行修改。這時(shí),可以使用Redis作為分布式鎖的后端存儲(chǔ),以確保秒殺活動(dòng)的進(jìn)行順利和公平。
推薦場(chǎng)景: 當(dāng)需要協(xié)調(diào)多個(gè)節(jié)點(diǎn)對(duì)共享資源進(jìn)行訪問控制時(shí),分布式鎖是一個(gè)非常有效的解決方案。例如,在分布式系統(tǒng)中,多個(gè)節(jié)點(diǎn)需要同時(shí)對(duì)同一資源進(jìn)行讀取或更新操作時(shí),為了保證數(shù)據(jù)的一致性和避免競(jìng)態(tài)條件,可以使用分布式鎖來(lái)進(jìn)行并發(fā)控制。