1、寫在前面git
以前網上看到不少寫分佈式事務的文章,不過大多都是將分佈式事務各類技術方案簡單介紹一下。不少朋友看了很多文章,仍是不知道分佈式事務到底怎麼回事,在項目裏到底如何使用。 github
因此我們這篇文章,就用大白話+手工繪圖,並結合一個電商系統的案例實踐,來給你們講清楚到底什麼是TCC分佈式事務。 面試
首先說一下,這裏可能會牽扯到一些Spring Cloud的原理,若是有不太清楚的同窗,能夠參考以前的文章:《拜託,面試請不要再問我Spring Cloud底層原理!》。redis
2、業務場景介紹數據庫
我們先來看看業務場景,假設你如今有一個電商系統,裏面有一個支付訂單的場景。架構
那對一個訂單支付以後,咱們須要作下面的步驟:框架
更改訂單的狀態爲「已支付」elasticsearch
扣減商品庫存分佈式
給會員增長積分微服務
建立銷售出庫單通知倉庫發貨
這是一系列比較真實的步驟,不管你們有沒有作過電商系統,應該都能理解。
3、進一步思考
好,業務場景有了,如今咱們要更進一步,實現一個TCC分佈式事務的效果。
什麼意思呢?也就是說,訂單服務-修改訂單狀態,庫存服務-扣減庫存,積分服務-增長積分,倉儲服務-建立銷售出庫單。
上述這幾個步驟,要麼一塊兒成功,要麼一塊兒失敗,必須是一個總體性的事務。
舉個例子,如今訂單的狀態都修改成「已支付」了,結果庫存服務扣減庫存失敗。那個商品的庫存原來是100件,如今賣掉了2件,原本應該是98件了。
結果呢?因爲庫存服務操做數據庫異常,致使庫存數量仍是100。這不是在坑人麼,固然不能容許這種狀況發生了!
可是若是你不用TCC分佈式事務方案的話,就用個Spring Cloud開發這麼一個微服務系統,頗有可能會幹出這種事兒來。
咱們來看看下面的這個圖,直觀的表達了上述的過程。
因此說,咱們有必要使用TCC分佈式事務機制來保證各個服務造成一個總體性的事務。
上面那幾個步驟,要麼所有成功,若是任何一個服務的操做失敗了,就所有一塊兒回滾,撤銷已經完成的操做。
好比說庫存服務要是扣減庫存失敗了,那麼訂單服務就得撤銷那個修改訂單狀態的操做,而後得中止執行增長積分和通知出庫兩個操做。
說了那麼多,老規矩,給你們上一張圖,大夥兒順着圖來直觀的感覺一下。
4、落地實現TCC分佈式事務
那麼如今到底要如何來實現一個TCC分佈式事務,使得各個服務,要麼一塊兒成功?要麼一塊兒失敗呢?
你們稍安勿躁,咱們這就來一步一步的分析一下。我們就以一個Spring Cloud開發系統做爲背景來解釋。
一、TCC實現階段一:Try
首先,訂單服務那兒,他的代碼大體來講應該是這樣子的:
若是你以前看過Spring Cloud架構原理那篇文章,同時對Spring Cloud有必定的瞭解的話,應該是能夠理解上面那段代碼的。
其實就是訂單服務完成本地數據庫操做以後,經過Spring Cloud的Feign來調用其餘的各個服務罷了。
可是光是憑藉這段代碼,是不足以實現TCC分佈式事務的啊?!兄弟們,彆着急,咱們對這個訂單服務修改點兒代碼好很差。
首先,上面那個訂單服務先把本身的狀態修改成:OrderStatus.UPDATING。
這是啥意思呢?也就是說,在pay()那個方法裏,你別直接把訂單狀態修改成已支付啊!你先把訂單狀態修改成UPDATING,也就是修改中的意思。
這個狀態是個沒有任何含義的這麼一個狀態,表明有人正在修改這個狀態罷了。
而後呢,庫存服務直接提供的那個reduceStock()接口裏,也別直接扣減庫存啊,你能夠是凍結掉庫存。
舉個例子,原本你的庫存數量是100,你別直接100 - 2 = 98,扣減這個庫存!
你能夠把可銷售的庫存:100 - 2 = 98,設置爲98沒問題,而後在一個單獨的凍結庫存的字段裏,設置一個2。也就是說,有2個庫存是給凍結了。
積分服務的addCredit()接口也是同理,別直接給用戶增長會員積分。你能夠先在積分表裏的一個預增長積分字段加入積分。
好比:用戶積分本來是1190,如今要增長10個積分,別直接1190 + 10 = 1200個積分啊!
你能夠保持積分爲1190不變,在一個預增長字段裏,好比說prepare_add_credit字段,設置一個10,表示有10個積分準備增長。
倉儲服務的saleDelivery()接口也是同理啊,你能夠先建立一個銷售出庫單,可是這個銷售出庫單的狀態是「UNKNOWN」。
也就是說,剛剛建立這個銷售出庫單,此時還不肯定他的狀態是什麼呢!
上面這套改造接口的過程,其實就是所謂的TCC分佈式事務中的第一個T字母表明的階段,也就是Try階段。
總結上述過程,若是你要實現一個TCC分佈式事務,首先你的業務的主流程以及各個接口提供的業務含義,不是說直接完成那個業務操做,而是完成一個Try的操做。
這個操做,通常都是鎖定某個資源,設置一個預備類的狀態,凍結部分數據,等等,大概都是這類操做。
我們來一塊兒看看下面這張圖,結合上面的文字,再來捋一捋這整個過程。
二、TCC實現階段二:Confirm
而後就分紅兩種狀況了,第一種狀況是比較理想的,那就是各個服務執行本身的那個Try操做,都執行成功了,bingo!
這個時候,就須要依靠TCC分佈式事務框架來推進後續的執行了。
這裏簡單提一句,若是你要玩兒TCC分佈式事務,必須引入一款TCC分佈式事務框架,好比國內開源的ByteTCC、himly、tcc-transaction。
不然的話,感知各個階段的執行狀況以及推動執行下一個階段的這些事情,不太可能本身手寫實現,太複雜了。
若是你在各個服務裏引入了一個TCC分佈式事務的框架,訂單服務裏內嵌的那個TCC分佈式事務框架能夠感知到,各個服務的Try操做都成功了。
此時,TCC分佈式事務框架會控制進入TCC下一個階段,第一個C階段,也就是Confirm階段。
爲了實現這個階段,你須要在各個服務裏再加入一些代碼。
好比說,訂單服務裏,你能夠加入一個Confirm的邏輯,就是正式把訂單的狀態設置爲「已支付」了,大概是相似下面這樣子:
庫存服務也是相似的,你能夠有一個InventoryServiceConfirm類,裏面提供一個reduceStock()接口的Confirm邏輯,這裏就是將以前凍結庫存字段的2個庫存扣掉變爲0。
這樣的話,可銷售庫存以前就已經變爲98了,如今凍結的2個庫存也沒了,那就正式完成了庫存的扣減。
積分服務也是相似的,能夠在積分服務裏提供一個CreditServiceConfirm類,裏面有一個addCredit()接口的Confirm邏輯,就是將預增長字段的10個積分扣掉,而後加入實際的會員積分字段中,從1190變爲1120。
倉儲服務也是相似,能夠在倉儲服務中提供一個WmsServiceConfirm類,提供一個saleDelivery()接口的Confirm邏輯,將銷售出庫單的狀態正式修改成「已建立」,能夠供倉儲管理人員查看和使用,而不是停留在以前的中間狀態「UNKNOWN」了。
好了,上面各類服務的Confirm的邏輯都實現好了,一旦訂單服務裏面的TCC分佈式事務框架感知到各個服務的Try階段都成功了之後,就會執行各個服務的Confirm邏輯。
訂單服務內的TCC事務框架會負責跟其餘各個服務內的TCC事務框架進行通訊,依次調用各個服務的Confirm邏輯。而後,正式完成各個服務的全部業務邏輯的執行。
一樣,給你們來一張圖,順着圖一塊兒來看看整個過程。
三、TCC實現階段三:Cancel
好,這是比較正常的一種狀況,那若是是異常的一種狀況呢?
舉個例子:在Try階段,好比積分服務吧,他執行出錯了,此時會怎麼樣?
那訂單服務內的TCC事務框架是能夠感知到的,而後他會決定對整個TCC分佈式事務進行回滾。
也就是說,會執行各個服務的第二個C階段,Cancel階段。
一樣,爲了實現這個Cancel階段,各個服務還得加一些代碼。
首先訂單服務,他得提供一個OrderServiceCancel的類,在裏面有一個pay()接口的Cancel邏輯,就是能夠將訂單的狀態設置爲「CANCELED」,也就是這個訂單的狀態是已取消。
庫存服務也是同理,能夠提供reduceStock()的Cancel邏輯,就是將凍結庫存扣減掉2,加回到可銷售庫存裏去,98 + 2 = 100。
積分服務也須要提供addCredit()接口的Cancel邏輯,將預增長積分字段的10個積分扣減掉。
倉儲服務也須要提供一個saleDelivery()接口的Cancel邏輯,將銷售出庫單的狀態修改成「CANCELED」設置爲已取消。
而後這個時候,訂單服務的TCC分佈式事務框架只要感知到了任何一個服務的Try邏輯失敗了,就會跟各個服務內的TCC分佈式事務框架進行通訊,而後調用各個服務的Cancel邏輯。
你們看看下面的圖,直觀的感覺一下。
5、總結與思考
好了,兄弟們,聊到這兒,基本上你們應該都知道TCC分佈式事務具體是怎麼回事了!
總結一下,你要玩兒TCC分佈式事務的話:
首先須要選擇某種TCC分佈式事務框架,各個服務裏就會有這個TCC分佈式事務框架在運行。
而後你本來的一個接口,要改造爲3個邏輯,Try-Confirm-Cancel。
先是服務調用鏈路依次執行Try邏輯
若是都正常的話,TCC分佈式事務框架推動執行Confirm邏輯,完成整個事務
若是某個服務的Try邏輯有問題,TCC分佈式事務框架感知到以後就會推動執行各個服務的Cancel邏輯,撤銷以前執行的各類操做
這就是所謂的TCC分佈式事務。
TCC分佈式事務的核心思想,說白了,就是當遇到下面這些狀況時,
某個服務的數據庫宕機了
某個服務本身掛了
那個服務的redis、elasticsearch、MQ等基礎設施故障了
某些資源不足了,好比說庫存不夠這些
先來Try一下,不要把業務邏輯完成,先試試看,看各個服務能不能基本正常運轉,能不能先凍結我須要的資源。
若是Try都ok,也就是說,底層的數據庫、redis、elasticsearch、MQ都是能夠寫入數據的,而且你保留好了須要使用的一些資源(好比凍結了一部分庫存)。
接着,再執行各個服務的Confirm邏輯,基本上Confirm就能夠很大機率保證一個分佈式事務的完成了。
那若是Try階段某個服務就失敗了,好比說底層的數據庫掛了,或者redis掛了,等等。
此時就自動執行各個服務的Cancel邏輯,把以前的Try邏輯都回滾,全部服務都不要執行任何設計的業務邏輯。保證你們要麼一塊兒成功,要麼一塊兒失敗。
寫到這裏,本文差很少該結束了。等一等,你有沒有想到一個問題?
若是有一些意外的狀況發生了,好比說訂單服務忽然掛了,而後再次重啓,TCC分佈式事務框架是如何保證以前沒執行完的分佈式事務繼續執行的呢?
因此,TCC事務框架都是要記錄一些分佈式事務的活動日誌的,能夠在磁盤上的日誌文件裏記錄,也能夠在數據庫裏記錄。保存下來分佈式事務運行的各個階段和狀態。
問題還沒完,萬一某個服務的Cancel或者Confirm邏輯執行一直失敗怎麼辦呢?
那也很簡單,TCC事務框架會經過活動日誌記錄各個服務的狀態。
舉個例子,好比發現某個服務的Cancel或者Confirm一直沒成功,會不停的重試調用他的Cancel或者Confirm邏輯,務必要他成功!
固然了,若是你的代碼沒有寫什麼bug,有充足的測試,並且Try階段都基本嘗試了一下,那麼其實通常Confirm、Cancel都是能夠成功的!
最後,再給你們來一張圖,來看看給咱們的業務,加上分佈式事務以後的整個執行流程:
很多大公司裏,其實都是本身研發TCC分佈式事務框架的,專門在公司內部使用,好比咱們就是這樣。
不過若是本身公司沒有研發TCC分佈式事務框架的話,那通常就會選用開源的框架。
這裏筆者給你們推薦幾個比較不錯的框架,都是我們國內本身開源出去的:ByteTCC,tcc-transaction,himly。
你們有興趣的能夠去他們的github地址,學習一下如何使用,以及如何跟Spring Cloud、Dubbo等服務框架整合使用。
只要把那些框架整合到你的系統裏,很容易就能夠實現上面那種奇妙的TCC分佈式事務的效果了。