分布式事務(wù):
1 CAP 定理
1.1 概念
CAP 理論在分布式系統(tǒng)中
- 一致性:分布式環(huán)境下多個(gè)節(jié)點(diǎn)的數(shù)據(jù)是否強(qiáng)一致
- 可用性:分布式服務(wù)能一直保證可用狀態(tài)。當(dāng)用戶發(fā)出一個(gè)請(qǐng)求后,服務(wù)能在有限時(shí)間內(nèi)返回結(jié)果
- 分區(qū)容忍性:特指對(duì)網(wǎng)絡(luò)分區(qū)的容忍性
對(duì)于共享數(shù)據(jù)系統(tǒng),最多只能同時(shí)擁有CAP其中的兩個(gè),沒法三者兼顧。
- 任兩者的組合都有其適用場(chǎng)景
- 真實(shí)系統(tǒng)應(yīng)當(dāng)是ACID與BASE的混合體
- 不同類型的業(yè)務(wù)可以也應(yīng)當(dāng)區(qū)別對(duì)待
其中,分區(qū)容忍性又是不可或缺的。

結(jié)論:分布式系統(tǒng)中,最重要的是滿足業(yè)務(wù)需求,而不是追求抽象、絕對(duì)的系統(tǒng)特性。
2.2 中間件實(shí)例
默認(rèn)優(yōu)先選擇AP,弱化C
Cassandra、Dynamo 等默認(rèn)優(yōu)先選擇CP,弱化A
HBase、MongoDB 等
2 BASE 理論
核心思想
基本可用(BasicallyAvailable)
分布式系統(tǒng)在出現(xiàn)故障時(shí),允許損失部分的可用性來保證核心可用。
軟狀態(tài)(SoftState)
允許分布式系統(tǒng)存在中間狀態(tài),該中間狀態(tài)不會(huì)影響到系統(tǒng)的整體可用性。
最終一致性(EventualConsistency)
分布式系統(tǒng)中的所有副本數(shù)據(jù)經(jīng)過一定時(shí)間后,最終能夠達(dá)到一致的狀態(tài)
一致性模型數(shù)據(jù)的一致性模型可以分成以下 3 類:
- 強(qiáng)一致性
數(shù)據(jù)更新成功后,任意時(shí)刻所有副本中的數(shù)據(jù)都是一致的,一般采用同步方式實(shí)現(xiàn) - 弱一致性
數(shù)據(jù)更新成功后,系統(tǒng)不承諾立即可以讀到最新寫入的值,也不承諾具體多久后可讀到 - 最終一致性:弱一致性的一種形式,數(shù)據(jù)更新成功后,系統(tǒng)不承諾立即可以返回最新寫入的值,但是保證最終會(huì)返回上一次更新操作的值。
分布式系統(tǒng)數(shù)據(jù)的強(qiáng)一致性、弱一致性和最終一致性可以通過Quorum NRW算法分析。
只要聊到做了分布式系統(tǒng),必問分布式事務(wù),若你對(duì)分布式事務(wù)一無所知的話,確實(shí)很坑,起碼得知道有哪些方案,一般怎么來做,每個(gè)方案的優(yōu)缺點(diǎn)是什么。
現(xiàn)在面試,分布式系統(tǒng)成了標(biāo)配,而分布式系統(tǒng)帶來的分布式事務(wù)也成了標(biāo)配.
你做系統(tǒng)肯定要用事務(wù),那你用事務(wù)的話,分布式系統(tǒng)之后肯定要用分布式事務(wù).
先不說你搞過沒有,起碼你得明白有哪幾種方案,每種方案可能有啥坑?比如TCC方案的網(wǎng)絡(luò)問題、XA方案的一致性問題
-
單塊系統(tǒng)里的事務(wù)
image -
分布式系統(tǒng)里的事務(wù)
image
分布式事務(wù)的幾種解決方案
● 異步校對(duì)數(shù)據(jù)的方式
支付寶、微信支付主動(dòng)查詢支付狀態(tài)、對(duì)賬單的形式;
● 基于可靠消息(MQ)的解決方案
異步場(chǎng)景;通用性較強(qiáng);拓展性較高
● TCC編程式解決方案
嚴(yán)選、阿里、螞蟻金服自己封裝的DTX
3 XA方案
即兩階段提交事務(wù)方案。
需要數(shù)據(jù)庫廠商支持; JAVA組件有atomikos等。
3.1 程序理解
有一個(gè)事務(wù)管理器,負(fù)責(zé)協(xié)調(diào)多個(gè)數(shù)據(jù)庫(資源管理器)的事務(wù)
- 事務(wù)管理器先問各個(gè)數(shù)據(jù)庫預(yù)提交 ok 嗎?
- 如果每個(gè)數(shù)據(jù)庫都回復(fù)ok,即預(yù)提交成功,就正式提交事務(wù),在各個(gè)數(shù)據(jù)庫開始執(zhí)行操作,這里失敗會(huì)有失敗異常重試,日志分析,人工重試。
3.3 適用場(chǎng)景
較適合單塊應(yīng)用中,跨多庫的分布式事務(wù),而且因?yàn)閲?yán)重依賴于數(shù)據(jù)庫層面來搞定復(fù)雜的事務(wù),效率很低,不適合高并發(fā)場(chǎng)景。
如果要玩,那么基于Spring + JTA就可以搞定。
互聯(lián)網(wǎng)公司基本都不用,因?yàn)槟硞€(gè)系統(tǒng)內(nèi)部如果出現(xiàn)跨多庫的操作,是不合規(guī)的!現(xiàn)在的微服務(wù),一個(gè)大的系統(tǒng)分成幾十甚至上百個(gè)服務(wù)。一般規(guī)約每個(gè)服務(wù)只能操作自己對(duì)應(yīng)的一個(gè)數(shù)據(jù)庫。
如果你要操作別的服務(wù)對(duì)應(yīng)的庫,不允許直連別的服務(wù)的庫。
要操作別人的服務(wù)的庫,必須通過調(diào)用別的服務(wù)的接口
4 TCC方案
4.1 三階段
- Try
對(duì)各個(gè)服務(wù)的資源做檢測(cè),對(duì)資源進(jìn)行提前鎖定或者預(yù)留 - Confirm
在各個(gè)服務(wù)中執(zhí)行實(shí)際的操作 - Cancel
如果任何一個(gè)服務(wù)的業(yè)務(wù)方法執(zhí)行出錯(cuò),那么這里就需要進(jìn)行補(bǔ)償,即執(zhí)行已操作成功的業(yè)務(wù)邏輯的回滾操作
4.2 跨行轉(zhuǎn)賬案例
涉及到兩個(gè)銀行的分布式事務(wù),如果用TCC方案來實(shí)現(xiàn),思路是這樣的:
- Try階段
先把兩個(gè)銀行賬戶中的資金給它凍結(jié)住,不讓操作了 - Confirm階段
執(zhí)行實(shí)際的轉(zhuǎn)賬操作,A銀行賬戶的資金扣減,B銀行賬戶的資金增加 - Cancel階段
如果任何一個(gè)銀行的操作執(zhí)行失敗,那么就需要回滾進(jìn)行補(bǔ)償
比如A銀行賬戶如果已經(jīng)扣減了,但是B銀行賬戶資金增加失敗了,那么就得把A銀行賬戶資金給加回去
該方案說實(shí)話幾乎很少使用,但也有使用場(chǎng)景.
因?yàn)檫@個(gè)事務(wù)的回滾實(shí)際上嚴(yán)重依賴于你自己寫代碼來回滾和補(bǔ)償了,會(huì)造成補(bǔ)償代碼巨大,非常惡心!
比如說我們,一般來說和錢相關(guān)的支付、交易等相關(guān)的場(chǎng)景,我們會(huì)用TCC,嚴(yán)格嚴(yán)格保證分布式事務(wù)要么全部成功,要么全部自動(dòng)回滾,嚴(yán)格保證資金的正確性!
4.3 適用場(chǎng)景
除非你是真的一致性要求太高,是系統(tǒng)中核心之核心的場(chǎng)景!
常見的就是資金類的場(chǎng)景,那可以用TCC方案,自己編寫大量的業(yè)務(wù)邏輯,自己判斷一個(gè)事務(wù)中的各個(gè)環(huán)節(jié)是否ok,不ok就執(zhí)行補(bǔ)償/回滾代碼
而且最好是你的各個(gè)業(yè)務(wù)執(zhí)行的時(shí)間都比較短
但是說實(shí)話,一般盡量別這么搞,自己手寫回滾邏輯,或者是補(bǔ)償邏輯,實(shí)在太惡心了,業(yè)務(wù)代碼也很難維護(hù)
4.4 方案示意圖

