分布式事務(wù)<轉(zhuǎn)>

原文 https://zhuanlan.zhihu.com/p/79833740

只要聊到你做了分布式系統(tǒng)璃哟,必問分布式事務(wù)斑匪,你對分布式事務(wù)一無所知的話,確實會很坑嬉愧,你起碼得知道有哪些方案乍丈,一般怎么來做剂碴,每個方案的優(yōu)缺點是什么。現(xiàn)在面試轻专,分布式系統(tǒng)成了標配汗茄,而分布式系統(tǒng)帶來的分布式事務(wù)也成了標配了。因為你做系統(tǒng)肯定要用事務(wù)吧铭若,如果是分布式系統(tǒng)洪碳,肯定要用分布式事務(wù)吧。先不說你搞過沒有叼屠,起碼你得明白有哪幾種方案瞳腌,每種方案可能有啥坑?比如 TCC 方案的網(wǎng)絡(luò)問題镜雨、XA 方案的一致性問題嫂侍。分布式事務(wù)的實現(xiàn)主要有以下 5 種方案:

  • XA 方案
  • TCC 方案
  • 本地消息表
  • 可靠消息最終一致性方案
  • 最大努力通知方案

1.兩階段提交方案/XA方案

所謂的 XA 方案,即:兩階段提交荚坞,有一個事務(wù)管理器的概念挑宠,負責協(xié)調(diào)多個數(shù)據(jù)庫(資源管理器)的事務(wù),事務(wù)管理器先問問各個數(shù)據(jù)庫你準備好了嗎颓影?如果每個數(shù)據(jù)庫都回復(fù) ok各淀,那么就正式提交事務(wù),在各個數(shù)據(jù)庫上執(zhí)行操作诡挂;如果任何其中一個數(shù)據(jù)庫回答不 ok碎浇,那么就回滾事務(wù)临谱。

這種分布式事務(wù)方案,比較適合單塊應(yīng)用里奴璃,跨多個庫的分布式事務(wù)悉默,而且因為嚴重依賴于數(shù)據(jù)庫層面來搞定復(fù)雜的事務(wù),效率很低苟穆,絕對不適合高并發(fā)的場景抄课。如果要玩兒,那么基于 Spring + JTA 就可以搞定雳旅,自己隨便搜個 demo 看看就知道了剖膳。

這個方案,我們很少用岭辣,一般來說某個系統(tǒng)內(nèi)部如果出現(xiàn)跨多個庫的這么一個操作吱晒,是不合規(guī)的。我可以給大家介紹一下沦童, 現(xiàn)在微服務(wù)仑濒,一個大的系統(tǒng)分成幾十個甚至幾百個服務(wù)。一般來說偷遗,我們的規(guī)定和規(guī)范墩瞳,是要求每個服務(wù)只能操作自己對應(yīng)的一個數(shù)據(jù)庫

如果你要操作別的服務(wù)對應(yīng)的庫氏豌,不允許直連別的服務(wù)的庫喉酌,違反微服務(wù)架構(gòu)的規(guī)范,你隨便交叉胡亂訪問泵喘,幾百個服務(wù)的話泪电,全體亂套,這樣的一套服務(wù)是沒法管理的纪铺,沒法治理的相速,可能會出現(xiàn)數(shù)據(jù)被別人改錯,自己的庫被別人寫掛等情況鲜锚。

如果你要操作別人的服務(wù)的庫突诬,你必須是通過調(diào)用別的服務(wù)的接口來實現(xiàn),絕對不允許交叉訪問別人的數(shù)據(jù)庫芜繁。

image

2.TCC 方案

TCC 的全稱是:Try旺隙、Confirm、Cancel骏令。

Try 階段:這個階段說的是對各個服務(wù)的資源做檢測以及對資源進行鎖定或者預(yù)留蔬捷。
Confirm 階段:這個階段說的是在各個服務(wù)中執(zhí)行實際的操作
Cancel 階段:如果任何一個服務(wù)的業(yè)務(wù)方法執(zhí)行出錯伏社,那么這里就需要進行補償抠刺,就是執(zhí)行已經(jīng)執(zhí)行成功的業(yè)務(wù)邏輯的回滾操作塔淤。(把那些執(zhí)行成功的回滾)
這種方案說實話幾乎很少人使用摘昌,我們用的也比較少速妖,但是也有使用的場景。因為這個事務(wù)回滾實際上是嚴重依賴于你自己寫代碼來回滾和補償了聪黎,會造成補償代碼巨大罕容,非常之惡心。

