分布式事務(wù)
微服務(wù)倡導(dǎo)將復(fù)雜的系統(tǒng)拆分為若干個(gè)簡(jiǎn)單雏蛮、職責(zé)單一糖荒、松耦合的服務(wù),可以降低開(kāi)發(fā)難度席噩,便于敏捷開(kāi)發(fā)班缰。而對(duì)大多數(shù)中小型公司來(lái)說(shuō),實(shí)施微服務(wù)架構(gòu)面臨以下困難:
- 單體應(yīng)用拆分為分布式系統(tǒng)后悼枢,應(yīng)用間的通訊和故障處理機(jī)制變得復(fù)雜
- 微服務(wù)化后埠忘,一個(gè)簡(jiǎn)單的功能需要調(diào)用多個(gè)服務(wù)并操作多個(gè)數(shù)據(jù)庫(kù)實(shí)現(xiàn),數(shù)據(jù)一致性難以保障
- 大量的微服務(wù)馒索,導(dǎo)致其測(cè)試莹妒、維護(hù)、部署變得困難
為了保障微服務(wù)架構(gòu)下數(shù)據(jù)的一致性双揪,通常需要引入分布式事務(wù)來(lái)解決动羽,當(dāng)前比較流行的分布式解決方案如下。
基于二階段提交的XA協(xié)議
- 第一階段:協(xié)調(diào)者詢問(wèn)所有參與者是否可以執(zhí)行提交操作渔期,參與者執(zhí)行準(zhǔn)備工作运吓,例如為資源上鎖,預(yù)留資源疯趟,寫undo/redo log拘哨。
-
第二階段:若所有參與者回應(yīng)“可提交”,則向所有參與者發(fā)送正式提交命令信峻;若某個(gè)參與者回應(yīng)“拒絕提交”倦青,則向所有參與者發(fā)送回滾命令。
image.png
XA協(xié)議保障了事務(wù)的強(qiáng)一致性盹舞,然而由于其采用的阻塞協(xié)議帶來(lái)的巨大性能開(kāi)銷产镐,難以達(dá)到較高的系統(tǒng)吞吐量。
TCC模式
TCC提供了一種全局事務(wù)解決方案踢步,業(yè)務(wù)系統(tǒng)只需實(shí)現(xiàn)下面三個(gè)操作癣亚,即可完成分布式事務(wù):
- TRY:完成參與者業(yè)務(wù)檢查并預(yù)留業(yè)務(wù)資源
- CONFIRM:使用TRY階段的預(yù)留業(yè)務(wù)資源,并執(zhí)行業(yè)務(wù)
-
CANCEL:釋放TRY結(jié)算預(yù)留的業(yè)務(wù)資源
image.png
TCC模式可以讓業(yè)務(wù)更靈活地定義數(shù)據(jù)庫(kù)操作的粒度获印,使得降低鎖沖突述雾、提高吞吐量成為可能,然而它對(duì)業(yè)務(wù)的侵入度較高兼丰,實(shí)現(xiàn)難度較大玻孟。
事務(wù)消息
通過(guò)消息的異步事務(wù),可以保證本地事務(wù)和消息發(fā)送同時(shí)執(zhí)行成功或失敗鳍征,從而保證了數(shù)據(jù)的最終一致性黍翎。
- 發(fā)送prepare消息,該消息對(duì)Consumer不可見(jiàn)
- 執(zhí)行本地事務(wù)
- 若本地事務(wù)執(zhí)行成功蟆技,則向MQ提交消息確認(rèn)發(fā)送指令玩敏;若本地事務(wù)執(zhí)行失敗斗忌,則向MQ發(fā)送取消指令
-
若MQ長(zhǎng)時(shí)間未收到確認(rèn)發(fā)送或取消發(fā)送的指令质礼,則向業(yè)務(wù)系統(tǒng)詢問(wèn)本地事務(wù)狀態(tài)旺聚,并做補(bǔ)償處理
image.png
RocketMq事務(wù)消息
客戶端事務(wù)消息發(fā)送
發(fā)送prepare消息復(fù)用了普通消息發(fā)送,只是給消息增加了TRAN_MSG=true的屬性眶蕉,該屬性決定prepare消息對(duì)Consumer不可見(jiàn)
消息寫入CommitLog
事務(wù)消息的CommitLog寫入和普通消息一致砰粹,它利用文件的順序?qū)憗?lái)提升吞吐量,并采用文件映射的方式降低系統(tǒng)開(kāi)銷造挽。
消息寫入ConsumeQueue
ConsumeQueue的寫入是采用異步方式完成的碱璃,ReputMessageSerivce作為一個(gè)長(zhǎng)駐線程負(fù)責(zé)查詢索引的構(gòu)造和ConsumeQueue的寫入,對(duì)于Prepare/Rollback消息不會(huì)寫ConsumeQueue饭入,從而保證它們對(duì)Consumer不可見(jiàn)
Broker端事務(wù)提交/回滾
Broker收到提交/回滾指令后嵌器,首先從根據(jù)offset從CommitLog讀出原有的prepare消息,構(gòu)造新的消息(修改事務(wù)狀態(tài)標(biāo)識(shí))并寫入Broker谐丢。對(duì)于一條事務(wù)消息爽航,RocketMq會(huì)存儲(chǔ)兩條消息,存在一定資源浪費(fèi)乾忱。其實(shí)它是為了保證隨后的消費(fèi)者能盡可能從PageCache中讀到該消息讥珍,而不是讀取較早的prepare消息(可能導(dǎo)致缺頁(yè)中斷),以提升系統(tǒng)吞吐量窄瘟。
此外衷佃,rocketmq的最新版本(4.2.0)尚未支持本地事務(wù)的狀態(tài)回查,這樣可能存在由于網(wǎng)絡(luò)抖動(dòng)蹄葱,導(dǎo)致commit/rollback未提交到broker導(dǎo)致prepare消息長(zhǎng)期懸掛的風(fēng)險(xiǎn)氏义。
在RocketMq的設(shè)計(jì)文檔中,為事務(wù)消息增加了一張事務(wù)狀態(tài)表图云,它維護(hù)了消息的Offset惯悠、事務(wù)狀態(tài)(P/C/R)信息∏淼荆可以采用如下思路實(shí)現(xiàn)事務(wù)消息的回查機(jī)制:
- 在prepare消息寫入commitLog后吮螺,可以通過(guò)CommitLogDispatcher寫入一條事務(wù)狀態(tài)記錄(state=P)
- 在提交/回滾事務(wù)時(shí),根據(jù)transactionId找到對(duì)應(yīng)的事務(wù)狀態(tài)記錄帕翻,并修改對(duì)應(yīng)的事務(wù)狀態(tài)
- 通過(guò)長(zhǎng)駐線程掃描事務(wù)狀態(tài)表鸠补,對(duì)于超過(guò)一定時(shí)間的Prepare事務(wù),發(fā)起對(duì)業(yè)務(wù)方的事務(wù)狀態(tài)回查嘀掸,根據(jù)回查結(jié)果修改事務(wù)狀態(tài)紫岩,并向brokder發(fā)送相應(yīng)的Commit/Rollback消息。