業(yè)務(wù)場(chǎng)景介紹
假設(shè)現(xiàn)在有一個(gè)電商系統(tǒng)法褥,里面有一個(gè)支付訂單的場(chǎng)景检吆。對(duì)一個(gè)訂單支付之后薪捍,我們需要做下面的步驟:
- 更改訂單的狀態(tài)為“已支付”
- 扣減商品庫(kù)存
- 給會(huì)員增加積分
-
創(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
-
首先衍慎,訂單服務(wù)那兒,他的代碼大致來說應(yīng)該是這樣子的:
訂單服務(wù)業(yè)務(wù)邏輯.jpg - 增加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ù)的話:
- 首先需要選擇某種TCC分布式事務(wù)框架,各個(gè)服務(wù)里就會(huì)有這個(gè)TCC分布式事務(wù)框架在運(yùn)行锌云。
- 然后你原本的一個(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í)禁谦,
- 某個(gè)服務(wù)的數(shù)據(jù)庫(kù)宕機(jī)了
- 某個(gè)服務(wù)自己掛了
- 那個(gè)服務(wù)的redis胁黑、elasticsearch、MQ等基礎(chǔ)設(shè)施故障了
- 某些資源不足了州泊,比如說庫(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