比如說我們稿饰,一般來說跟錢相關(guān)的锦秒,跟錢打交道的,支付喉镰、交易相關(guān)的場景旅择,我們會用 TCC,嚴格保證分布式事務(wù)要么全部成功侣姆,要么全部自動回滾生真,嚴格保證資金的正確性,保證在資金上不會出現(xiàn)問題捺宗。

而且最好是你的各個業(yè)務(wù)執(zhí)行的時間都比較短柱蟀。

但是說實話,一般盡量別這么搞蚜厉,自己手寫回滾邏輯长已,或者是補償邏輯,實在太惡心了昼牛,那個業(yè)務(wù)代碼是很難維護的术瓮。

image

3.本地消息表

本地消息表其實是國外的 ebay 搞出來的這么一套思想。

這個大概意思是這樣的:

A 系統(tǒng)在自己本地一個事務(wù)里操作同時贰健,插入一條數(shù)據(jù)到消息表斤斧;
接著 A 系統(tǒng)將這個消息發(fā)送到 MQ 中去;
B 系統(tǒng)接收到消息之后霎烙,在一個事務(wù)里撬讽,往自己本地消息表里插入一條數(shù)據(jù),同時執(zhí)行其他的業(yè)務(wù)操作悬垃,如果這個消息已經(jīng)被處理過了游昼,那么此時這個事務(wù)會回滾,這樣保證不會重復(fù)處理消息尝蠕;
B 系統(tǒng)執(zhí)行成功之后烘豌,就會更新自己本地消息表的狀態(tài)以及 A 系統(tǒng)消息表的狀態(tài);
如果 B 系統(tǒng)處理失敗了看彼,那么就不會更新消息表狀態(tài)廊佩,那么此時 A 系統(tǒng)會定時掃描自己的消息表囚聚,如果有未處理的消息,會再次發(fā)送到 MQ 中去标锄,讓 B 再次處理顽铸;
這個方案保證了最終一致性,哪怕 B 事務(wù)失敗了料皇,但是 A 會不斷重發(fā)消息谓松,直到 B 那邊成功為止。
這個方案說實話最大的問題就在于嚴重依賴于數(shù)據(jù)庫的消息表來管理事務(wù)啥的践剂,如果是高并發(fā)場景咋辦呢鬼譬?咋擴展呢?所以一般確實很少用逊脯。

image

分布式事務(wù)就是在分布式的場景下优质,需要滿足事務(wù)的需求!上篇文章我們聊過了消息中間件军洼,那這篇文章我們要聊的是分布式事務(wù)饵筑,把兩者一結(jié)合磕秤,便有了基于消息中間件的分布式事務(wù)解決方案诚撵!不管是本地事務(wù)材诽,還是分布式事務(wù),都是為了解決數(shù)據(jù)的一致性問題汗捡!一致性這個詞咱們前面多次提及淑际!與本地事務(wù)不同的是,分布式事務(wù)需要保證的是分布式環(huán)境下扇住,不同數(shù)據(jù)庫表中的數(shù)據(jù)的一致性問題春缕。分布式事務(wù)的解決方案有多種,如XA協(xié)議艘蹋、TCC三階段提交锄贼、基于消息隊列等等,本文只會涉及基于消息隊列的解決方案女阀!

本地事務(wù)講到了一致性宅荤,分布式事務(wù)不可避免的面臨著一致性的問題!回到最開始跨行轉(zhuǎn)賬的例子浸策,如果A銀行用戶向B銀行用戶轉(zhuǎn)賬冯键,正常流程應(yīng)該是:

1、A銀行對轉(zhuǎn)出賬戶執(zhí)行檢查校驗庸汗,進行金額扣減惫确。
2、A銀行同步調(diào)用B銀行轉(zhuǎn)賬接口。
3改化、B銀行對轉(zhuǎn)入賬戶進行檢查校驗掩蛤,進行金額增加。
4陈肛、B銀行返回處理結(jié)果給A銀行揍鸟。

image

在正常情況對一致性要求不高的場景,這樣的設(shè)計是可以滿足需求的燥爷。但是像銀行這樣的系統(tǒng)蜈亩,如果這樣實現(xiàn)大概早就破產(chǎn)了吧懦窘。我們先看看這樣的設(shè)計最主要的問題:

1前翎、同步調(diào)用遠程接口,如果接口比較耗時畅涂,會導(dǎo)致主線程阻塞時間較長港华。
2、流量不能很好控制午衰,A銀行系統(tǒng)的流量高峰可能壓垮B銀行系統(tǒng)(當然B銀行肯定會有自己的限流機制)立宜。
3、如果“第1步”剛執(zhí)行完臊岸,系統(tǒng)由于某種原因宕機了橙数,那會導(dǎo)致A銀行賬戶扣款了,但是B銀行沒有收到接口的調(diào)用帅戒,這就出現(xiàn)了兩個系統(tǒng)數(shù)據(jù)的不一致灯帮。
4、如果在執(zhí)行“第3步”后逻住,B銀行由于某種原因宕機了而無法正確回應(yīng)請求(實際上轉(zhuǎn)賬操作在B銀行系統(tǒng)已經(jīng)執(zhí)行且入庫)钟哥,這時候A銀行等待接口響應(yīng)會異常,誤以為轉(zhuǎn)賬失敗而回滾“第1步”操作瞎访,這也會出現(xiàn)了兩個系統(tǒng)數(shù)據(jù)的不一致腻贰。

對于問題的1、2都很好解決扒秸,如果對消息隊列熟悉的朋友應(yīng)該很快能想到可以引入消息中間件進行異步和削峰處理播演,于是又重新設(shè)計了一個方案,流程如下:

1伴奥、A銀行對賬戶進行檢查校驗写烤,進行金額扣減。
2渔伯、將對B銀行的請求異步寫入隊列顶霞,主線程返回。
3、啟動后臺程序從隊列獲取待處理數(shù)據(jù)选浑。
4蓝厌、后臺程序?qū)銀行接口進行遠程調(diào)用。
5古徒、B銀行對轉(zhuǎn)入賬戶進行檢查校驗拓提,進行金額增加。
6隧膘、B銀行處理完成回調(diào)A銀行接口通知處理結(jié)果代态。

image

通過上面的圖我們能看到,引入消息隊列后疹吃,系統(tǒng)的復(fù)雜性瞬間提升了蹦疑,雖然彌補了我們第一種方案的幾個不足點,但也帶來了更多的問題萨驶,比如消息隊列系統(tǒng)本身的可用性歉摧、消息隊列的延遲等等!并且腔呜,這樣的設(shè)計依然沒有解決我們面臨的核心問題-數(shù)據(jù)的一致性叁温!

1、如果“第1步”剛執(zhí)行完核畴,系統(tǒng)由于某種原因宕機了膝但,那會導(dǎo)致A銀行賬戶扣款了,但是寫入消息隊列失敗谤草,無法進行B銀行接口調(diào)用跟束,從而導(dǎo)致數(shù)據(jù)不一致。
2咖刃、如果B銀行在執(zhí)行“第5步”時由于校驗失敗而未能成功轉(zhuǎn)賬泳炉,在回調(diào)A銀行接口通知回滾時網(wǎng)絡(luò)異常或者宕機嚎杨,會導(dǎo)致A銀行轉(zhuǎn)賬無法完成回滾花鹅,從而導(dǎo)致數(shù)據(jù)不一致。

面對上述問題枫浙,我們不得不對系統(tǒng)再次進行升級改造刨肃。為了解決“A銀行賬戶扣款了,但是寫入消息隊列失敗”的問題箩帚,我們需要借助一個轉(zhuǎn)賬日志表真友,或者叫轉(zhuǎn)賬流水表,該表簡單的設(shè)計如下:

image

