分布式事務(wù)之TCC

業(yè)務(wù)場(chǎng)景介紹

假設(shè)現(xiàn)在有一個(gè)電商系統(tǒng)法褥,里面有一個(gè)支付訂單的場(chǎng)景检吆。對(duì)一個(gè)訂單支付之后薪捍,我們需要做下面的步驟:

  1. 更改訂單的狀態(tài)為“已支付”
  2. 扣減商品庫(kù)存
  3. 給會(huì)員增加積分
  4. 創(chuàng)建銷售出庫(kù)單通知倉(cāng)庫(kù)發(fā)貨


    訂單支付業(yè)務(wù).png

業(yè)務(wù)場(chǎng)景分析

業(yè)務(wù)場(chǎng)景有了币他,現(xiàn)在我們要更進(jìn)一步尔当,實(shí)現(xiàn)一個(gè)TCC分布式事務(wù)的效果莲祸。也就是訂單服務(wù)-修改訂單狀態(tài),庫(kù)存服務(wù)-扣減庫(kù)存椭迎,積分服務(wù)-增加積分锐帜,倉(cāng)儲(chǔ)服務(wù)-創(chuàng)建銷售出庫(kù)單。這幾個(gè)步驟畜号,要么一起成功缴阎,要么一起失敗,必須是一個(gè)整體性的事務(wù)简软。
我們有必要使用TCC分布式事務(wù)機(jī)制來保證各個(gè)服務(wù)形成一個(gè)整體性的事務(wù)蛮拔。上面那幾個(gè)步驟,要么全部成功痹升,如果任何一個(gè)服務(wù)的操作失敗了建炫,就全部一起回滾,撤銷已經(jīng)完成的操作疼蛾。比如說庫(kù)存服務(wù)要是扣減庫(kù)存失敗了肛跌,那么訂單服務(wù)就得撤銷那個(gè)修改訂單狀態(tài)的操作,然后得停止執(zhí)行增加積分和通知出庫(kù)兩個(gè)操作察郁。

落地實(shí)現(xiàn)TCC分布式事務(wù)

TCC實(shí)現(xiàn)階段一:Try

  1. 首先衍慎,訂單服務(wù)那兒,他的代碼大致來說應(yīng)該是這樣子的:


    訂單服務(wù)業(yè)務(wù)邏輯.jpg
  2. 增加Try邏輯
  • 首先绳锅,訂單服務(wù)先把自己的狀態(tài)修改為:OrderStatus.UPDATING西饵。也就是說,在pay()那個(gè)方法里鳞芙,你別直接把訂單狀態(tài)修改為已支付眷柔!你先把訂單狀態(tài)修改為UPDATING期虾,也就是修改中的意思。這個(gè)狀態(tài)是個(gè)沒有任何含義的這么一個(gè)狀態(tài)驯嘱,代表有人正在修改這個(gè)狀態(tài)镶苞。
  • 然后,庫(kù)存服務(wù)直接提供的那個(gè)reduceStock()接口里鞠评,也別直接扣減庫(kù)存啊茂蚓,你可以是凍結(jié)掉庫(kù)存。
    舉個(gè)例子剃幌,本來你的庫(kù)存數(shù)量是100聋涨,你別直接100 - 2 = 98,扣減這個(gè)庫(kù)存负乡!
    你可以把可銷售的庫(kù)存:100 - 2 = 98牍白,設(shè)置為98沒問題,然后在一個(gè)單獨(dú)的凍結(jié)庫(kù)存的字段里抖棘,設(shè)置一個(gè)2茂腥。也就是說,有2個(gè)庫(kù)存是給凍結(jié)了切省。
  • 積分服務(wù)的addCredit()接口也是同理最岗,別直接給用戶增加會(huì)員積分。你可以先在積分表里的一個(gè)預(yù)增加積分字段加入積分朝捆。
    比如:用戶積分原本是1190般渡,現(xiàn)在要增加10個(gè)積分,別直接1190 + 10 = 1200個(gè)積分坝冶摹诊杆!
    你可以保持積分為1190不變,在一個(gè)預(yù)增加字段里何陆,比如說prepare_add_credit字段,設(shè)置一個(gè)10豹储,表示有10個(gè)積分準(zhǔn)備增加贷盲。
  • 倉(cāng)儲(chǔ)服務(wù)的saleDelivery()接口也是同理啊,你可以先創(chuàng)建一個(gè)銷售出庫(kù)單剥扣,但是這個(gè)銷售出庫(kù)單的狀態(tài)是“UNKNOWN”巩剖。也就是說,剛剛創(chuàng)建這個(gè)銷售出庫(kù)單钠怯,此時(shí)還不確定他的狀態(tài)是什么呢佳魔!
  • 上面這套改造接口的過程,其實(shí)就是所謂的TCC分布式事務(wù)中的第一個(gè)T字母代表的階段晦炊,也就是Try階段鞠鲜。
  • 總結(jié)上述過程宁脊,如果你要實(shí)現(xiàn)一個(gè)TCC分布式事務(wù),首先你的業(yè)務(wù)的主流程以及各個(gè)接口提供的業(yè)務(wù)含義贤姆,不是說直接完成那個(gè)業(yè)務(wù)操作榆苞,而是完成一個(gè)Try的操作。
  • 這個(gè)操作霞捡,一般都是鎖定某個(gè)資源坐漏,設(shè)置一個(gè)預(yù)備類的狀態(tài),凍結(jié)部分?jǐn)?shù)據(jù)碧信,等等赊琳,大概都是這類操作。


    TCC之T.png