5 本地消息表
ebay搞出來的這么一套思想
5.1 簡(jiǎn)介
- A系統(tǒng)在本地一個(gè)事務(wù)里操作的同時(shí),插入一條數(shù)據(jù)到消息表
- 接著A系統(tǒng)將這個(gè)消息發(fā)送到MQ
- B系統(tǒng)接收到消息后,在一個(gè)事務(wù)里,往自己本地消息表里插入一條數(shù)據(jù),同時(shí)執(zhí)行其他的業(yè)務(wù)操作,如果這個(gè)消息已經(jīng)被處理過了,那么此時(shí)這個(gè)事務(wù)會(huì)回滾,這樣保證不會(huì)重復(fù)處理消息
- B系統(tǒng)執(zhí)行成功后,就會(huì)更新自己本地消息表的狀態(tài)以及A系統(tǒng)消息表的狀態(tài)
- 如果B系統(tǒng)處理失敗,那么就不會(huì)更新消息表狀態(tài),那么此時(shí)A系統(tǒng)會(huì)定時(shí)掃描自己的消息表,如果有未處理的消息,會(huì)再次發(fā)送到MQ中去,讓B再處理
5.2 優(yōu)點(diǎn)
這個(gè)方案保證了最終一致性
哪怕B事務(wù)失敗了,但是A會(huì)不斷重發(fā)消息,直到B那邊成功為止
5.3 缺陷
最大的問題就在于嚴(yán)重依賴于數(shù)據(jù)庫的消息表來管理事務(wù),這個(gè)會(huì)導(dǎo)致高并發(fā)場(chǎng)景無力,難以擴(kuò)展呢,一般確實(shí)很少用
-
本地消息表方案
image
6 可靠消息最終一致性方案
干脆不用本地的消息表了,直接基于MQ來實(shí)現(xiàn)事務(wù)。比如阿里的RocketMQ就支持消息事務(wù)!
6.1 簡(jiǎn)介
- A系統(tǒng)先發(fā)送一個(gè)prepared消息到MQ,如果這個(gè)prepared消息發(fā)送失敗,那么就直接取消操作,不執(zhí)行了
- 如果這個(gè)消息發(fā)送成功過了,那么接著執(zhí)行本地事務(wù),如果成功就告訴MQ發(fā)送確認(rèn)消息,如果失敗就告訴MQ回滾消息
- 如果發(fā)送了確認(rèn)消息,那么此時(shí)B系統(tǒng)會(huì)接收到確認(rèn)消息,然后執(zhí)行本地的事務(wù)
- MQ會(huì)自動(dòng)定時(shí)輪詢所有prepared消息回調(diào)你的接口,問你這個(gè)消息是不是本地事務(wù)處理失敗了,所有沒發(fā)送確認(rèn)的消息,是繼續(xù)重試還是回滾?
這里你就可以查下數(shù)據(jù)庫看之前本地事務(wù)是否執(zhí)行,如果回滾了,那么這里也回滾吧。這個(gè)就是避免可能本地事務(wù)執(zhí)行成功了,別確認(rèn)消息發(fā)送失敗了。 - 如果系統(tǒng)B的事務(wù)失敗了咋辦?
重試咯,自動(dòng)不斷重試直到成功,如果實(shí)在是不行,要么就是針對(duì)重要的資金類業(yè)務(wù)進(jìn)行回滾,比如B系統(tǒng)本地回滾后,想辦法通知系統(tǒng)A也回滾;或者是發(fā)送報(bào)警由人工來手工回滾和補(bǔ)償
這個(gè)還是比較合適的,目前國(guó)內(nèi)互聯(lián)網(wǎng)公司大都是這么玩的,要不你舉用RocketMQ支持的,要不你就自己基于類似ActiveMQ?RabbitMQ?自己封裝一套類似的邏輯出來,總之思路就是這樣子的
-
可靠消息最終一致性方案
image
7 最大努力通知方案
7.1 簡(jiǎn)介
系統(tǒng)A本地事務(wù)執(zhí)行完后,發(fā)送一個(gè)消息到MQ
有一專門消費(fèi)MQ的最大努力通知服務(wù),會(huì)消費(fèi)MQ,然后寫入數(shù)據(jù)庫中記錄下來,亦可是放入內(nèi)存隊(duì)列,接著調(diào)用系統(tǒng)B的接口
若系統(tǒng)B執(zhí)行成功就ok;若系統(tǒng)B執(zhí)行失敗,那么最大努力通知服務(wù)就定時(shí)嘗試重新調(diào)用系統(tǒng)B,反復(fù)N次,最后還是不行才放棄
-
最大努力通知方案示意圖
image
實(shí)戰(zhàn)方案:
1. 最終一致性
1.1 本地事務(wù)表 + 輪詢補(bǔ)償
交互流程
- ① commit DB事務(wù)提交階段 本地客戶端向DB進(jìn)行事務(wù)提交,此時(shí)需要將業(yè)務(wù)數(shù)據(jù)和記錄消息事務(wù)狀態(tài)的信息表同時(shí)實(shí)現(xiàn)本地事務(wù),此時(shí)標(biāo)記消息事務(wù)狀態(tài)為UN_SEND未發(fā)送或未完成狀態(tài),此時(shí)MQ未發(fā)送
- ② ack DB確認(rèn)階段 返回DB事務(wù)提交成功或失敗狀態(tài)
- ③ commit MQ事務(wù)提交階段 客戶端發(fā)起MQ發(fā)送請(qǐng)求
- ④ update 本地事務(wù)表更新階段 根據(jù)MQ發(fā)送結(jié)果進(jìn)行本地消息事務(wù)表狀態(tài)更新,成功則更新為SEND發(fā)送成功或發(fā)送完畢
- ⑤ MQ補(bǔ)償 本地消息事務(wù)表定時(shí)輪詢 對(duì)未發(fā)送成功消息事務(wù)進(jìn)行補(bǔ)償發(fā)送,實(shí)現(xiàn)分布式事務(wù)的最終一致
場(chǎng)景:重構(gòu)業(yè)務(wù)新老系統(tǒng)雙寫庫同步
項(xiàng)目背景
這是一個(gè)重構(gòu)系統(tǒng)新老系統(tǒng)同時(shí)服役切量遷移的業(yè)務(wù)場(chǎng)景,老系統(tǒng)正在線上服役為各業(yè)務(wù)方提供接口查詢功能,新系統(tǒng)重構(gòu)完成后需要對(duì)接接入,調(diào)用流量要陸續(xù)從老系統(tǒng)切換到新系統(tǒng),最終老系統(tǒng)迭代下線。
分布式事務(wù)
需要解決的分布式事務(wù)問題就是,雙系統(tǒng)的數(shù)據(jù)是異構(gòu)、分散的,線上不可停量,需要陸續(xù)切換完成,因此需要事先將老庫存量數(shù)據(jù)洗入新庫,此過程中增量數(shù)據(jù)寫入是存在的,但是最終新老庫是相對(duì)一致和統(tǒng)一的,該場(chǎng)景需要解決的是數(shù)據(jù)雙庫的雙寫問題。
設(shè)計(jì)方案
場(chǎng)景Q&A
- Q:如何保證雙庫雙寫? A: 同步寫辛庫,MQ異步寫老庫 本地事務(wù) + 消息事務(wù)表
業(yè)務(wù)數(shù)據(jù)持久化開啟數(shù)據(jù)庫本地事務(wù),該事務(wù)中記錄業(yè)務(wù)數(shù)據(jù)和同步狀態(tài)信息 確保本地事務(wù)一定成功,不保證異步MQ事務(wù)
數(shù)據(jù)庫事務(wù)成功后再發(fā)送寫老庫的MQ,保證本地事務(wù)一定完成才會(huì)觸發(fā)MQ發(fā)送,這樣確保本地事務(wù)一定成功,MQ可能成功也可能失敗 重試MQ事務(wù)狀態(tài),最終一致
如果MQ事務(wù)失敗,通過定時(shí)任務(wù)輪詢進(jìn)行重發(fā)驅(qū)動(dòng),最終一致 - Q:異步MQ寫,延遲問題如何解決? A: 增加冗余查詢
增加冗余對(duì)另一庫的冗余查詢進(jìn)行Double Check。由于調(diào)用方幾乎不可能同時(shí)使用新老系統(tǒng)做業(yè)務(wù),因此延遲時(shí)間取決于MQ異步消費(fèi)寫的速度,如果場(chǎng)景比較復(fù)雜要確保絕對(duì)一致可以增加該處理方式 - Q:雙寫過程中多個(gè)MQ如何保證順序、防重等問題? A: 業(yè)務(wù)時(shí)間 + 業(yè)務(wù)ID (1)同一個(gè)業(yè)務(wù)ID代表一個(gè)同一筆業(yè)務(wù),可以依此進(jìn)行業(yè)務(wù)防重處理 消息業(yè)務(wù)ID業(yè)務(wù)時(shí)間消費(fèi)時(shí)間處理邏輯IDTT執(zhí)行IDTT+1防重不處理 (2)同一個(gè)業(yè)務(wù)ID的基礎(chǔ)上增加業(yè)務(wù)時(shí)間,可以依此保證業(yè)務(wù)數(shù)據(jù)的實(shí)時(shí)刷新 業(yè)務(wù)時(shí)間、消費(fèi)時(shí)間同序 消息業(yè)務(wù)ID業(yè)務(wù)時(shí)間消費(fèi)時(shí)間處理邏輯IDTT執(zhí)行IDT+1T+1執(zhí)行(覆蓋) 業(yè)務(wù)時(shí)間、消費(fèi)時(shí)間亂序 消息業(yè)務(wù)ID業(yè)務(wù)時(shí)間消費(fèi)時(shí)間處理邏輯IDTT+1拋棄(業(yè)務(wù)時(shí)間較早)IDT+1T執(zhí)行 (3)不同業(yè)務(wù)ID的基礎(chǔ)上增加業(yè)務(wù)時(shí)間,可以依此保證不同業(yè)務(wù)數(shù)據(jù)的按照業(yè)務(wù)時(shí)間刷新 消息業(yè)務(wù)ID業(yè)務(wù)時(shí)間消費(fèi)時(shí)間處理邏輯IDT+1T+1執(zhí)行(業(yè)務(wù)時(shí)間較新)ID+XTT拋棄(業(yè)務(wù)時(shí)間較早)
場(chǎng)景:第三方認(rèn)證核驗(yàn)
項(xiàng)目背景
這是一個(gè)認(rèn)證系統(tǒng)以來外部核驗(yàn)系統(tǒng)進(jìn)行用戶身份鑒權(quán)的場(chǎng)景,即認(rèn)證系統(tǒng)記錄認(rèn)證發(fā)起記錄,并請(qǐng)求到外部的核驗(yàn)系統(tǒng)發(fā)起一筆核驗(yàn)請(qǐng)求,用戶在核驗(yàn)系統(tǒng)確認(rèn)后核驗(yàn)結(jié)果返回到認(rèn)證系統(tǒng)確認(rèn)用戶的真實(shí)數(shù)據(jù)狀態(tài)。
分布式事務(wù)
該流程中認(rèn)證系統(tǒng)是一個(gè)本地系統(tǒng),存放用戶發(fā)起的認(rèn)證流水信息和核驗(yàn)狀態(tài),依賴外部核驗(yàn)系統(tǒng)返回該筆認(rèn)證記錄的核驗(yàn)狀態(tài),由于核驗(yàn)過程是異步的,用戶可以選擇任意時(shí)間完成或者永遠(yuǎn)不完成,需要保證每次認(rèn)證流程只有一筆業(yè)務(wù)發(fā)起,而且需要根據(jù)業(yè)務(wù)時(shí)間進(jìn)行核驗(yàn)流程的超時(shí)進(jìn)行強(qiáng)制取消或者補(bǔ)償查詢對(duì)齊核驗(yàn)狀態(tài)等,需要解決的分布式事務(wù)是認(rèn)證流水、核驗(yàn)結(jié)果的一致性。
設(shè)計(jì)方案
- 正向流程