這個流水表需要怎么用呢紧帕?我們在“第1步”進行扣款時盔然,同時往流水表寫入一條操作流水桅打,狀態(tài)為“待處理”,并且這兩個操作必須是原子的愈案,也就是說必須通過本地事務(wù)保證這兩個操作要么同時成功挺尾,要么同時失敗站绪!這就保證了只要轉(zhuǎn)賬扣款成功遭铺,必定會記錄一條狀態(tài)為“待處理”的轉(zhuǎn)賬流水。如果在這一步失敗了恢准,那自然就是轉(zhuǎn)賬失敗魂挂,沒有后續(xù)操作了。如果這步操作后系統(tǒng)宕機了導(dǎo)致沒有將消息成功寫入消息隊列(也就是“第2步”)也沒關(guān)系馁筐,因為我們的流水數(shù)據(jù)已經(jīng)持久化了涂召!這時候我們只需要加入一個后臺線程進行補償,定期的從轉(zhuǎn)賬流水表中讀取狀態(tài)為“待處理”且最后更新的時間距當前時間大于某個閾值的數(shù)據(jù)眯漩,重新放入消息隊列進行補償芹扭。這樣麻顶,就保證了消息即使丟失赦抖,也會有補償機制!B銀行在處理完轉(zhuǎn)賬請求后會回調(diào)A銀行的接口通知轉(zhuǎn)賬的狀態(tài)辅肾,從而更新A銀行流水表中的狀態(tài)字段队萤!這樣就完美解決了上一個方案中的兩個不足點。系統(tǒng)設(shè)計圖如下:

image

到目前為止矫钓,我們很好的解決了消息丟失的問題要尔,保證了只要A銀行轉(zhuǎn)賬操作成功,轉(zhuǎn)賬的請求就一定能發(fā)送到B銀行新娜!但是該方案又引入了一個問題赵辕,通過后臺線程輪詢將消息放入消息隊列處理,同一次轉(zhuǎn)賬請求可能會出現(xiàn)多次放入消息隊列而多次消費的情況概龄,這樣B銀行會對同一轉(zhuǎn)賬多次處理導(dǎo)致數(shù)據(jù)出現(xiàn)不一致还惠!那怎么保證B銀行轉(zhuǎn)賬接口的冪等性呢?

同樣的私杜,我們可以在B銀行系統(tǒng)中需要增加一個轉(zhuǎn)賬日志表蚕键,或者叫轉(zhuǎn)賬流水表,B銀行每次接收到轉(zhuǎn)賬請求衰粹,在對賬戶進行操作的時候同時往轉(zhuǎn)賬日志表中插入一條轉(zhuǎn)賬日志記錄锣光,同樣這兩個操作也必須是原子的!在接收到轉(zhuǎn)賬請求后铝耻,首先根據(jù)唯一轉(zhuǎn)賬流水Id在日志表中查找判斷該轉(zhuǎn)賬是否已經(jīng)處理過誊爹,如果未處理過則進行處理,否則直接回調(diào)返回! 最終的架構(gòu)圖如下:

image

所以频丘,我們這里最核心的就是A銀行通過本地事務(wù)保證日志記錄+后臺線程輪詢保證消息不丟失箍铭。B銀行通過本地事務(wù)保證日志記錄從而保證消息不重復(fù)消費!B銀行在回調(diào)A銀行的接口時會通知處理結(jié)果椎镣,如果轉(zhuǎn)賬失敗诈火,A銀行會根據(jù)處理結(jié)果進行回滾。


4.可靠消息最終一致性方案

這個的意思状答,就是干脆不要用本地的消息表了冷守,直接基于 MQ 來實現(xiàn)事務(wù)。比如阿里的 RocketMQ 就支持消息事務(wù)惊科。

大概的意思就是:

  • A 系統(tǒng)先發(fā)送一個 prepared 消息到 mq拍摇,如果這個 prepared 消息發(fā)送失敗那么就直接取消操作別執(zhí)行了;
  • 如果這個消息發(fā)送成功過了馆截,那么接著執(zhí)行本地事務(wù)充活,如果成功就告訴 mq 發(fā)送確認消息,如果失敗就告訴 mq 回滾消息蜡娶;
  • 如果發(fā)送了確認消息混卵,那么此時 B 系統(tǒng)會接收到確認消息,然后執(zhí)行本地的事務(wù)窖张;
  • mq 會自動定時輪詢所有 prepared 消息回調(diào)你的接口幕随,問你,這個消息是不是本地事務(wù)處理失敗了宿接,所有沒發(fā)送確認的消息赘淮,是繼續(xù)重試還是回滾?一般來說這里你就可以查下數(shù)據(jù)庫看之前本地事務(wù)是否執(zhí)行睦霎,如果回滾了梢卸,那么這里也回滾吧。這個就是避免可能本地事務(wù)執(zhí)行成功了副女,而確認消息卻發(fā)送失敗了蛤高。
  • 這個方案里,要是系統(tǒng) B 的事務(wù)失敗了咋辦肮塞?重試咯襟齿,自動不斷重試直到成功,如果實在是不行枕赵,要么就是針對重要的資金類業(yè)務(wù)進行回滾猜欺,比如 B 系統(tǒng)本地回滾后,想辦法通知系統(tǒng) A 也回滾拷窜;或者是發(fā)送報警由人工來手工回滾和補償开皿。
  • 這個還是比較合適的涧黄,目前國內(nèi)互聯(lián)網(wǎng)公司大都是這么玩兒的,要不你舉用 RocketMQ 支持的赋荆,要不你就自己基于類似 ActiveMQ笋妥?RabbitMQ?自己封裝一套類似的邏輯出來窄潭,總之思路就是這樣子的春宣。
