1. 程式人生 > 程式設計 >分散式事務 TCC 兩階段補償

分散式事務 TCC 兩階段補償

原文

TCC方案是可能是目前最火的一種柔性事務方案了。關於TCC(Try-Confirm-Cancel)的概念,最早是由Pat Helland於2007年發表的一篇名為《Life beyond Distributed Transactions:an Apostate’s Opinion》的論文提出。在該論文中,TCC還是以Tentative-Confirmation-Cancellation命名。正式以Try-Confirm-Cancel作為名稱的是Atomikos公司,其註冊了TCC商標。

國內最早關於TCC的報道,應該是InfoQ上對阿里程立博士的一篇採訪。經過程博士的這一次傳道之後,TCC在國內逐漸被大家廣為瞭解並接受。

Atomikos公司在商業版本事務管理器ExtremeTransactions中提供了TCC方案的實現,但是由於其是收費的,因此相應的很多的開源實現方案也就湧現出來,如:tcc-transaction、ByteTCC、spring-cloud-rest-tcc。

TCC的作用主要是解決跨服務呼叫場景下的分散式事務問題,在本文中,筆者將先介紹一個跨服務的場景案例,並分析其中存在的分散式事務問題;然後介紹TCC的基本概念以及其是如何解決這個問題的。

場景案例

Atomikos官網上<>一文中,以航班預定的案例,來介紹TCC要解決的事務場景。在這裡筆者虛構一個完全相同的場景,把自己當做航班預定的主人公,來介紹這個案例。事實上,你可以把本案例當做官方檔案案例的一個翻譯,只不過把地點從Brussels-->Toronto-->Washington,改成從合肥-->昆明-->大理。

有一次,筆者買彩票中獎了(純屬虛構),準備從合肥出發,到雲南大理去遊玩,然後使用美團App(機票代理商)來訂機票。發現沒有從合肥直達大理的航班,需要到昆明進行中轉。如下圖:

從圖中我們可以看出來,從合肥到昆明乘坐的是四川航空,從昆明到大理乘坐的是東方航空。

由於使用的是美團App預定,當我選擇了這種航班預定方案後,美團App要去四川航空和東方航空各幫我購買一張票。如下圖:

考慮最簡單的情況:美團先去川航幫我買票,如果買不到,那麼東航也沒必要買了。如果川航購買成功,再去東航購買另一張票。

現在問題來了:假設美團先從川航成功買到了票,然後去東航買票的時候,因為天氣問題,東航航班被取消了。那麼此時,美團必須取消川航的票,因為只有一張票是沒用的,不取消就是浪費我的錢。那麼如果取消會怎樣呢?如果讀者有取消機票經歷的話,非正常退票,肯定要扣手續費的。在這裡,川航本來已經購買成功,現在因為東航的原因要退川航的票,川航應該是要扣代理商的錢的。

那麼美團就要保證,如果任一航班購買失敗,都不能扣錢,怎麼做呢?

兩個航空公司都為美團提供以下3個介面:機票預留介面、確認介面、取消介面。美團App分2個階段進行呼叫,如下所示:

在第1階段:

美團分別請求兩個航空公司預留機票,兩個航空公司分別告訴美圖預留成功還是失敗。航空公司需要保證,機票預留成功的話,之後一定能購買到。

在第2階段:

如果兩個航空公司都預留成功,則分別向兩個公司傳送確認購買請求。

如果兩個航空公司任意一個預留失敗,則對於預留成功的航空公司也要取消預留。這種情況下,對於之前預留成功機票的航班取消,也不會扣使用者的錢,因為購買並沒實際發生,之前只是請求預留機票而已。

通過這種方案,可以保證兩個航空公司購買機票的一致性,要不都成功,要不都失敗,即使失敗也不會扣使用者的錢。如果在兩個航班都已經已經確認購買後,再退票,那肯定還是要扣錢的。

當然,實際情況肯定這裡提到的肯定要複雜,通常航空公司在第一階段,對於預留的機票,會要求在指定的時間必須確認購買(支付成功),如果沒有及時確認購買,會自動取消。假設川航要求10分鐘內支付成功,東航要求30分鐘內支付成功。以較短的時間算,如果使用者在10分鐘內支付成功的話,那麼美團會向兩個航空公司都傳送確認購買的請求,如果超過10分鐘(以較短的時間為準),那麼就不能進行支付。