- 補(bǔ)償流程
場(chǎng)景Q&A
- Q:本地事務(wù)認(rèn)證流水成功了,外部核驗(yàn)系統(tǒng)提交失敗了怎么辦? A:通過定時(shí)任務(wù)補(bǔ)償觸發(fā)二次提交,只要外部事物提交一直處于未成功,便一直會(huì)被重試提交,直到成功
- Q:外部核驗(yàn)系統(tǒng)事務(wù)完成了,本地事務(wù)認(rèn)證流水提前被作廢了怎么辦? A:以本地事務(wù)認(rèn)證流水的結(jié)果為準(zhǔn)。本地事務(wù)是通過定時(shí)任務(wù)進(jìn)行補(bǔ)充提交+外部事務(wù)狀態(tài)核驗(yàn)查詢的,即時(shí)在臨界點(diǎn)外部事務(wù)完成了,但是超過了業(yè)務(wù)處理時(shí)間已經(jīng)關(guān)閉,不會(huì)再補(bǔ)充修改,這也是根據(jù)業(yè)務(wù)場(chǎng)景做的取舍,用戶可以再次發(fā)起新流程進(jìn)行核驗(yàn)
1.2 本地事務(wù)表 + 事務(wù)消息
交互流程
- ① prepare 準(zhǔn)備階段 本地客戶端向DB、MQ發(fā)送prepare請(qǐng)求
- ② ack 準(zhǔn)備確認(rèn)階段 DB、MQ作為事務(wù)參與者返回本地客戶端ack確認(rèn)
- ③ commit/rollback 提交/回滾階段 根據(jù)事務(wù)參與者在準(zhǔn)備確認(rèn)階段返回結(jié)果進(jìn)行事務(wù)提交或回滾,此時(shí)一旦有事務(wù)參與者返回異?;虺瑫r(shí)未返回則進(jìn)行回滾提交
- ④ callback 補(bǔ)償回調(diào)階段 當(dāng)事務(wù)超時(shí)提交時(shí),則由MQ進(jìn)行回調(diào)查詢數(shù)據(jù)庫本地事務(wù)情況
- ⑤ commit/rollback 提交/回滾階段 根據(jù)補(bǔ)償回調(diào)階段進(jìn)行事務(wù)提交和回滾,實(shí)現(xiàn)分布式事務(wù)的最終一致性
場(chǎng)景:分庫分表路由字段綁定
項(xiàng)目背景
該業(yè)務(wù)是在分庫分表場(chǎng)景下,需要一個(gè)切分鍵字段進(jìn)行數(shù)據(jù)分片路由,一般我們ToC業(yè)務(wù)的話會(huì)使用userId這樣的字段進(jìn)行切分。然而水平切分?jǐn)?shù)據(jù)帶來了數(shù)據(jù)庫讀寫性能的同時(shí)也存在一個(gè)問題,那就是查詢必須攜帶切分鍵才可以完成,因?yàn)橐蕾囁M(jìn)行數(shù)據(jù)路由查詢,比如在以u(píng)serId進(jìn)行數(shù)據(jù)路由切分時(shí),如果想按照用戶的身份證、姓名等進(jìn)行匹配查詢是無法實(shí)現(xiàn)的,因?yàn)槲覀兪孪仁遣恢?strong>userId、身份證、姓名的等值匹配關(guān)系。一般而言,我們可以通過HBase + ES的方案進(jìn)行解決,即監(jiān)聽不同庫的binlog日志,將其按照時(shí)間進(jìn)行排序切分匯入HBase表中,加入要檢索的索引到ES中解決分庫分表下數(shù)據(jù)切片產(chǎn)生的聚合問題。
分布式事務(wù)
基于以上場(chǎng)景,除了通過HBase+ES實(shí)現(xiàn)之外,還可以通過切分鍵與非切分鍵進(jìn)行數(shù)據(jù)綁定解決,但是由于切分鍵與非切分鍵的路由一般不一致,數(shù)據(jù)會(huì)分散到不同庫,因此無法做本地事務(wù),這是我們需要解決的分布式事務(wù)問題。
設(shè)計(jì)方案
- 交互流程
場(chǎng)景Q&A
- Q:異步MQ寫有延遲,查不到切分鍵如何處理? A:事務(wù)處理開始階段通過Redis記錄事務(wù)開啟的分布式鎖標(biāo)識(shí),當(dāng)其他存在沖突的同業(yè)務(wù)在該事務(wù)的處理過程有查詢時(shí),通過判斷分布式鎖標(biāo)識(shí)是否存在來判斷事務(wù)的開啟,若存在則異步等待并發(fā)起間隔查詢直到事務(wù)超時(shí),若事務(wù)超時(shí)周期內(nèi)反復(fù)查詢到則返回,否則根據(jù)事務(wù)處理后結(jié)果返回
- Q:本地事務(wù)與MQ事務(wù)是如何協(xié)作的? A:相當(dāng)于兩個(gè)2PC事務(wù)協(xié)作。 1.一階段DB、MQ同時(shí)prepare(并行) 2.二階段DB先commit,成功后MQ再commit(串行) 流程階段操作Ack反饋持久化是否結(jié)束分布式事務(wù)成功正向流程PrepareDB prepare MQ prepareYesNoNoNo正向流程CommitDB commit MQ commitYesYesYesYes------異常流程PrepareDB 或 MQ 異常NoNoYesNo異常流程CommitDB提交異常NoNoYesNo異常流程CommitMQ提交超時(shí) 回調(diào)本地事務(wù)狀態(tài),本地事務(wù)成功則提交MQ事務(wù)YesYesYesYes異常流程CommitMQ提交超時(shí) 回調(diào)本地事務(wù)狀態(tài),本地事務(wù)失敗則取消MQ事務(wù)YesNoYesNo
- Q:會(huì)不會(huì)存在MQ事務(wù)成功,本地事務(wù)失??? A:不會(huì),而且要避免這種情況發(fā)生。兩個(gè)2PC事務(wù)的prepare準(zhǔn)備階段可以同時(shí)發(fā)起,但在commit提交階段要先保證本地?cái)?shù)據(jù)庫事務(wù)成功之后再進(jìn)行MQ事務(wù)消息的commit,也就是在commit階段是存在依賴關(guān)系、串行化之行的
- Q:事務(wù)消息如何確保最終一致? A:prepare階段失敗、本地事務(wù)commit階段則均不會(huì)持久化;當(dāng)prepare階段成功、本地事務(wù)commit成功,此時(shí)MQ的commit階段異常,則依賴MQ事務(wù)消息的超時(shí)commit機(jī)制觸發(fā)回調(diào)本地事務(wù)狀態(tài)接口方法來決定MQ的二階段是commit還是cancel
1.3 TCC(Try-Commit-Cancel)
交互流程
TCC事務(wù)其實(shí)主要包含兩個(gè)階段:Try階段、Confirm/Cancel階段。
- ① try-嘗試執(zhí)行業(yè)務(wù)
完成所有業(yè)務(wù)檢查(一致性)預(yù)留必須業(yè)務(wù)資源(準(zhǔn)隔離性) - ② confirm-確認(rèn)執(zhí)行業(yè)務(wù)
真正執(zhí)行業(yè)務(wù)不作任何業(yè)務(wù)檢查只使用Try階段預(yù)留的業(yè)務(wù)資源Confirm操作必須保證冪等性 - ③ cancel-取消執(zhí)行業(yè)務(wù)
釋放Try階段預(yù)留的業(yè)務(wù)資源Cancel操作必須保證冪等性
從TCC的邏輯模型上我們可以看到,TCC的核心思想是,Try階段檢查并預(yù)留資源,確保在Confirm階段有資源可用,這樣可以最大程度的確保Confirm階段能夠執(zhí)行成功。這里的資源可以是DB,或者M(jìn)Q,RPC,只要是分布式環(huán)境中的事務(wù)載體就可以,而且需要這些分布式事務(wù)的參與者具備正逆向條件,比如DB、MQ的事務(wù)可以支持2PC,可以根據(jù)協(xié)調(diào)者對(duì)其進(jìn)行事務(wù)提交或者取消操作,RPC比如賬戶服務(wù)可以支持正向增加也可以支持逆向減少,除此之外,DB、MQ要自身支持事務(wù)的ACID,這是參與分布式事務(wù)的原子性保證,RPC底層也是DB,這里可以等同理解。以上參與者具備參與分布式事務(wù)的基本條件后便可以進(jìn)行整合和使用,業(yè)務(wù)流程的驅(qū)動(dòng)和事務(wù)的完整性由中間協(xié)調(diào)者來操作和推進(jìn)。
場(chǎng)景:積分商品兌換
項(xiàng)目背景
這是一個(gè)電商系統(tǒng)比較經(jīng)典的下單、扣款、發(fā)貨流程,在下單之前會(huì)通過一系列商品在架狀態(tài)、庫存數(shù)量、購買限制等有效性過濾,然后在業(yè)務(wù)系統(tǒng)中進(jìn)行一個(gè)商品兌換的場(chǎng)景。
分布式事務(wù)
由于是商品兌換,對(duì)于用戶和系統(tǒng)本身來說是這個(gè)過程一個(gè)原子性的、一鍵完成的操作,因此下單+動(dòng)賬+發(fā)貨是一個(gè)現(xiàn)實(shí)存在的分布式事務(wù)。這里假設(shè)訂單數(shù)據(jù)和動(dòng)賬數(shù)據(jù)在一個(gè)本地?cái)?shù)據(jù)庫事務(wù)中,持久化開啟數(shù)據(jù)庫本地事務(wù),該事務(wù)中記錄訂單生成數(shù)據(jù)和動(dòng)賬數(shù)據(jù)信息,以及發(fā)貨狀態(tài)的信息記錄。這里需要解決的分布式事務(wù)是訂單數(shù)據(jù)、動(dòng)賬數(shù)據(jù)、發(fā)貨狀態(tài)三種的最終一致。
設(shè)計(jì)方案