image

5.最大努力通知方案

這個方案的大致意思就是:

  • 系統(tǒng) A 本地事務(wù)執(zhí)行完之后,發(fā)送個消息到 MQ嫉你;
  • 這里會有個專門消費 MQ 的最大努力通知服務(wù)月帝,這個服務(wù)會消費 MQ 然后寫入數(shù)據(jù)庫中記錄下來,或者是放入個內(nèi)存隊列也可以幽污,接著調(diào)用系統(tǒng) B 的接口嚷辅;
  • 要是系統(tǒng) B 執(zhí)行成功就 ok 了;要是系統(tǒng) B 執(zhí)行失敗了距误,那么最大努力通知服務(wù)就定時嘗試重新調(diào)用系統(tǒng) B簸搞,反復(fù) N 次,最后還是不行就放棄准潭。

你們公司是如何處理分布式事務(wù)的趁俊?

如果你真的被問到,可以這么說惋鹅,我們某某特別嚴格的場景则酝,用的是 TCC 來保證強一致性;然后其他的一些場景基于阿里的 RocketMQ 來實現(xiàn)分布式事務(wù)闰集。

你找一個嚴格資金要求絕對不能錯的場景,你可以說你是用的 TCC 方案般卑;如果是一般的分布式事務(wù)場景武鲁,訂單插入之后要調(diào)用庫存服務(wù)更新庫存,庫存數(shù)據(jù)沒有資金那么的敏感蝠检,可以用可靠消息最終一致性方案沐鼠。

友情提示一下,RocketMQ 3.2.6 之前的版本叹谁,是可以按照上面的思路來的饲梭,但是之后接口做了一些改變,我這里不再贅述了焰檩。

當然如果你愿意憔涉,你可以參考可靠消息最終一致性方案來自己實現(xiàn)一套分布式事務(wù),比如基于 RocketMQ 來玩兒析苫。

當然兜叨,分布式事務(wù)最好的解決方案是盡量避免出現(xiàn)分布式事務(wù)穿扳!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市国旷,隨后出現(xiàn)的幾起案子矛物,更是在濱河造成了極大的恐慌,老刑警劉巖跪但,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件履羞,死亡現(xiàn)場離奇詭異,居然都是意外死亡屡久,警方通過查閱死者的電腦和手機吧雹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涂身,“玉大人雄卷,你說我怎么就攤上這事「蚴郏” “怎么了丁鹉?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長悴能。 經(jīng)常有香客問我揣钦,道長,這世上最難降的妖魔是什么漠酿? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任冯凹,我火速辦了婚禮,結(jié)果婚禮上炒嘲,老公的妹妹穿的比我還像新娘宇姚。我一直安慰自己,他們只是感情好夫凸,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布浑劳。 她就那樣靜靜地躺著,像睡著了一般夭拌。 火紅的嫁衣襯著肌膚如雪魔熏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天鸽扁,我揣著相機與錄音蒜绽,去河邊找鬼。 笑死桶现,一個胖子當著我的面吹牛躲雅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播巩那,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吏夯,長吁一口氣:“原來是場噩夢啊……” “哼此蜈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起噪生,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤裆赵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后跺嗽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體战授,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年桨嫁,在試婚紗的時候發(fā)現(xiàn)自己被綠了植兰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡璃吧,死狀恐怖楣导,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情畜挨,我是刑警寧澤筒繁,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站巴元,受9級特大地震影響毡咏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逮刨,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一呕缭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧修己,春花似錦恢总、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至戴涝,卻和暖如春钻蔑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咪笑。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留窗怒,地道東北人映跟。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像努隙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子荸镊,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容