再次強調,這個案例,可以算是<>中航班預定案例的漢化版。而實際美團App是如何實現這種需要中轉的航班預定需求,筆者並不知情。

另外,注意這只是一個案例場景,實際情況中,你是很難去驅動航空公司進行介面改造的。

Whatever,這個方案提供給我們一種跨服務條用保證事務一致性的一種解決思路,可以把這種方案當做TCC的雛形。

TCC 的基本概念

TCC是Try-Confirm-Cancel的簡稱:

Try階段:

完成所有業務檢查(一致性),預留業務資源(準隔離性)

回顧上面航班預定案例的階段1,機票就是業務資源,所有的資源提供者(航空公司)預留都成功,try階段才算陳宮

Confirm階段:

確認執行業務操作,不做任何業務檢查, 只使用Try階段預留的業務資源。回顧上面航班預定案例的階段2,美團APP確認兩個航空公司機票都預留成功,因此向兩個航空公司分別傳送確認購買的請求。

Cancel階段:

取消Try階段預留的業務資源。回顧上面航班預定案例的階段2,如果某個業務方的業務資源沒有預留成功,則取消所有業務資源預留請求。

敏銳的讀者立馬會想到,TCC與XA兩階段提交有著異曲同工之妙,下圖列出了二者之間的對比:

  1. 在階段1:

在XA中,各個RM準備提交各自的事務分支,事實上就是準備提交資源的更新操作(insert、delete、update等);而在TCC中,是主業務活動請求(try)各個從業務服務預留資源。

  1. 在階段2:

XA根據第一階段每個RM是否都prepare成功,判斷是要提交還是回滾。如果都prepare成功,那麼就commit每個事務分支,反之則rollback每個事務分支。

TCC中,如果在第一階段所有業務資源都預留成功,那麼confirm各個從業務服務,否則取消(cancel)所有從業務服務的資源預留請求。

TCC兩階段提交與XA兩階段提交的區別是:

XA是資源層面的分散式事務,強一致性,在兩階段提交的整個過程中,一直會持有資源的鎖。

XA事務中的兩階段提交內部過程是對開發者遮蔽的,回顧我們之前講解JTA規範時,通過UserTransaction的commit方法來提交全域性事務,這只是一次方法呼叫,其內部會委派給TransactionManager進行真正的兩階段提交,因此開發者從程式碼層面是感知不到這個過程的。而事務管理器在兩階段提交過程中,從prepare到commit/rollback過程中,資源實際上一直都是被加鎖的。如果有其他人需要更新這兩條記錄,那麼就必須等待鎖釋放。

TCC是業務層面的分散式事務,最終一致性,不會一直持有資源的鎖。

TCC中的兩階段提交併沒有對開發者完全遮蔽,也就是說從程式碼層面,開發者是可以感受到兩階段提交的存在。如上述航班預定案例:在第一階段,航空公司需要提供try介面(機票資源預留)。在第二階段,航空公司提需要提供confirm/cancel介面(確認購買機票/取消預留)。開發者明顯的感知到了兩階段提交過程的存在。try、confirm/cancel在執行過程中,一般都會開啟各自的本地事務,來保證方法內部業務邏輯的ACID特性。其中:

1、try過程的本地事務,是保證資源預留的業務邏輯的正確性。

2、confirm/cancel執行的本地事務邏輯確認/取消預留資源,以保證最終一致性,也就是所謂的補償型事務(Compensation-Based Transactions)。

由於是多個獨立的本地事務,因此不會對資源一直加鎖。

另外,這裡提到confirm/cancel執行的本地事務是補償性事務,關於什麼事補償性事務,atomikos 官網上有以下描述:

紅色框中的內容,是對補償性事務的解釋。大致含義是,"補償是一個獨立的支援ACID特性的本地事務,用於在邏輯上取消服務提供者上一個ACID事務造成的影響,對於一個長事務(long-running transaction),與其實現一個巨大的分散式ACID事務,不如使用基於補償性的方案,把每一次服務呼叫當做一個較短的本地ACID事務來處理,執行完就立即提交”。