- 正向流程
- 補(bǔ)償流程

場(chǎng)景Q&A
- Q:動(dòng)賬扣款成功,但是發(fā)貨失敗怎么辦? A: 定時(shí)任務(wù)根據(jù)發(fā)貨狀態(tài)進(jìn)行發(fā)貨流程驅(qū)動(dòng) 定時(shí)任務(wù)補(bǔ)償再次發(fā)貨
發(fā)貨成功則分布式事務(wù)最終一致,下游發(fā)貨系統(tǒng)進(jìn)行發(fā)貨防重處理 發(fā)貨失敗進(jìn)入逆向流程
定時(shí)任務(wù)驅(qū)動(dòng)發(fā)貨最終一致理論上可以一直進(jìn)行,但是發(fā)貨可能有一個(gè)流程時(shí)效和庫存售罄的問題,可以根據(jù)業(yè)務(wù)場(chǎng)景進(jìn)行配置比如2天內(nèi)重復(fù)調(diào)用失敗或調(diào)用返回?zé)o庫存則進(jìn)入逆向關(guān)單退款流程使得分布式事務(wù)恢復(fù)成最開始的一致性狀態(tài) - Q:逆向流程回滾賬戶扣款,怎么防重? A:賬戶流水表做唯一索引正逆向類型 + 業(yè)務(wù)ID,和賬戶額度表進(jìn)行本地事務(wù)操作,確保只能成功一筆正向/逆向業(yè)務(wù)操作
場(chǎng)景:廣告任務(wù)結(jié)算