TCC實(shí)現(xiàn)階段二:Confirm

  • 然后就分成兩種情況了砰碴,第一種情況是比較理想的慨畸,那就是各個(gè)服務(wù)執(zhí)行自己的那個(gè)Try操作,都執(zhí)行成功了衣式,bingo寸士!
  • 這個(gè)時(shí)候,就需要依靠TCC分布式事務(wù)框架來推動(dòng)后續(xù)的執(zhí)行了碴卧。
  • 如果你要玩兒TCC分布式事務(wù)弱卡,必須引入一款TCC分布式事務(wù)框架,比如國(guó)內(nèi)開源的ByteTCC住册、himly婶博、tcc-transaction。
  • 否則的話荧飞,感知各個(gè)階段的執(zhí)行情況以及推進(jìn)執(zhí)行下一個(gè)階段的這些事情凡人,不太可能自己手寫實(shí)現(xiàn),太復(fù)雜了叹阔。
  • 如果你在各個(gè)服務(wù)里引入了一個(gè)TCC分布式事務(wù)的框架挠轴,訂單服務(wù)里內(nèi)嵌的那個(gè)TCC分布式事務(wù)框架可以感知到,各個(gè)服務(wù)的Try操作都成功了耳幢。
  • 此時(shí)岸晦,TCC分布式事務(wù)框架會(huì)控制進(jìn)入TCC下一個(gè)階段,第一個(gè)C階段睛藻,也就是Confirm階段启上。
  • 為了實(shí)現(xiàn)這個(gè)階段,你需要在各個(gè)服務(wù)里再加入一些代碼店印。
  • 比如說冈在,訂單服務(wù)里,你可以加入一個(gè)Confirm的邏輯按摘,就是正式把訂單的狀態(tài)設(shè)置為“已支付”了包券。
  • 庫(kù)存服務(wù)也是類似的纫谅,你可以有一個(gè)InventoryServiceConfirm類,里面提供一個(gè)reduceStock()接口的Confirm邏輯兴使,這里就是將之前凍結(jié)庫(kù)存字段的2個(gè)庫(kù)存扣掉變?yōu)?系宜。這樣的話,可銷售庫(kù)存之前就已經(jīng)變?yōu)?8了发魄,現(xiàn)在凍結(jié)的2個(gè)庫(kù)存也沒了盹牧,那就正式完成了庫(kù)存的扣減。
  • 積分服務(wù)也是類似的励幼,可以在積分服務(wù)里提供一個(gè)CreditServiceConfirm類汰寓,里面有一個(gè)addCredit()接口的Confirm邏輯,就是將預(yù)增加字段的10個(gè)積分扣掉苹粟,然后加入實(shí)際的會(huì)員積分字段中有滑,從1190變?yōu)?120。
  • 倉(cāng)儲(chǔ)服務(wù)也是類似嵌削,可以在倉(cāng)儲(chǔ)服務(wù)中提供一個(gè)WmsServiceConfirm類毛好,提供一個(gè)saleDelivery()接口的Confirm邏輯,將銷售出庫(kù)單的狀態(tài)正式修改為“已創(chuàng)建”苛秕,可以供倉(cāng)儲(chǔ)管理人員查看和使用肌访,而不是停留在之前的中間狀態(tài)“UNKNOWN”了。
  • 上面各種服務(wù)的Confirm的邏輯都實(shí)現(xiàn)好了艇劫,一旦訂單服務(wù)里面的TCC分布式事務(wù)框架感知到各個(gè)服務(wù)的Try階段都成功了以后吼驶,就會(huì)執(zhí)行各個(gè)服務(wù)的Confirm邏輯。
  • 訂單服務(wù)內(nèi)的TCC事務(wù)框架會(huì)負(fù)責(zé)跟其他各個(gè)服務(wù)內(nèi)的TCC事務(wù)框架進(jìn)行通信店煞,依次調(diào)用各個(gè)服務(wù)的Confirm邏輯蟹演。然后,正式完成各個(gè)服務(wù)的所有業(yè)務(wù)邏輯的執(zhí)行顷蟀。


    TCC之C.png

