<SOFA:Channel/>,有趣實用的分佈式架構頻道。
本文根據 SOFAChannel#4 直播分享整理,主題:分佈式事務 Seata TCC 模式深度解析。
Seata: https://github.com/seata/seata
回顧視頻以及 PPT 查看地址見文末。
歡迎加入直播互動釘釘羣:23127468,不錯過每場直播。
2019 年 3 月,螞蟻金服加入分佈式事務 Seata 的社區共建中,並貢獻其 TCC 模式。本期是 SOFAChannel 第四期,主題:分佈式事務 Seata TCC 模式深度解析,本文根據覺生的直播整理。git
你們晚上好,我是 Seata Committer 覺生,來自螞蟻金服數據中間件團隊。今天的內容主要分爲如下四個部分:github
下面咱們就進入第一個主題,Seata 的 TCC 模式。螞蟻金服早期是單系統架構,全部業務服務幾乎都在少數幾個系統中。隨着業務的發展,業務愈來愈複雜,服務之間的耦合度也愈來愈高,故咱們對系統進行了重構,服務按照功能進行解耦和垂直拆分。拆分以後所帶來的問題就是一個業務活動原來只須要調用一個服務就能完成,如今須要調用多個服務才能完成,而網絡、機器等不可靠,數據一致性的問題很容易出現,與可擴展性、高可用容災等要求並肩成爲金融 IT 架構支撐業務轉型升級的最大挑戰之一。數據庫
從圖中能夠看到,從單系統到微服務轉變,實際上是一個資源橫向擴展的過程,資源的橫向擴展是指當單臺機器達到資源性能瓶頸,沒法知足業務增加需求時,就須要橫向擴展資源,造成集羣。經過橫向擴展資源,提高非熱點數據的併發性能,這對於大致量的互聯網產品來講,是相當重要的。服務的拆分,也能夠認爲是資源的橫向擴展,只不過方向不一樣而已。性能優化
資源橫向擴展可能沿着兩個方向發展,包括業務拆分和數據分片:網絡
橫向擴展的兩種方法能夠同時進行運用:交易、支付與帳務三個不一樣微服務能夠存儲在不一樣的數據庫中。另外,每一個微服務內根據其業務量能夠再拆分到多個數據庫中,各微服務能夠相互獨立地進行擴展。架構
Seata 關注的就是微服務架構下的數據一致性問題,是一整套的分佈式事務解決方案。Seata 框架包含兩種模式,一種是 AT 模式。AT 模式主要從數據分片的角度,關注多 DB 訪問的數據一致性,固然也包括多服務下的多 DB 數據訪問一致性問題。併發
另一個就是 TCC 模式,TCC 模式主要關注業務拆分,在按照業務橫向擴展資源時,解決微服務間調用的一致性問題,保證讀資源訪問的事務屬性。框架
今天咱們主要講的就是TCC模式。在講 TCC 以前,咱們先回顧一下 AT 模式,這樣有助於咱們理解後面的 TCC 模式。異步
對於 AT 模式,以前其餘同窗已經分享過不少次,你們也應該比較熟悉了。AT 模式下,把每一個數據庫被當作是一個 Resource,Seata 裏稱爲 DataSource Resource。業務經過 JDBC 標準接口訪問數據庫資源時,Seata 框架會對全部請求進行攔截,作一些操做。每一個本地事務提交時,Seata RM(Resource Manager,資源管理器) 都會向 TC(Transaction Coordinator,事務協調器) 註冊一個分支事務。當請求鏈路調用完成後,發起方通知 TC 提交或回滾分佈式事務,進入二階段調用流程。此時,TC 會根據以前註冊的分支事務回調到對應參與者去執行對應資源的第二階段。TC 是怎麼找到分支事務與資源的對應關係呢?每一個資源都有一個全局惟一的資源 ID,而且在初始化時用該 ID 向 TC 註冊資源。在運行時,每一個分支事務的註冊都會帶上其資源 ID。這樣 TC 就能在二階段調用時正確找到對應的資源。分佈式
這就是咱們的 AT 模式。簡單總結一下,就是把每一個數據庫當作一個 Resource,在本地事務提交時會去註冊一個分支事務。
那麼對應到 TCC 模式裏,也是同樣的,Seata 框架把每組 TCC 接口當作一個 Resource,稱爲 TCC Resource。這套 TCC 接口能夠是 RPC,也以是服務內 JVM 調用。在業務啓動時,Seata 框架會自動掃描識別到 TCC 接口的調用方和發佈方。若是是 RPC 的話,就是 sofa:reference、sofa:service、dubbo:reference、dubbo:service 等。
掃描到 TCC 接口的調用方和發佈方以後。若是是發佈方,會在業務啓動時向 TC 註冊 TCC Resource,與DataSource Resource 同樣,每一個資源也會帶有一個資源 ID。
若是是調用方,Seata 框架會給調用方加上切面,與 AT 模式同樣,在運行時,該切面會攔截全部對 TCC 接口的調用。每調用一次 Try 接口,切面會先向 TC 註冊一個分支事務,而後纔去執行原來的 RPC 調用。當請求鏈路調用完成後,TC 經過分支事務的資源ID回調到正確的參與者去執行對應 TCC 資源的 Confirm 或 Cancel 方法。
在講完了整個框架模型之後,你們可能會問 TCC 三個接口怎麼實現。由於框架自己很簡單,主要是掃描 TCC 接口,註冊資源,攔截接口調用,註冊分支事務,最後回調二階段接口。最核心的其實是 TCC 接口的實現邏輯。下面我將根據螞蟻金服內部多年的實踐來爲你們分析怎麼實現一個完備的 TCC 接口。
從 TCC 模型的框架能夠發現,TCC 模型的核心在於 TCC 接口的設計。用戶在接入 TCC 時,大部分工做都集中在如何實現 TCC 服務上。下面我會分享螞蟻金服內多年的 TCC 應用實踐以及在 TCC 設計和實現過程當中的注意事項。
設計一套 TCC 接口最重要的是什麼?主要有兩點,第一點,須要將操做分紅兩階段完成。TCC(Try-Confirm-Cancel)分佈式事務模型相對於 XA 等傳統模型,其特徵在於它不依賴 RM 對分佈式事務的支持,而是經過對業務邏輯的分解來實現分佈式事務。
TCC 模型認爲對於業務系統中一個特定的業務邏輯 ,其對外提供服務時,必須接受一些不肯定性,即對業務邏輯初步操做的調用僅是一個臨時性操做,調用它的主業務服務保留了後續的取消權。若是主業務服務認爲全局事務應該回滾,它會要求取消以前的臨時性操做,這就對應從業務服務的取消操做。而當主業務服務認爲全局事務應該提交時,它會放棄以前臨時性操做的取消權,這對應從業務服務的確認操做。每個初步操做,最終都會被確認或取消。所以,針對一個具體的業務服務,TCC 分佈式事務模型須要業務系統提供三段業務邏輯:
1.初步操做 Try:完成全部業務檢查,預留必須的業務資源。
2.確認操做 Confirm:真正執行的業務邏輯,不作任何業務檢查,只使用 Try 階段預留的業務資源。所以,只要 Try 操做成功,Confirm 必須能成功。另外,Confirm 操做需知足冪等性,保證一筆分佈式事務能且只能成功一次。
3.取消操做 Cancel:釋放 Try 階段預留的業務資源。一樣的,Cancel 操做也須要知足冪等性。
第二點,就是要根據自身的業務模型控制併發,這個對應 ACID 中的隔離性。後面會詳細講到。
下面咱們以金融核心鏈路裏的帳務服務來分析一下。首先一個最簡化的帳務模型就是圖中所列,每一個用戶或商戶有一個帳戶及其可用餘額。而後,分析下帳務服務的全部業務邏輯操做,不管是交易、充值、轉帳、退款等,均可以認爲是對帳戶的加錢與扣錢。
所以,咱們能夠把帳務系統拆分紅兩套 TCC 接口,即兩個 TCC Resource,一個是加錢 TCC 接口,一個是扣錢 TCC 接口。
那這兩套接口分別須要作什麼事情呢?如何將其分紅兩個階段完成?下面將會舉例說明 TCC 業務模式的設計過程,並逐漸優化。
咱們先來看扣錢的 TCC 資源怎麼實現。場景爲 A 轉帳 30 元給 B。帳戶 A 的餘額中有 100 元,須要扣除其中 30 元。這裏的餘額就是所謂的業務資源,按照前面提到的原則,在第一階段須要檢查並預留業務資源,所以,咱們在扣錢 TCC 資源的 Try 接口裏先檢查 A 帳戶餘額是否足夠,而後預留餘額裏的業務資源,即扣除 30 元。
在 Confirm 接口,因爲業務資源已經在 Try 接口裏扣除掉了,那麼在第二階段的 Confirm 接口裏,能夠什麼都不用作。而在 Cancel 接口裏,則須要把 Try 接口裏扣除掉的 30 元還給帳戶。這是一個比較簡單的扣錢 TCC 資源的實現,後面會繼續優化它。
而在加錢的 TCC 資源裏。在第一階段 Try 接口裏不能直接給帳戶加錢,若是這個時候給帳戶增長了可用餘額,那麼在一階段執行完後,帳戶裏的錢就能夠被使用了。可是一階段執行完之後,有多是要回滾的。所以,真正加錢的動做須要放在 Confirm 接口裏。對於加錢這個動做,第一階段 Try 接口裏不須要預留任何資源,能夠設計爲空操做。那相應的,Cancel 接口沒有資源須要釋放,也是一個空操做。只有真正須要提交時,再在 Confirm 接口裏給帳戶增長可用餘額。
這就是一個最簡單的扣錢和加錢的 TCC 資源的設計。在扣錢 TCC 資源裏,Try 接口預留資源扣除餘額,Confirm 接口空操做,Cancel 接口釋放資源,增長餘額。在加錢 TCC 資源裏,Try 接口無需預留資源,空操做;Confirm 接口直接增長餘額;Cancel 接口無需釋放資源,空操做。
以前提到,設計一套 TCC 接口須要有兩點,一點是須要拆分業務邏輯成兩階段完成。這個咱們已經介紹了。另一點是要根據自身的業務模型控制併發。
Seata 框架自己僅提供兩階段原子提交協議,保證分佈式事務原子性。事務的隔離須要交給業務邏輯來實現。隔離的本質就是控制併發,防止併發事務操做相同資源而引發的結果錯亂。
舉個例子,好比金融行業裏管理用戶資金,當用戶發起交易時,通常會先檢查用戶資金,若是資金充足,則扣除相應交易金額,增長賣家資金,完成交易。若是沒有事務隔離,用戶同時發起兩筆交易,兩筆交易的檢查都認爲資金充足,實際上卻只夠支付一筆交易,結果兩筆交易都支付成功,致使資損。
能夠發現,併發控制是業務邏輯執行正確的保證,可是像兩階段鎖這樣的併發訪問控制技術要求一直持有數據庫資源鎖直到整個事務執行結束,特別是在分佈式事務架構下,要求持有鎖到分佈式事務第二階段執行結束,也就是說,分佈式事務會加長資源鎖的持有時間,致使併發性能進一步降低。
所以,TCC 模型的隔離性思想就是經過業務的改造,在第一階段結束以後,從底層數據庫資源層面的加鎖過渡爲上層業務層面的加鎖,從而釋放底層數據庫鎖資源,放寬分佈式事務鎖協議,將鎖的粒度降到最低,以最大限度提升業務併發性能。
仍是以上面的例子舉例,「帳戶 A 上有 100 元,事務 T1 要扣除其中的 30 元,事務 T2 也要扣除 30 元,出現併發」。在第一階段 Try 操做中,須要先利用數據庫資源層面的加鎖,檢查帳戶可用餘額,若是餘額充足,則預留業務資源,扣除本次交易金額,一階段結束後,雖然數據庫層面資源鎖被釋放了,但這筆資金被業務隔離,不容許除本事務以外的其它併發事務動用。
併發的事務 T2 在事務 T1 一階段接口結束釋放了數據庫層面的資源鎖之後,就能夠繼續操做,跟事務 T1 同樣,加鎖,檢查餘額,扣除交易金額。
事務 T1 和 T2 分別扣除的那一部分資金,相互之間無干擾。這樣在分佈式事務的二階段,不管 T1 是提交仍是回滾,都不會對 T2 產生影響,這樣 T1 和 T2 能夠在同一個帳戶上併發執行。
你們能夠感覺下,一階段結束之後,實際上採用業務加鎖的方式,隔離帳戶資金,在第一階段結束後直接釋放底層資源鎖,該用戶和賣家的其餘交易均可以馬上併發執行,而不用等到整個分佈式事務結束,能夠得到更高的併發交易能力。
這裏稍微有點抽象,下面咱們將會針對業務模型進行優化,你們能夠更直觀的感覺業務加鎖的思想。
前面的模型你們確定會想,爲啥一階段就把錢扣除了?是的。以前只是爲了簡單說明 TCC 模型的設計思想。在實際中,爲了更好的用戶體驗,在第一階段,通常不會直接把帳戶的餘額扣除,而是凍結,這樣給用戶展現的時候,就能夠很清晰的知道,哪些是可用餘額,哪些是凍結金額。
那業務模型變成什麼樣了呢?如圖所示,須要在業務模型中增長凍結金額字段,用來表示帳戶有多少金額處以凍結狀態。
既然業務模型發生了變化,那扣錢和加錢的 TCC 接口也應該相應的調整。仍是之前面的例子來講明。
在扣錢的 TCC 資源裏。Try 接口再也不是直接扣除帳戶的可用餘額,而是真正的預留資源,凍結部分可用餘額,即減小可用餘額,增長凍結金額。Confirm 接口也再也不是空操做,而是使用 Try 接口預留的業務資源,即將該部分凍結金額扣除;最後在 Cancel 接口裏,就是釋放預留資源,把 Try 接口的凍結金額扣除,增長帳戶可用餘額。加錢的 TCC資源因爲不涉及凍結金額的使用,因此無需更改。
經過這樣的優化,能夠更直觀的感覺到 TCC 接口的預留資源、使用資源、釋放資源的過程。
那併發控制又變成什麼樣了呢?跟前面大部分相似,在事務 T1 的第一階段 Try 操做中,先鎖定帳戶,檢查帳戶可用餘額,若是餘額充足,則預留業務資源,減小可用餘額,增長凍結金額。併發的事務 T2 相似,加鎖,檢查餘額,減小可用餘額金額,增長凍結金額。
這裏能夠發現,事務 T1 和T2 在一階段執行完成後,都釋放了數據庫層面的資源鎖,可是在各自二階段的時候,相互之間並沒有干擾,各自使用本事務內第一階段 Try 接口內凍結金額便可。這裏你們就能夠直觀感覺到,在每一個事務的第一階段,先經過數據庫層面的資源鎖,預留業務資源,即凍結金額。雖然在一階段結束之後,數據庫層面的資源鎖被釋放了,可是第二階段的執行並不會被幹擾,這是由於數據庫層面資源鎖釋放之後經過業務隔離的方式爲這部分資源加鎖,不容許除本事務以外的其它併發事務動用,從而保證該事務的第二階段可以正確順利的執行。
經過這兩個例子,爲你們講解了怎麼去設計一套完備的 TCC 接口。最主要的有兩點,一點是將業務邏輯拆分紅兩個階段完成,即 Try、Confirm、Cancel 接口。其中 Try 接口檢查資源、預留資源、Confirm 使用資源、Cancel 接口釋放預留資源。另一點就是併發控制,採用數據庫鎖與業務加鎖的方式結合。因爲業務加鎖的特性不影響性能,所以,儘量下降數據庫鎖粒度,過渡爲業務加鎖,從而提升業務併發能力。
在有了一套完備的 TCC 接口以後,是否是就真的高枕無憂了呢?答案是否認的。在微服務架構下,頗有可能出現網絡超時、重發,機器宕機等一系列的異常 Case。一旦遇到這些 Case,就會致使咱們的分佈式事務執行過程出現異常。根據螞蟻金服內部多年的使用來看,最多見的主要是這三種異常,分別是空回滾、冪等、懸掛。
所以,TCC 接口裏還須要解決這三類異常。實際上,這三類問題能夠在 Seata 框架裏完成,只不過咱們如今的 Seata框架還不具有,以後咱們會把這些異常 Case 的處理移植到 Seata 框架裏,業務就無需關注這些異常狀況,專一於業務邏輯便可。
雖然業務以後無需關心,可是瞭解一下其內部實現機制,也能更好的排查問題。下面我將爲你們一一講解這三類異常出現的緣由以及對應的解決方案。
首先是空回滾。什麼是空回滾?空回滾就是對於一個分佈式事務,在沒有調用 TCC 資源 Try 方法的狀況下,調用了二階段的 Cancel 方法,Cancel 方法須要識別出這是一個空回滾,而後直接返回成功。
什麼樣的情形會形成空回滾呢?能夠看圖中的第 2 步,前面講過,註冊分支事務是在調用 RPC 時,Seata 框架的切面會攔截到該次調用請求,先向 TC 註冊一個分支事務,而後纔去執行 RPC 調用邏輯。若是 RPC 調用邏輯有問題,好比調用方機器宕機、網絡異常,都會形成 RPC 調用失敗,即未執行 Try 方法。可是分佈式事務已經開啓了,須要推動到終態,所以,TC 會回調參與者二階段 Cancel 接口,從而造成空回滾。
那會不會有空提交呢?理論上來講不會的,若是調用方宕機,那分佈式事務默認是回滾的。若是是網絡異常,那 RPC 調用失敗,發起方應該通知 TC 回滾分佈式事務,這裏能夠看出爲何是理論上的,就是說發起方能夠在 RPC 調用失敗的狀況下依然通知 TC 提交,這時就會發生空提交,這種狀況要麼是編碼問題,要麼開發同窗明確知道須要這樣作。
那怎麼解決空回滾呢?前面提到,Cancel 要識別出空回滾,直接返回成功。那關鍵就是要識別出這個空回滾。思路很簡單就是須要知道一階段是否執行,若是執行了,那就是正常回滾;若是沒執行,那就是空回滾。所以,須要一張額外的事務控制表,其中有分佈式事務 ID 和分支事務 ID,第一階段 Try 方法裏會插入一條記錄,表示一階段執行了。Cancel 接口裏讀取該記錄,若是該記錄存在,則正常回滾;若是該記錄不存在,則是空回滾。
接下來是冪等。冪等就是對於同一個分佈式事務的同一個分支事務,重複去調用該分支事務的第二階段接口,所以,要求 TCC 的二階段 Confirm 和 Cancel 接口保證冪等,不會重複使用或者釋放資源。若是冪等控制沒有作好,頗有可能致使資損等嚴重問題。
什麼樣的情形會形成重複提交或回滾?從圖中能夠看到,提交或回滾是一次 TC 到參與者的網絡調用。所以,網絡故障、參與者宕機等都有可能形成參與者 TCC 資源實際執行了二階段防範,可是 TC 沒有收到返回結果的狀況,這時,TC 就會重複調用,直至調用成功,整個分佈式事務結束。
怎麼解決重複執行的冪等問題呢?一個簡單的思路就是記錄每一個分支事務的執行狀態。在執行前狀態,若是已執行,那就再也不執行;不然,正常執行。前面在講空回滾的時候,已經有一張事務控制表了,事務控制表的每條記錄關聯一個分支事務,那咱們徹底能夠在這張事務控制表上加一個狀態字段,用來記錄每一個分支事務的執行狀態。
如圖所示,該狀態字段有三個值,分別是初始化、已提交、已回滾。Try 方法插入時,是初始化狀態。二階段 Confirm 和 Cancel 方法執行後修改成已提交或已回滾狀態。當重複調用二階段接口時,先獲取該事務控制表對應記錄,檢查狀態,若是已執行,則直接返回成功;不然正常執行。
最後是防懸掛。按照慣例,我們來先講講什麼是懸掛。懸掛就是對於一個分佈式事務,其二階段 Cancel 接口比 Try 接口先執行。由於容許空回滾的緣由,Cancel 接口認爲 Try 接口沒執行,空回滾直接返回成功,對於 Seata 框架來講,認爲分佈式事務的二階段接口已經執行成功,整個分佈式事務就結束了。可是這以後 Try 方法才真正開始執行,預留業務資源,前面提到事務併發控制的業務加鎖,對於一個 Try 方法預留的業務資源,只有該分佈式事務才能使用,然而 Seata 框架認爲該分佈式事務已經結束,也就是說,當出現這種狀況時,該分佈式事務第一階段預留的業務資源就再也沒有人可以處理了,對於這種狀況,咱們就稱爲懸掛,即業務資源預留後無法繼續處理。
什麼樣的狀況會形成懸掛呢?按照前面所講,在 RPC 調用時,先註冊分支事務,再執行 RPC 調用,若是此時 RPC 調用的網絡發生擁堵,一般 RPC 調用是有超時時間的,RPC 超時之後,發起方就會通知 TC 回滾該分佈式事務,可能回滾完成後,RPC 請求才到達參與者,真正執行,從而形成懸掛。
怎麼實現才能作到防懸掛呢?根據懸掛出現的條件先來分析下,懸掛是指二階段 Cancel 執行完後,一階段才執行。也就是說,爲了不懸掛,若是二階段執行完成,那一階段就不能再繼續執行。所以,當一階段執行時,須要先檢查二階段是否已經執行完成,若是已經執行,則一階段再也不執行;不然能夠正常執行。那怎麼檢查二階段是否已經執行呢?你們是否想到了剛纔解決空回滾和冪等時用到的事務控制表,能夠在二階段執行時插入一條事務控制記錄,狀態爲已回滾,這樣當一階段執行時,先讀取該記錄,若是記錄存在,就認爲二階段已經執行;不然二階段沒執行。
在分析完空回滾、冪等、懸掛等異常 Case 的成因以及解決方案之後,下面咱們就綜合起來考慮,一個 TCC 接口如何完整的解決這三個問題。
首先是 Try 方法。結合前面講到空回滾和懸掛異常,Try 方法主要須要考慮兩個問題,一個是 Try 方法須要可以告訴二階段接口,已經預留業務資源成功。第二個是須要檢查第二階段是否已經執行完成,若是已完成,則再也不執行。所以,Try 方法的邏輯能夠如圖所示:
先插入事務控制表記錄,若是插入成功,說明第二階段尚未執行,能夠繼續執行第一階段。若是插入失敗,則說明第二階段已經執行或正在執行,則拋出異常,終止便可。
接下來是 Confirm 方法。由於 Confirm 方法不容許空回滾,也就是說,Confirm 方法必定要在 Try 方法以後執行。所以,Confirm 方法只須要關注重複提交的問題。能夠先鎖定事務記錄,若是事務記錄爲空,則說明是一個空提交,不容許,終止執行。若是事務記錄不爲空,則繼續檢查狀態是否爲初始化,若是是,則說明一階段正確執行,那二階段正常執行便可。若是狀態是已提交,則認爲是重複提交,直接返回成功便可;若是狀態是已回滾,也是一個異常,一個已回滾的事務,不能從新提交,須要可以攔截到這種異常狀況,並報警。
最後是 Cancel 方法。由於 Cancel 方法容許空回滾,而且要在先執行的狀況下,讓 Try 方法感知到 Cancel 已經執行,因此和 Confirm 方法略有不一樣。首先依然是鎖定事務記錄。若是事務記錄爲空,則認爲 Try 方法還沒執行,便是空回滾。空回滾的狀況下,應該先插入一條事務記錄,確保後續的 Try 方法不會再執行。若是插入成功,則說明 Try 方法尚未執行,空回滾繼續執行。若是插入失敗,則認爲Try 方法正再執行,等待 TC 的重試便可。若是一開始讀取事務記錄不爲空,則說明 Try 方法已經執行完畢,再檢查狀態是否爲初始化,若是是,則尚未執行過其餘二階段方法,正常執行 Cancel 邏輯。若是狀態爲已回滾,則說明這是重複調用,容許冪等,直接返回成功便可。若是狀態爲已提交,則一樣是一個異常,一個已提交的事務,不能再次回滾。
經過這一部分的講解,你們應該對 TCC 模型下最多見的三類異常 Case,空回滾、冪等、懸掛的成因有所瞭解,也從實際例子中知道了怎麼解決這三類異常,在解決了這三類異常的狀況下,咱們的 TCC 接口設計就是比較完備的了。後續咱們將會把這些解決方案移植到 Seata 框架中,由 Seata 框架來完成異常的處理,開發 TCC 接口的同窗就再也不須要關心了。
雖然 TCC 模型已經完備,可是隨着業務的增加,對於 TCC 模型的挑戰也愈來愈大,可能還須要一些特殊的優化,才能知足業務需求。下面咱們將會給你們講講,螞蟻金服內部在 TCC 模型上都作了哪些優化。
第一個優化方案是改成同庫模式。同庫模式簡單來講,就是分支事務記錄與業務數據在相同的庫中。什麼意思呢?以前提到,在註冊分支事務記錄的時候,框架的調用方切面會先向 TC 註冊一個分支事務記錄,註冊成功後,纔會繼續往下執行 RPC 調用。TC 在收到分支事務記錄註冊請求後,會往本身的數據庫裏插入一條分支事務記錄,從而保證事務數據的持久化存儲。那同庫模式就是調用方切面再也不向 TC 註冊了,而是直接往業務的數據庫裏插入一條事務記錄。
在講解同庫模式的性能優化點以前,先給你們簡單講講同庫模式的恢復邏輯。一個分佈式事務的提交或回滾仍是由發起方通知 TC,可是因爲分支事務記錄保存在業務數據庫,而不是 TC 端。所以,TC 不知道有哪些分支事務記錄,在收到提交或回滾的通知後,僅僅是記錄一下該分佈式事務的狀態。那分支事務記錄怎麼真正執行第二階段呢?須要在各個參與者內部啓動一個異步任務,按期撈取業務數據庫中未結束的分支事務記錄,而後向 TC 檢查整個分佈式事務的狀態,即圖中的 StateCheckRequest 請求。TC 在收到這個請求後,會根據以前保存的分佈式事務的狀態,告訴參與者是提交仍是回滾,從而完成分支事務記錄。
那這樣作有什麼好處呢?左邊是採用同庫模式前的調用關係圖,在每次調用一個參與者的時候,都是先向 TC 註冊一個分佈式事務記錄,TC 再持久化存儲在本身的數據庫中,也就是說,一個分支事務記錄的註冊,包含一次 RPC 和一次持久化存儲。
右邊是優化後的調用關係圖。從圖中能夠看出,每次調用一個參與者的時候,都是直接保存在業務的數據庫中,從而減小與 TC 之間的 RPC 調用。優化後,有多少個參與者,就節約多少次 RPC 調用。
這就是同庫模式的性能方案。把分支事務記錄保存在業務數據庫中,從而減小與 TC 的 RPC 調用。
另一個性能優化方式就是異步化,什麼是異步化。TCC 模型的一個做用就是把兩階段拆分紅了兩個獨立的階段,經過資源業務鎖定的方式進行關聯。資源業務鎖定方式的好處在於,既不會阻塞其餘事務在第一階段對於相同資源的繼續使用,也不會影響本事務第二階段的正確執行。從理論上來講,只要業務容許,事務的第二階段何時執行均可以,反正資源已經業務鎖定,不會有其餘事務動用該事務鎖定的資源。
假設只有一箇中間帳戶的狀況下,每次調用支付服務的 Commit 接口,都會鎖定中間帳戶,中間帳戶存在熱點性能問題。
可是,在擔保交易場景中,七天之後才須要將資金從中間帳戶劃撥給商戶,中間帳戶並不須要對外展現。所以,在執行完支付服務的第一階段後,就能夠認爲本次交易的支付環節已經完成,並向用戶和商戶返回支付成功的結果,並不須要立刻執行支付服務二階段的 Commit 接口,等到低鋒期時,再慢慢消化,異步地執行。
今天進行了 Seata TCC 模式的深度解析。主要介紹 Seata TCC 模式的原理,從 TCC 業務模型與併發控制的角度告訴你們怎麼設計一個 TCC 的接口以及怎麼處理空回滾、冪等、懸掛等異常,最後對螞蟻金服內部對 TCC 的性能優化點簡單介紹,使得 TCC 模式可以知足更高的業務需求。
業務各有不一樣,有些業務能容忍短時間不一致,有些業務的操做能夠冪等,不管什麼樣的分佈式事務解決方案都有其優缺點,沒有一個銀彈可以適配全部。所以,業務須要什麼樣的解決方案,還須要結合自身的業務需求、業務特色、技術架構以及各解決方案的特性,綜合分析,才能找到最適合的方案。
若是你們對 Seata 的性能和需求有本身的想法,歡迎你們在釘釘羣(搜索羣號便可加入:23127468)或者 Github 上與咱們討論交流。
Seata:github.com/seata/seata
今天的直播分享到這裏結束了,謝謝你們!
公衆號:金融級分佈式架構(Antfin_SOFA)