項(xiàng)目背景
當(dāng)一個(gè)App有了足夠多的用戶體量,便開始有商家進(jìn)行廣告或商品的投放,平臺(tái)通過包裝運(yùn)營(yíng)活動(dòng)、廣告位等,將用戶曝光與商家付費(fèi)結(jié)合達(dá)到流量變現(xiàn)的目的。
分布式事務(wù)
當(dāng)用戶進(jìn)行瀏覽、關(guān)注、商品購買、視頻觀看、App下載等多種任務(wù),我們會(huì)根據(jù)商家配置等付費(fèi)規(guī)則進(jìn)行商家廣告費(fèi)用的扣減,同時(shí)還要為用戶完成任務(wù)進(jìn)行獎(jiǎng)勵(lì)結(jié)算,此時(shí)的分布式事務(wù)便是商家賬戶扣減與用戶賬戶增加的數(shù)據(jù)一致性問題
設(shè)計(jì)方案
- 交互流程
- 補(bǔ)償流程
場(chǎng)景Q&A
- Q:商戶扣款失敗或者商戶扣款成功,用戶結(jié)算失敗怎么辦? A: 定時(shí)任務(wù)根據(jù)結(jié)算狀態(tài)進(jìn)行結(jié)算流程驅(qū)動(dòng),會(huì)一直輪詢補(bǔ)償嘗試結(jié)算用戶,直到成功。
- Q:商戶扣款成功,用戶結(jié)算失敗為何不進(jìn)行做業(yè)務(wù)超時(shí)的逆向流程回滾商家扣款? A: 用戶做任務(wù)時(shí)一定是做了前置校驗(yàn)進(jìn)行平臺(tái)任務(wù)發(fā)放的,也就是說用戶任務(wù)只要完成必須要進(jìn)行結(jié)算,這是一個(gè)不能逆向的流程。即使極端情況下商戶余額空了暫時(shí)無法結(jié)算到用戶也要一直重試,一旦商戶余額充足則繼續(xù)整個(gè)結(jié)算流程。
場(chǎng)景:運(yùn)營(yíng)活動(dòng)抽獎(jiǎng)
項(xiàng)目背景
抽獎(jiǎng)是運(yùn)營(yíng)活動(dòng)中比較常見的方式,對(duì)于用戶來說需要做的是參加設(shè)定人物獲取積分,攢夠積分就可以開始抽獎(jiǎng),抽中后即等待獎(jiǎng)品入賬,一般券會(huì)立刻入賬使用,而實(shí)物商品則需要耐心等待物流送到用戶手上。
分布式事務(wù)
關(guān)于抽獎(jiǎng),涉及賬戶動(dòng)賬、庫存參與次數(shù)扣減、抽獎(jiǎng)等多個(gè)環(huán)節(jié),該場(chǎng)景要解決的分布式事務(wù)是賬戶動(dòng)賬、活動(dòng)庫存變更、抽獎(jiǎng)下單數(shù)據(jù)一致性。
設(shè)計(jì)方案