TCC實(shí)現(xiàn)階段三:Cancel

舉個(gè)例子:在Try階段酒请,比如積分服務(wù)吧,他執(zhí)行出錯(cuò)了衩椒,此時(shí)會(huì)怎么樣蚌父?

  • 那訂單服務(wù)內(nèi)的TCC事務(wù)框架是可以感知到的,然后他會(huì)決定對(duì)整個(gè)TCC分布式事務(wù)進(jìn)行回滾毛萌。
  • 也就是說,會(huì)執(zhí)行各個(gè)服務(wù)的第二個(gè)C階段喝滞,Cancel階段阁将。
  • 同樣,為了實(shí)現(xiàn)這個(gè)Cancel階段右遭,各個(gè)服務(wù)還得加一些代碼做盅。
  • 首先訂單服務(wù)缤削,他得提供一個(gè)OrderServiceCancel的類,在里面有一個(gè)pay()接口的Cancel邏輯吹榴,就是可以將訂單的狀態(tài)設(shè)置為“CANCELED”亭敢,也就是這個(gè)訂單的狀態(tài)是已取消。
  • 庫(kù)存服務(wù)也是同理图筹,可以提供reduceStock()的Cancel邏輯帅刀,就是將凍結(jié)庫(kù)存扣減掉2,加回到可銷售庫(kù)存里去远剩,98 + 2 = 100扣溺。
  • 積分服務(wù)也需要提供addCredit()接口的Cancel邏輯,將預(yù)增加積分字段的10個(gè)積分扣減掉瓜晤。
  • 倉(cāng)儲(chǔ)服務(wù)也需要提供一個(gè)saleDelivery()接口的Cancel邏輯锥余,將銷售出庫(kù)單的狀態(tài)修改為“CANCELED”設(shè)置為已取消。
  • 然后這個(gè)時(shí)候痢掠,訂單服務(wù)的TCC分布式事務(wù)框架只要感知到了任何一個(gè)服務(wù)的Try邏輯失敗了驱犹,就會(huì)跟各個(gè)服務(wù)內(nèi)的TCC分布式事務(wù)框架進(jìn)行通信,然后調(diào)用各個(gè)服務(wù)的Cancel邏輯足画。


    TCC之CC.png

總結(jié)與思考

