分布式事務(wù)之TCC

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

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

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


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

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

業(yè)務(wù)場景有了,現(xiàn)在我們要更進(jìn)一步,實現(xiàn)一個TCC分布式事務(wù)的效果。也就是訂單服務(wù)-修改訂單狀態(tài),庫存服務(wù)-扣減庫存,積分服務(wù)-增加積分,倉儲服務(wù)-創(chuàng)建銷售出庫單。這幾個步驟,要么一起成功,要么一起失敗,必須是一個整體性的事務(wù)。
我們有必要使用TCC分布式事務(wù)機制來保證各個服務(wù)形成一個整體性的事務(wù)。上面那幾個步驟,要么全部成功,如果任何一個服務(wù)的操作失敗了,就全部一起回滾,撤銷已經(jīng)完成的操作。比如說庫存服務(wù)要是扣減庫存失敗了,那么訂單服務(wù)就得撤銷那個修改訂單狀態(tài)的操作,然后得停止執(zhí)行增加積分和通知出庫兩個操作。

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

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

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


    訂單服務(wù)業(yè)務(wù)邏輯.jpg
  2. 增加Try邏輯
  • 首先,訂單服務(wù)先把自己的狀態(tài)修改為:OrderStatus.UPDATING。也就是說,在pay()那個方法里,你別直接把訂單狀態(tài)修改為已支付!你先把訂單狀態(tài)修改為UPDATING,也就是修改中的意思。這個狀態(tài)是個沒有任何含義的這么一個狀態(tài),代表有人正在修改這個狀態(tài)。
  • 然后,庫存服務(wù)直接提供的那個reduceStock()接口里,也別直接扣減庫存啊,你可以是凍結(jié)掉庫存。
    舉個例子,本來你的庫存數(shù)量是100,你別直接100 - 2 = 98,扣減這個庫存!
    你可以把可銷售的庫存:100 - 2 = 98,設(shè)置為98沒問題,然后在一個單獨的凍結(jié)庫存的字段里,設(shè)置一個2。也就是說,有2個庫存是給凍結(jié)了。
  • 積分服務(wù)的addCredit()接口也是同理,別直接給用戶增加會員積分。你可以先在積分表里的一個預(yù)增加積分字段加入積分。
    比如:用戶積分原本是1190,現(xiàn)在要增加10個積分,別直接1190 + 10 = 1200個積分?。?br> 你可以保持積分為1190不變,在一個預(yù)增加字段里,比如說prepare_add_credit字段,設(shè)置一個10,表示有10個積分準(zhǔn)備增加。
  • 倉儲服務(wù)的saleDelivery()接口也是同理啊,你可以先創(chuàng)建一個銷售出庫單,但是這個銷售出庫單的狀態(tài)是“UNKNOWN”。也就是說,剛剛創(chuàng)建這個銷售出庫單,此時還不確定他的狀態(tài)是什么呢!
  • 上面這套改造接口的過程,其實就是所謂的TCC分布式事務(wù)中的第一個T字母代表的階段,也就是Try階段。
  • 總結(jié)上述過程,如果你要實現(xiàn)一個TCC分布式事務(wù),首先你的業(yè)務(wù)的主流程以及各個接口提供的業(yè)務(wù)含義,不是說直接完成那個業(yè)務(wù)操作,而是完成一個Try的操作。
  • 這個操作,一般都是鎖定某個資源,設(shè)置一個預(yù)備類的狀態(tài),凍結(jié)部分?jǐn)?shù)據(jù),等等,大概都是這類操作。


    TCC之T.png

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

  • 然后就分成兩種情況了,第一種情況是比較理想的,那就是各個服務(wù)執(zhí)行自己的那個Try操作,都執(zhí)行成功了,bingo!
  • 這個時候,就需要依靠TCC分布式事務(wù)框架來推動后續(xù)的執(zhí)行了。
  • 如果你要玩兒TCC分布式事務(wù),必須引入一款TCC分布式事務(wù)框架,比如國內(nèi)開源的ByteTCC、himly、tcc-transaction。
  • 否則的話,感知各個階段的執(zhí)行情況以及推進(jìn)執(zhí)行下一個階段的這些事情,不太可能自己手寫實現(xiàn),太復(fù)雜了。
  • 如果你在各個服務(wù)里引入了一個TCC分布式事務(wù)的框架,訂單服務(wù)里內(nèi)嵌的那個TCC分布式事務(wù)框架可以感知到,各個服務(wù)的Try操作都成功了。
  • 此時,TCC分布式事務(wù)框架會控制進(jìn)入TCC下一個階段,第一個C階段,也就是Confirm階段。
  • 為了實現(xiàn)這個階段,你需要在各個服務(wù)里再加入一些代碼。
  • 比如說,訂單服務(wù)里,你可以加入一個Confirm的邏輯,就是正式把訂單的狀態(tài)設(shè)置為“已支付”了。
  • 庫存服務(wù)也是類似的,你可以有一個InventoryServiceConfirm類,里面提供一個reduceStock()接口的Confirm邏輯,這里就是將之前凍結(jié)庫存字段的2個庫存扣掉變?yōu)?。這樣的話,可銷售庫存之前就已經(jīng)變?yōu)?8了,現(xiàn)在凍結(jié)的2個庫存也沒了,那就正式完成了庫存的扣減。
  • 積分服務(wù)也是類似的,可以在積分服務(wù)里提供一個CreditServiceConfirm類,里面有一個addCredit()接口的Confirm邏輯,就是將預(yù)增加字段的10個積分扣掉,然后加入實際的會員積分字段中,從1190變?yōu)?120。
  • 倉儲服務(wù)也是類似,可以在倉儲服務(wù)中提供一個WmsServiceConfirm類,提供一個saleDelivery()接口的Confirm邏輯,將銷售出庫單的狀態(tài)正式修改為“已創(chuàng)建”,可以供倉儲管理人員查看和使用,而不是停留在之前的中間狀態(tài)“UNKNOWN”了。
  • 上面各種服務(wù)的Confirm的邏輯都實現(xiàn)好了,一旦訂單服務(wù)里面的TCC分布式事務(wù)框架感知到各個服務(wù)的Try階段都成功了以后,就會執(zhí)行各個服務(wù)的Confirm邏輯。
  • 訂單服務(wù)內(nèi)的TCC事務(wù)框架會負(fù)責(zé)跟其他各個服務(wù)內(nèi)的TCC事務(wù)框架進(jìn)行通信,依次調(diào)用各個服務(wù)的Confirm邏輯。然后,正式完成各個服務(wù)的所有業(yè)務(wù)邏輯的執(zhí)行。


    TCC之C.png

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