在這裡,筆者理解為confirm和cancel就是補償事務,用於取消try階段本地事務造成的影響。因為第一階段try只是預留資源,之後必須要明確的告訴服務提供者,這個資源你到底要不要,對應第二階段的confirm/cancel。

提示:讀者現在應該明白為什麼把TCC叫做兩階段補償性事務了,提交過程分為2個階段,第二階段的confirm/cancel執行的事務屬於補償事務。

TCC事務模型 VS DTP事務模型

在介紹完TCC的基本概念之後,我們再來比較一下TCC事務模型和DTP事務模型,如下所示:

這兩張圖看起來差別較大,實際上很多地方是類似的!

1、TCC模型中的主業務服務 相當於 DTP模型中的AP,TCC模型中的從業務服務 相當於 DTP模型中的RM

在DTP模型中,應用AP操作多個資源管理器RM上的資源;而在TCC模型中,是主業務服務操作多個從業務服務上的資源。例如航班預定案例中,美團App就是主業務服務,而川航和東航就是從業務服務,主業務服務需要使用從業務服務上的機票資源。不同的是DTP模型中的資源提供者是類似於Mysql這種關係型資料庫,而TCC模型中資源的提供者是其他業務服務。

2、TCC模型中,從業務服務提供的try、confirm、cancel介面 相當於 DTP模型中RM提供的prepare、commit、rollback介面

XA協議中規定了DTP模型中定RM需要提供prepare、commit、rollback介面給TM呼叫,以實現兩階段提交。

而在TCC模型中,從業務服務相當於RM,提供了類似的try、confirm、cancel介面。

3、事務管理器

DTP模型和TCC模型中都有一個事務管理器。不同的是:

在DTP模型中,階段1的(prepare)和階段2的(commit、rollback),都是由TM進行呼叫的。

在TCC模型中,階段1的try介面是主業務服務呼叫(綠色箭頭),階段2的(confirm、cancel介面)是事務管理器TM呼叫(紅色箭頭)。這就是 TCC 分散式事務模型的二階段非同步化功能,從業務服務的第一階段執行成功,主業務服務就可以提交完成,然後再由事務管理器框架非同步的執行各從業務服務的第二階段。這裡犧牲了一定的隔離性和一致性的,但是提高了長事務的可用性。問題來了,既然第二階段是非同步執行的,主業務服務怎麼知道非同步執行的結果呢?發訊息非同步通知?返回一個id,後面讓業務去查?

TCC事務的優缺點:

優點:XA兩階段提交資源層面的,而TCC實際上把資源層面二階段提交上提到了業務層面來實現。有效了的避免了XA兩階段提交佔用資源鎖時間過長導致的效能地下問題。

缺點:主業務服務和從業務服務都需要進行改造,從業務方改造成本更高。還是航班預定案例,原來只需要提供一個購買介面,現在需要改造成try、confirm、canel3個介面,開發成本高。

提示:國內有一些關於TCC方案介紹的文章中,把TCC分成三種型別:

通用型TCC,如果我們上面介紹的TCC模型例項,從業務服務需要提供try、confirm、cancel

補償性TCC,從業務服務只需要提供 Do 和 Compensate 兩個介面

非同步確保型 TCC,主業務服務的直接從業務服務是可靠訊息服務,而真正的從業務服務則通過訊息服務解耦,作為訊息服務的消費端,非同步地執行。

關於這種劃分,筆者並不贊同,基於兩點:

1、筆者在Atomikos在官網上參考了多份資料,並沒有看到這種劃分,猜測應該是這些公司在內部實踐中,自行提出的概念。

2、對於上面所謂的"補償性TCC”、”非同步確保型 TCC”,從業務服務不需要提供try、confirm、cancel三個介面,在這種情況下,好像稱之為TCC也不太合適。

推薦閱讀資料:

開源TCC實現方案

Atomikos的官方檔案

  • Composite Transactions for SOA

  • Transaction management API for REST: TCC

  • Atomikos ExtremeTransactions Guide