總結(jié)一下雄驹,你要玩兒TCC分布式事務(wù)的話:

  1. 首先需要選擇某種TCC分布式事務(wù)框架,各個(gè)服務(wù)里就會(huì)有這個(gè)TCC分布式事務(wù)框架在運(yùn)行锌云。
  2. 然后你原本的一個(gè)接口荠医,要改造為3個(gè)邏輯,Try-Confirm-Cancel桑涎。
  • 先是服務(wù)調(diào)用鏈路依次執(zhí)行Try邏輯
  • 如果都正常的話彬向,TCC分布式事務(wù)框架推進(jìn)執(zhí)行Confirm邏輯,完成整個(gè)事務(wù)
  • 如果某個(gè)服務(wù)的Try邏輯有問題攻冷,TCC分布式事務(wù)框架感知到之后就會(huì)推進(jìn)執(zhí)行各個(gè)服務(wù)的Cancel邏輯娃胆,撤銷之前執(zhí)行的各種操作。
  • 這就是所謂的TCC分布式事務(wù)等曼。
  • TCC分布式事務(wù)的核心思想里烦,說白了,就是當(dāng)遇到下面這些情況時(shí)禁谦,
  1. 某個(gè)服務(wù)的數(shù)據(jù)庫(kù)宕機(jī)了
  2. 某個(gè)服務(wù)自己掛了
  3. 那個(gè)服務(wù)的redis胁黑、elasticsearch、MQ等基礎(chǔ)設(shè)施故障了
  4. 某些資源不足了州泊,比如說庫(kù)存不夠這些
  • 先來Try一下丧蘸,不要把業(yè)務(wù)邏輯完成,先試試看遥皂,看各個(gè)服務(wù)能不能基本正常運(yùn)轉(zhuǎn)力喷,能不能先凍結(jié)我需要的資源刽漂。
  • 如果Try都o(jì)k,也就是說弟孟,底層的數(shù)據(jù)庫(kù)贝咙、redis、elasticsearch拂募、MQ都是可以寫入數(shù)據(jù)的庭猩,并且你保留好了需要使用的一些資源(比如凍結(jié)了一部分庫(kù)存)。
  • 接著没讲,再執(zhí)行各個(gè)服務(wù)的Confirm邏輯眯娱,基本上Confirm就可以很大概率保證一個(gè)分布式事務(wù)的完成了。
  • 那如果Try階段某個(gè)服務(wù)就失敗了爬凑,比如說底層的數(shù)據(jù)庫(kù)掛了徙缴,或者redis掛了,等等嘁信。
  • 此時(shí)就自動(dòng)執(zhí)行各個(gè)服務(wù)的Cancel邏輯于样,把之前的Try邏輯都回滾,所有服務(wù)都不要執(zhí)行任何設(shè)計(jì)的業(yè)務(wù)邏輯潘靖。保證大家要么一起成功穿剖,要么一起失敗。

終極大招

  • 如果有一些意外的情況發(fā)生了卦溢,比如說訂單服務(wù)突然掛了糊余,然后再次重啟,TCC分布式事務(wù)框架是如何保證之前沒執(zhí)行完的分布式事務(wù)繼續(xù)執(zhí)行的呢单寂?
  • TCC事務(wù)框架都是要記錄一些分布式事務(wù)的活動(dòng)日志的贬芥,可以在磁盤上的日志文件里記錄,也可以在數(shù)據(jù)庫(kù)里記錄宣决。保存下來分布式事務(wù)運(yùn)行的各個(gè)階段和狀態(tài)蘸劈。
  • 萬一某個(gè)服務(wù)的Cancel或者Confirm邏輯執(zhí)行一直失敗怎么辦呢?
  • 那也很簡(jiǎn)單尊沸,TCC事務(wù)框架會(huì)通過活動(dòng)日志記錄各個(gè)服務(wù)的狀態(tài)威沫。
  • 舉個(gè)例子,比如發(fā)現(xiàn)某個(gè)服務(wù)的Cancel或者Confirm一直沒成功洼专,會(huì)不停的重試調(diào)用他的Cancel或者Confirm邏輯棒掠,務(wù)必要他成功!
  • 當(dāng)然了屁商,如果你的代碼沒有寫什么bug句柠,有充足的測(cè)試,而且Try階段都基本嘗試了一下棒假,那么其實(shí)一般Confirm溯职、Cancel都是可以成功的!


    事務(wù)活動(dòng)日志.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帽哑,一起剝皮案震驚了整個(gè)濱河市谜酒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妻枕,老刑警劉巖僻族,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異屡谐,居然都是意外死亡述么,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門愕掏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來度秘,“玉大人,你說我怎么就攤上這事饵撑〗J幔” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵滑潘,是天一觀的道長(zhǎng)垢乙。 經(jīng)常有香客問我,道長(zhǎng)语卤,這世上最難降的妖魔是什么追逮? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮粹舵,結(jié)果婚禮上钮孵,老公的妹妹穿的比我還像新娘。我一直安慰自己齐婴,他們只是感情好油猫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柠偶,像睡著了一般情妖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诱担,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天毡证,我揣著相機(jī)與錄音,去河邊找鬼蔫仙。 笑死料睛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播恤煞,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼屎勘,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了居扒?” 一聲冷哼從身側(cè)響起概漱,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喜喂,沒想到半個(gè)月后瓤摧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡玉吁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年照弥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片进副。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡这揣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出敢会,到底是詐尸還是另有隱情曾沈,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布鸥昏,位于F島的核電站塞俱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吏垮。R本人自食惡果不足惜障涯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望膳汪。 院中可真熱鬧唯蝶,春花似錦、人聲如沸遗嗽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痹换。三九已至征字,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間娇豫,已是汗流浹背匙姜。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冯痢,地道東北人氮昧。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓框杜,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親袖肥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咪辱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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