舉個例子:在Try階段,比如積分服務(wù)吧,他執(zhí)行出錯了,此時會怎么樣?

  • 那訂單服務(wù)內(nèi)的TCC事務(wù)框架是可以感知到的,然后他會決定對整個TCC分布式事務(wù)進(jìn)行回滾。
  • 也就是說,會執(zhí)行各個服務(wù)的第二個C階段,Cancel階段。
  • 同樣,為了實現(xiàn)這個Cancel階段,各個服務(wù)還得加一些代碼。
  • 首先訂單服務(wù),他得提供一個OrderServiceCancel的類,在里面有一個pay()接口的Cancel邏輯,就是可以將訂單的狀態(tài)設(shè)置為“CANCELED”,也就是這個訂單的狀態(tài)是已取消。
  • 庫存服務(wù)也是同理,可以提供reduceStock()的Cancel邏輯,就是將凍結(jié)庫存扣減掉2,加回到可銷售庫存里去,98 + 2 = 100。
  • 積分服務(wù)也需要提供addCredit()接口的Cancel邏輯,將預(yù)增加積分字段的10個積分扣減掉。
  • 倉儲服務(wù)也需要提供一個saleDelivery()接口的Cancel邏輯,將銷售出庫單的狀態(tài)修改為“CANCELED”設(shè)置為已取消。
  • 然后這個時候,訂單服務(wù)的TCC分布式事務(wù)框架只要感知到了任何一個服務(wù)的Try邏輯失敗了,就會跟各個服務(wù)內(nèi)的TCC分布式事務(wù)框架進(jìn)行通信,然后調(diào)用各個服務(wù)的Cancel邏輯。


    TCC之CC.png

總結(jié)與思考

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

  1. 首先需要選擇某種TCC分布式事務(wù)框架,各個服務(wù)里就會有這個TCC分布式事務(wù)框架在運行。
  2. 然后你原本的一個接口,要改造為3個邏輯,Try-Confirm-Cancel。
  • 先是服務(wù)調(diào)用鏈路依次執(zhí)行Try邏輯
  • 如果都正常的話,TCC分布式事務(wù)框架推進(jìn)執(zhí)行Confirm邏輯,完成整個事務(wù)
  • 如果某個服務(wù)的Try邏輯有問題,TCC分布式事務(wù)框架感知到之后就會推進(jìn)執(zhí)行各個服務(wù)的Cancel邏輯,撤銷之前執(zhí)行的各種操作。
  • 這就是所謂的TCC分布式事務(wù)。
  • TCC分布式事務(wù)的核心思想,說白了,就是當(dāng)遇到下面這些情況時,
  1. 某個服務(wù)的數(shù)據(jù)庫宕機了
  2. 某個服務(wù)自己掛了
  3. 那個服務(wù)的redis、elasticsearch、MQ等基礎(chǔ)設(shè)施故障了
  4. 某些資源不足了,比如說庫存不夠這些
  • 先來Try一下,不要把業(yè)務(wù)邏輯完成,先試試看,看各個服務(wù)能不能基本正常運轉(zhuǎn),能不能先凍結(jié)我需要的資源。
  • 如果Try都o(jì)k,也就是說,底層的數(shù)據(jù)庫、redis、elasticsearch、MQ都是可以寫入數(shù)據(jù)的,并且你保留好了需要使用的一些資源(比如凍結(jié)了一部分庫存)。
  • 接著,再執(zhí)行各個服務(wù)的Confirm邏輯,基本上Confirm就可以很大概率保證一個分布式事務(wù)的完成了。
  • 那如果Try階段某個服務(wù)就失敗了,比如說底層的數(shù)據(jù)庫掛了,或者redis掛了,等等。
  • 此時就自動執(zhí)行各個服務(wù)的Cancel邏輯,把之前的Try邏輯都回滾,所有服務(wù)都不要執(zhí)行任何設(shè)計的業(yè)務(wù)邏輯。保證大家要么一起成功,要么一起失敗。

終極大招

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


    事務(wù)活動日志.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容