- 交互流程
- 補(bǔ)償流程
場(chǎng)景Q&A
- Q:動(dòng)賬扣減失敗,補(bǔ)償流程為何不驅(qū)動(dòng)扣減完成抽獎(jiǎng)? A: 抽獎(jiǎng)業(yè)務(wù)對(duì)于用戶來說實(shí)時(shí)性要求很高,正向流程沒有完成的話,沒有通過補(bǔ)償流程驅(qū)動(dòng)的必要性了,用戶體驗(yàn)不好容易產(chǎn)生問題,補(bǔ)償流程只針對(duì)賬戶扣減成功扣沒有順利完成抽獎(jiǎng)的情況進(jìn)行賬戶補(bǔ)款即可。而且這部分補(bǔ)款是有延遲的,在C端展示可以優(yōu)化或者忽略合并掉,保證的是賬戶額度無損。
2. 弱一致性
2.1 最大努力通知 + 消息重試控制
場(chǎng)景:數(shù)據(jù)變更同步下游業(yè)務(wù)方
項(xiàng)目背景
系統(tǒng)數(shù)據(jù)發(fā)生變更時(shí),會(huì)對(duì)外部系統(tǒng)產(chǎn)生一定影響,外部系統(tǒng)需要知道這種數(shù)據(jù)變化,這便是數(shù)據(jù)狀態(tài)同步的場(chǎng)景。一般來說數(shù)據(jù)交互可以有推(Push)、拉(Pull) 兩種形式,這里先說推模式,即數(shù)據(jù)變更方負(fù)責(zé)將變化通知到數(shù)據(jù)關(guān)注方。
分布式事務(wù)
這里要保證的是數(shù)據(jù)變更在多個(gè)應(yīng)用中的狀態(tài)一致。
設(shè)計(jì)方案
- 交互流程

