分布式事務(wù)
微服務(wù)倡導(dǎo)將復(fù)雜的系統(tǒng)拆分為若干個(gè)簡(jiǎn)單、職責(zé)單一、松耦合的服務(wù),可以降低開發(fā)難度,便于敏捷開發(fā)。而對(duì)大多數(shù)中小型公司來說,實(shí)施微服務(wù)架構(gòu)面臨以下困難:
- 單體應(yīng)用拆分為分布式系統(tǒng)后,應(yīng)用間的通訊和故障處理機(jī)制變得復(fù)雜
- 微服務(wù)化后,一個(gè)簡(jiǎn)單的功能需要調(diào)用多個(gè)服務(wù)并操作多個(gè)數(shù)據(jù)庫實(shí)現(xiàn),數(shù)據(jù)一致性難以保障
- 大量的微服務(wù),導(dǎo)致其測(cè)試、維護(hù)、部署變得困難
為了保障微服務(wù)架構(gòu)下數(shù)據(jù)的一致性,通常需要引入分布式事務(wù)來解決,當(dāng)前比較流行的分布式解決方案如下。
基于二階段提交的XA協(xié)議
- 第一階段:協(xié)調(diào)者詢問所有參與者是否可以執(zhí)行提交操作,參與者執(zhí)行準(zhǔn)備工作,例如為資源上鎖,預(yù)留資源,寫undo/redo log。
-
第二階段:若所有參與者回應(yīng)“可提交”,則向所有參與者發(fā)送正式提交命令;若某個(gè)參與者回應(yīng)“拒絕提交”,則向所有參與者發(fā)送回滾命令。
image.png
XA協(xié)議保障了事務(wù)的強(qiáng)一致性,然而由于其采用的阻塞協(xié)議帶來的巨大性能開銷,難以達(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ù)庫操作的粒度,使得降低鎖沖突、提高吞吐量成為可能,然而它對(duì)業(yè)務(wù)的侵入度較高,實(shí)現(xiàn)難度較大。
事務(wù)消息
通過消息的異步事務(wù),可以保證本地事務(wù)和消息發(fā)送同時(shí)執(zhí)行成功或失敗,從而保證了數(shù)據(jù)的最終一致性。
- 發(fā)送prepare消息,該消息對(duì)Consumer不可見
- 執(zhí)行本地事務(wù)
- 若本地事務(wù)執(zhí)行成功,則向MQ提交消息確認(rèn)發(fā)送指令;若本地事務(wù)執(zhí)行失敗,則向MQ發(fā)送取消指令
-
若MQ長時(shí)間未收到確認(rèn)發(fā)送或取消發(fā)送的指令,則向業(yè)務(wù)系統(tǒng)詢問本地事務(wù)狀態(tài),并做補(bǔ)償處理
image.png
RocketMq事務(wù)消息

客戶端事務(wù)消息發(fā)送

發(fā)送prepare消息復(fù)用了普通消息發(fā)送,只是給消息增加了TRAN_MSG=true的屬性,該屬性決定prepare消息對(duì)Consumer不可見
消息寫入CommitLog

事務(wù)消息的CommitLog寫入和普通消息一致,它利用文件的順序?qū)憗硖嵘掏铝?,并采用文件映射的方式降低系統(tǒng)開銷。
消息寫入ConsumeQueue

ConsumeQueue的寫入是采用異步方式完成的,ReputMessageSerivce作為一個(gè)長駐線程負(fù)責(zé)查詢索引的構(gòu)造和ConsumeQueue的寫入,對(duì)于Prepare/Rollback消息不會(huì)寫ConsumeQueue,從而保證它們對(duì)Consumer不可見
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)致缺頁中斷),以提升系統(tǒng)吞吐量。
此外,rocketmq的最新版本(4.2.0)尚未支持本地事務(wù)的狀態(tài)回查,這樣可能存在由于網(wǎng)絡(luò)抖動(dòng),導(dǎo)致commit/rollback未提交到broker導(dǎo)致prepare消息長期懸掛的風(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后,可以通過CommitLogDispatcher寫入一條事務(wù)狀態(tài)記錄(state=P)
- 在提交/回滾事務(wù)時(shí),根據(jù)transactionId找到對(duì)應(yīng)的事務(wù)狀態(tài)記錄,并修改對(duì)應(yīng)的事務(wù)狀態(tài)
- 通過長駐線程掃描事務(wù)狀態(tài)表,對(duì)于超過一定時(shí)間的Prepare事務(wù),發(fā)起對(duì)業(yè)務(wù)方的事務(wù)狀態(tài)回查,根據(jù)回查結(jié)果修改事務(wù)狀態(tài),并向brokder發(fā)送相應(yīng)的Commit/Rollback消息。


