原文git
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商標。github
國內最先關於TCC的報道,應該是InfoQ上對阿里程立博士的一篇採訪。通過程博士的這一次傳道以後,TCC在國內逐漸被你們廣爲了解並接受。spring
Atomikos公司在商業版本事務管理器ExtremeTransactions中提供了TCC方案的實現,可是因爲其是收費的,所以相應的不少的開源實現方案也就涌現出來,如:tcc-transaction、ByteTCC、spring-cloud-rest-tcc。sql
TCC的做用主要是解決跨服務調用場景下的分佈式事務問題,在本文中,筆者將先介紹一個跨服務的場景案例,並分析其中存在的分佈式事務問題;而後介紹TCC的基本概念以及其是如何解決這個問題的。數據庫
Atomikos官網上<>一文中,以航班預約的案例,來介紹TCC要解決的事務場景。在這裏筆者虛構一個徹底相同的場景,把本身當作航班預約的主人公,來介紹這個案例。事實上,你能夠把本案例當作官方文檔案例的一個翻譯,只不過把地點從Brussels-->Toronto-->Washington,改爲從合肥-->昆明-->大理。框架
有一次,筆者買彩票中獎了(純屬虛構),準備從合肥出發,到雲南大理去遊玩,而後使用美團App(機票代理商)來訂機票。發現沒有從合肥直達大理的航班,須要到昆明進行中轉。以下圖:異步
從圖中咱們能夠看出來,從合肥到昆明乘坐的是四川航空,從昆明到大理乘坐的是東方航空。分佈式
因爲使用的是美團App預約,當我選擇了這種航班預約方案後,美團App要去四川航空和東方航空各幫我購買一張票。以下圖:ide
考慮最簡單的狀況:美團先去川航幫我買票,若是買不到,那麼東航也不必買了。若是川航購買成功,再去東航購買另外一張票。post
如今問題來了:假設美團先從川航成功買到了票,而後去東航買票的時候,由於天氣問題,東航航班被取消了。那麼此時,美團必須取消川航的票,由於只有一張票是沒用的,不取消就是浪費個人錢。那麼若是取消會怎樣呢?若是讀者有取消機票經歷的話,非正常退票,確定要扣手續費的。在這裏,川航原本已經購買成功,如今由於東航的緣由要退川航的票,川航應該是要扣代理商的錢的。
那麼美團就要保證,若是任一航班購買失敗,都不能扣錢,怎麼作呢?
兩個航空公司都爲美團提供如下3個接口:機票預留接口、確認接口、取消接口。美團App分2個階段進行調用,以下所示:
美團分別請求兩個航空公司預留機票,兩個航空公司分別告訴美圖預留成功仍是失敗。航空公司須要保證,機票預留成功的話,以後必定能購買到。
在第2階段:
若是兩個航空公司都預留成功,則分別向兩個公司發送確認購買請求。
若是兩個航空公司任意一個預留失敗,則對於預留成功的航空公司也要取消預留。這種狀況下,對於以前預留成功機票的航班取消,也不會扣用戶的錢,由於購買並沒實際發生,以前只是請求預留機票而已。
經過這種方案,能夠保證兩個航空公司購買機票的一致性,要不都成功,要不都失敗,即便失敗也不會扣用戶的錢。若是在兩個航班都已經已經確認購買後,再退票,那確定仍是要扣錢的。
固然,實際狀況確定這裏提到的確定要複雜,一般航空公司在第一階段,對於預留的機票,會要求在指定的時間必須確認購買(支付成功),若是沒有及時確認購買,會自動取消。假設川航要求10分鐘內支付成功,東航要求30分鐘內支付成功。以較短的時間算,若是用戶在10分鐘內支付成功的話,那麼美團會向兩個航空公司都發送確認購買的請求,若是超過10分鐘(以較短的時間爲準),那麼就不能進行支付。
再次強調,這個案例,能夠算是<>中航班預約案例的漢化版。而實際美團App是如何實現這種須要中轉的航班預約需求,筆者並不知情。
另外,注意這只是一個案例場景,實際狀況中,你是很難去驅動航空公司進行接口改造的。
Whatever,這個方案提供給咱們一種跨服務條用保證事務一致性的一種解決思路,能夠把這種方案當作TCC的雛形。
TCC是Try-Confirm-Cancel的簡稱:
Try階段:
完成全部業務檢查(一致性),預留業務資源(準隔離性)
回顧上面航班預約案例的階段1,機票就是業務資源,全部的資源提供者(航空公司)預留都成功,try階段纔算陳宮
Confirm階段:
確認執行業務操做,不作任何業務檢查, 只使用Try階段預留的業務資源。回顧上面航班預約案例的階段2,美團APP確認兩個航空公司機票都預留成功,所以向兩個航空公司分別發送確認購買的請求。
Cancel階段:
取消Try階段預留的業務資源。回顧上面航班預約案例的階段2,若是某個業務方的業務資源沒有預留成功,則取消全部業務資源預留請求。
敏銳的讀者立馬會想到,TCC與XA兩階段提交有着殊途同歸之妙,下圖列出了兩者之間的對比:
在XA中,各個RM準備提交各自的事務分支,事實上就是準備提交資源的更新操做(insert、delete、update等);而在TCC中,是主業務活動請求(try)各個從業務服務預留資源。
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特性。其中:
一、try過程的本地事務,是保證資源預留的業務邏輯的正確性。
二、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的基本概念以後,咱們再來比較一下TCC事務模型和DTP事務模型,以下所示:
這兩張圖看起來差異較大,實際上不少地方是相似的!
一、TCC模型中的主業務服務 至關於 DTP模型中的AP,TCC模型中的從業務服務 至關於 DTP模型中的RM
在DTP模型中,應用AP操做多個資源管理器RM上的資源;而在TCC模型中,是主業務服務操做多個從業務服務上的資源。例如航班預約案例中,美團App就是主業務服務,而川航和東航就是從業務服務,主業務服務須要使用從業務服務上的機票資源。不一樣的是DTP模型中的資源提供者是相似於Mysql這種關係型數據庫,而TCC模型中資源的提供者是其餘業務服務。
二、TCC模型中,從業務服務提供的try、confirm、cancel接口 至關於 DTP模型中RM提供的prepare、commit、rollback接口
XA協議中規定了DTP模型中定RM須要提供prepare、commit、rollback接口給TM調用,以實現兩階段提交。
而在TCC模型中,從業務服務至關於RM,提供了相似的try、confirm、cancel接口。
三、事務管理器
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,主業務服務的直接從業務服務是可靠消息服務,而真正的從業務服務則經過消息服務解耦,做爲消息服務的消費端,異步地執行。
關於這種劃分,筆者並不贊同,基於兩點:
一、筆者在Atomikos在官網上參考了多份資料,並無看到這種劃分,猜想應該是這些公司在內部實踐中,自行提出的概念。
二、對於上面所謂的"補償性TCC」、」異步確保型 TCC」,從業務服務不須要提供try、confirm、cancel三個接口,在這種狀況下,好像稱之爲TCC也不太合適。
開源TCC實現方案
Atomikos的官方文檔
Composite Transactions for SOA
Transaction management API for REST: TCC
Atomikos ExtremeTransactions Guide