場(chǎng)景Q&A
這里是弱一致性的實(shí)現(xiàn),沒有做本地事務(wù)表和定時(shí)任務(wù)輪詢對(duì)比各事務(wù)狀態(tài)進(jìn)行補(bǔ)償操作。完全依賴于MQ的失敗重試驅(qū)動(dòng),若RPC調(diào)用失敗即數(shù)據(jù)同步業(yè)務(wù)方失敗,MQ會(huì)一直進(jìn)行重試操作,隨著重試次數(shù)增加,重試間隔也會(huì)增加,這里也可以業(yè)務(wù)自行進(jìn)行最大努力嘗試次數(shù)的控制,超過多少次嘗試仍失敗則放棄,因此不能保證最終一致。
場(chǎng)景:數(shù)據(jù)變更廣播下游業(yè)務(wù)方

項(xiàng)目背景
這里是數(shù)據(jù)同步的說拉模式,即數(shù)據(jù)關(guān)注方對(duì)數(shù)據(jù)變更方進(jìn)行數(shù)據(jù)狀態(tài)變更的監(jiān)聽,這種處理方式處理的主動(dòng)權(quán)在于數(shù)據(jù)關(guān)注方,數(shù)據(jù)變更方只負(fù)責(zé)和保證一定通知到數(shù)據(jù)變更情況,是否能夠同步成功則由監(jiān)聽方處理和兼容。
分布式事務(wù)
這里要保證的是數(shù)據(jù)變更在多個(gè)應(yīng)用中的狀態(tài)一致。
設(shè)計(jì)方案

- 交互流程
場(chǎng)景Q&A
這里也是弱一致性的實(shí)現(xiàn),沒有做本地事務(wù)表和定時(shí)任務(wù)輪詢對(duì)比各事務(wù)狀態(tài)進(jìn)行補(bǔ)償操作。完全依賴于MQ消費(fèi)方的處理,若消費(fèi)方處理失敗或在消息隊(duì)列規(guī)定時(shí)間內(nèi)沒有消費(fèi)完畢,則數(shù)據(jù)無法保證最終一致。
2.2 戰(zhàn)略放棄 + 報(bào)警 + 人工修復(fù)
場(chǎng)景:秒殺庫存回滾
項(xiàng)目背景
在秒殺場(chǎng)景中,最復(fù)雜的除了解決高并發(fā)問題外,最核心的就是活動(dòng)商品的庫存控制、變更問題,一般商品庫存會(huì)初始化到Redis緩存中進(jìn)行管理,秒殺方法會(huì)對(duì)Redis緩存庫存數(shù)量進(jìn)行校驗(yàn)、扣減操作,通過MQ異步扣減DB庫存,既利用Reids原子操作進(jìn)行庫存數(shù)量操作,又利用緩存抗住高并發(fā)請(qǐng)求,起到異步削峰的作用,這是秒殺的正向流程。而逆向流程是用戶秒殺到商品預(yù)占了庫存,但是沒有及時(shí)進(jìn)行訂單支付或者進(jìn)行了訂單取消,此時(shí)要發(fā)起對(duì)庫存的恢復(fù)操作。
分布式事務(wù)
這里的分布式事務(wù)是Redis緩存庫存與DB庫存數(shù)量一致性問題。
設(shè)計(jì)方案

- 交互流程

場(chǎng)景Q&A
- Q:秒殺場(chǎng)景會(huì)出現(xiàn)哪些分布式問題? A: 根據(jù)如上流程圖,扣減緩存庫存、創(chuàng)建訂單、異步MQ發(fā)送是在一個(gè)同步的函數(shù)方法中的三個(gè)非原子的子步驟,而秒殺場(chǎng)景下流量洪峰會(huì)一瞬間打滿線程,以上三個(gè)子步驟任何異步都會(huì)出現(xiàn)問題,因?yàn)槎际窍瓤劬彺鎺齑鏀?shù)量,根據(jù)實(shí)踐經(jīng)驗(yàn)看,極端情況下會(huì)出現(xiàn)扣減緩存庫存成功了,后面創(chuàng)建訂單失敗了或者異步MQ沒有發(fā)出來無法削減DB庫存數(shù)量,因此數(shù)據(jù)結(jié)果是緩存庫存扣減的多,DB扣減的少,實(shí)際搶購賣出的少,換句話說就是出現(xiàn)了少賣的現(xiàn)象。
- Q:會(huì)不會(huì)出現(xiàn)超賣現(xiàn)象? A: 不會(huì)。依賴于Redis單線程命令執(zhí)行的保證。這里需要注意的是讀、寫命令不是一致,可以結(jié)合分布式鎖實(shí)現(xiàn),也可以通過Lua腳本實(shí)現(xiàn)命令的原子性執(zhí)行。
這里也是一個(gè)弱一致性的實(shí)現(xiàn),業(yè)務(wù)場(chǎng)景我們保證不超賣即可,對(duì)于極端情況出現(xiàn)的庫存數(shù)量無效多扣減做戰(zhàn)略性放棄,一般情況下不會(huì)影響大多業(yè)務(wù)使用,如果非要吹毛求疵達(dá)到賬戶金額那種強(qiáng)一致性,思路也很簡(jiǎn)單,可以借助定時(shí)任務(wù)輪詢對(duì)比緩存與DB庫存數(shù)量進(jìn)行校驗(yàn),這里還要考慮到其他在行流程如超市關(guān)單庫存恢復(fù),仍然在行的秒殺活動(dòng)等,保證數(shù)據(jù)處理不多加不多減。
3. 總結(jié)
3.1 分布式角色
-
參與者
可以通俗的認(rèn)為是DB、RPC、MQ這些能夠提供事務(wù)能力的中間件或接口服務(wù) -
協(xié)調(diào)者
維系分布式事務(wù)各個(gè)參與者分布式狀態(tài)的系統(tǒng)、中間件,如Zookeeper、業(yè)務(wù)系統(tǒng)
3.2 技術(shù)保證
- 數(shù)據(jù)庫事務(wù) 數(shù)據(jù)庫如MYSQL提供了2PC、XA協(xié)議,依賴于WAL + Redo Log + 刷盤策略保證
- MQ事務(wù) 提供了2PC協(xié)議,依賴于Ack機(jī)制+刷盤策略保證
3.2 強(qiáng)弱一致選擇
-
強(qiáng)一致性
強(qiáng)一致性確保的不是事務(wù)一定成功,而是事務(wù)參與者的子事務(wù)要么全成功,要么全失敗,保證子事務(wù)的最終一致。一般依賴于定時(shí)任務(wù)、補(bǔ)償機(jī)制、Double Check等方式進(jìn)行事務(wù)狀態(tài)的校準(zhǔn)和協(xié)調(diào),一般設(shè)計(jì)和實(shí)現(xiàn)的復(fù)雜度大,參與者越多,流程越復(fù)雜,越難以維護(hù),最終一致的延遲性和可靠性保證越難 -
弱一致性
弱一致性放棄了最終一致性的保證,通過最大努力實(shí)現(xiàn)而不保證最終的結(jié)果,這種場(chǎng)景減少和減低了開發(fā)和設(shè)計(jì)的復(fù)雜度
3.3 冪等&防重
- 業(yè)務(wù)冪等通常會(huì)定義bizId代表全局唯一的業(yè)務(wù)標(biāo)識(shí)。在MQ重發(fā)、重復(fù)消費(fèi)、亂序,RPC重復(fù)調(diào)用等場(chǎng)景進(jìn)行業(yè)務(wù)防重兼容處理。
- 如賬戶余額的強(qiáng)一致防重處理,可以結(jié)合流水表唯一索引正逆向類型 + 業(yè)務(wù)ID進(jìn)行攔截
- 一般大多依賴于數(shù)據(jù)庫的唯一索引進(jìn)行防重保證,如果擔(dān)心數(shù)據(jù)庫性能問題,可以前置緩存攔截處理
3.4 盡早干預(yù)&補(bǔ)償一致
- 盡早干預(yù)
指的是代碼邏輯上盡早對(duì)串行處理的做個(gè)子事務(wù)進(jìn)行回滾或逆向操作,這樣可以盡快結(jié)束分布式事務(wù),而不需要等待相對(duì)更為延遲的定時(shí)任務(wù)或其他補(bǔ)償機(jī)制來驅(qū)動(dòng),這里可以使用旁路方法或不阻塞主方法放到MQ或異步線程中進(jìn)行處理,比如秒殺下單發(fā)貨因?yàn)閹齑娌蛔慊蛏唐废录芸梢粤⒖踢M(jìn)行發(fā)起關(guān)單退款的逆向流程
- 補(bǔ)償一致
補(bǔ)償機(jī)制一般可以通過定時(shí)任務(wù)、MQ重試來進(jìn)行子事務(wù)驅(qū)動(dòng)整個(gè)分布式事務(wù)的完結(jié)




