SOFA:Channel/,有趣實用的分散式架構頻道。 本文根據 SOFAChannel#12 直播分享整理,主題:螞蟻金服分散式事務實踐解析。 回顧視訊以及 PPT 檢視地址見文末。歡迎加入直播互動釘釘群 : 30315793,不錯過每場直播。
大家好,我是今天分享的講師仁空,目前是螞蟻金服分散式事務產品的研發。今天跟大家分享的是螞蟻金服分散式事務實踐解析,也就是分散式事務 Seata 在螞蟻金服內部的實踐。
今天我們將從以下 4 個主題進行詳細介紹:
- 為什麼會有分散式事務產品的需求;
- 理論界針對這個需求提出的一些理論和解決方案;
- 螞蟻金服在工程上是如何解決這個問題的;
- 針對螞蟻金服業務場景的效能優化;
分散式事務產生背景
首先是分散式事務產生的背景。
支付寶支付產品在 2003 年上線的時候,那時候的軟體形態是單體應用,在一個應用內完成所有的業務邏輯操作。隨著軟體的工業化,場景越來越複雜,軟體也越做越大,所有的業務在一個應用內去完成變的不可能,出現了軟體模組化、服務化。
在從單體應用升級到分散式架構過程中,很自然得需要進行業務服務拆分,將原來糅在一個系統中的業務進行梳理,拆分出能獨立成體系的各個子系統,例如交易系統、支付系統、賬務系統等,這個過程就是服務化。業務服務拆分之後,原來一個服務就能完成的業務操作現在需要跨多個服務進行了。
另一個就是資料庫拆分,分庫分表。原來的單體資料庫存不下的這麼多資訊,按服務維度拆庫,比如把使用者相關的存一起,形成使用者庫,訂單放一塊形成訂單庫,這個是拆庫的過程;另一個是拆表,使用者資訊按照使用者 ID 雜湊到不同的 DB 中,水平拆分,資料庫的容量增大了。這樣分庫分表之後,寫操作就會跨多個資料庫了。
分散式事務理論基礎
我們可以看到,在分散式架構中,跨資料庫、跨服務的問題是天然存在的。一個業務操作的完成,需要經過多個服務配合完成,這些服務操作的資料可能在一個機房中,也可能跨機房存在,如果中間某一個服務因為網路或機房硬體的問題發生了抖動,怎麼保證這筆業務最終的狀態是正確的,比如支付場景,怎麼防止我轉錢給你的過程中,我的錢扣了,而對方的賬戶並沒有收到錢。這個就是業務最終一致性的問題,是分散式事務需要解決的問題。
2PC 協議
針對這個問題,理論界也提出瞭解決方案,其中最為人熟知的就是二階段協議了,簡稱2PC(Two Phase Commitment Protocol)兩階段提交協議。
兩階段提交協議,就是把整個過程分成了兩個階段,這其中,它把參與整個過程的實體分成了兩類角色,一個叫事務管理器或事務協調者,一個叫資源管理器,事務管理器我們也把它叫做事務發起方,資源管理器稱為事務參與者。
兩個階段,第一個階段是資源準備階段,比如我要轉賬,我要先查詢下我的餘額夠不夠,夠的話我就把餘額資源預留起來,然後告訴發起方“我準備好了”,第二個階段,事務發起方根據各個參與者的反饋,決定事務的二階段操作是提交還是取消。
TCC 協議
另一個協議是 TCC 協議,各個參與者需要實現3個操作:Try、Confirm 和 Cancel,3個操作對應2個階段,Try 方法是一階段的資源檢測和預留階段,Confirm 和 Cancel 對應二階段的提交和回滾。
圖中,事務開啟的時候,由發起方去觸發一階段的方法,然後根據各個參與者的返回狀態,決定二階段是調 Confirm 還是 Cancel 方法。
螞蟻金服分散式事務介紹
2019年,螞蟻金服跟阿里巴巴共同開源了分散式事務 Seata ,目前 Seata 已經有 TCC、AT、Saga 模式,Seata 意為:Simple Extensible Autonomous Transaction Architecture,是一套一站式分散式事務解決方案。今天的分享也是 Seata 在螞蟻金服內部的實踐。
分散式事務在螞蟻金服的發展
基於上述的理論,接下來我們詳細看下螞蟻金服的分散式事務實現。
經過多年的發展,螞蟻金服內部針對不同的場景發展了幾種不同的模式,最早的是 TCC 模式,也就是上面講的 Try - confirm - Cancel,我們定義介面規範,業務自己實現這3個操作。這個模式提供了更多的靈活性,因為是業務自己實現的,使用者可以介入兩階段提交過程,以達到特殊場景下的自定義優化及特殊功能的實現,這個模式能幾乎滿足任何我們想到的事務場景,比如自定義補償型事務、自定義資源預留型事務、訊息事務等場景。TCC 模式廣泛用於螞蟻金服內部各金融核心系統。
這裡要強調一點的是,TCC 模式與底層資料庫事務實現無關,是一個抽象的基於 Service 層的概念,也就是說,在 TCC 的範圍內,無論是關係型資料庫 MySQL,Oracle,還是 KV 儲存 MemCache,或者列式儲存資料庫 HBase,只要將對它們的操作包裝成 TCC 的參與者,就可以接入到 TCC 事務範圍內。
TCC 模式的好處是靈活性,弊端是犧牲了易用性,接入難度比較大,所有參與者需要進行改造提供 Try - Confirm - Cancel 三個方法。為了解決 TCC 模式的易用性問題,螞蟻金服分散式事務推出了框架管理事務模式(Framework - Managed Transactions,簡稱 FMT),也就是 Seata 中的 AT 模式。FMT 模式解決分散式事務的易用性問題,最大的特點是易於使用、快速接入、對業務程式碼無侵入。
XA 模式是依賴於底層資料庫實現的。
Saga 模式是基於衝正模型實現的一個事務模式,現在的銀行業金融機構普遍用的是衝正模型。
這期我們重點講 TCC 和 FMT,關於 Saga 模式,之前 Saga 模式也有專場直播分享過,感興趣的可以看一下之前的直播回顧:《Seata 長事務解決方案 Saga 模式 | SOFAChannel#10 回顧》。
TCC 模式在螞蟻金服內的使用
首先看下 TCC 模式,主要包含一下幾個模組:
- 參與者,它要實現全部的三個方法,prepare、commit 和 rollback;
- 發起方,主要是作為協調者的角色,編排各個參與者,比如呼叫參與者的一階段方法,決策二階段是執行提交還是回滾;
舉個例子,比如在這個流程圖中,存在1個發起方和2個參與者,兩個參與者分別實現了 prepare,commit 和 rollback 介面,第一階段被包含在發起方的本地事務模版中,也就是說,發起方負責呼叫各個參與者的一階段方法,發起方的本地事務結束後,開始執行二階段操作,二階段結束,整個分散式事務結束。
二階段是通過 spring 提供的事務同步器實現的,發起方在發起一個分散式事務的時候,會註冊一個事務同步器,當發起方本地事務結束的時候,會進入事務同步器的回撥方法中,如果發起方的本地事務失敗,則在回撥中自動回滾所有參與者,如果發起方的本地事務成功,則二階段自動提交所有參與者。二階段結束後,刪除所有事務記錄。
總結一下:
- 事務發起方是分散式事務的協調者;
- 分散式事務必須在本地事務模板中進行;發起方本地事務的最終狀態(提交或回滾)決定整個分散式事務的最終狀態;
- 發起方主職責:開啟事務(start)+呼叫參與者一階段方法,第二階段由框架自動呼叫;
- 使用資料庫持久化記錄事務資料,也就是會跟蹤發起方和各個參與者的狀態,我們稱為主事務狀態和分支事務狀態;
再回到剛才那張圖,綠顏色的這兩個模組:“start”和“二階段”的呼叫,其實是框架提供的,發起方實現的時候,首先開啟一個本地事務,呼叫 start 開啟分散式事務,框架會自動註冊一個 spring 事務同步器,然後發起方發起對參與者 prepare 方法的呼叫,當有一個 prepare 方法失敗,則阻斷髮起方本地事務,狀態置為回滾;否則,所有的參與者 prepare 成功,整個分散式事務的狀態就是提交。框架會利用事務同步器自動去執行參與者的二階段方法。
上面講了整個流程以及發起方的實現內容,現在看下業務在實現參與者的時候,需要遵循以下規範:
- 業務模型分二階段設計;
- 冪等控制;
- 併發控制;
- 允許空回滾;
- 防懸掛控制;
我們逐個瞭解一下:
- 二階段設計
二階段設計和冪等控制比較容易明白。二階段設計就是一階段的資源預留和二階段的提交回滾。
比如以扣錢場景為例,賬戶 A 有 100 元,要扣除其中的 30 元。一階段要先檢查資源是否足夠,賬戶餘額是否大於等於 30 塊,資源不足則需要立馬返回失敗;資源足夠則把這部分資源預留起來,預留就是鎖資源,鎖的粒度可大可小,儘量是按照最小粒度、儘快釋放的原則來,比如這裡引入一個“凍結部分”的欄位,“可用餘額”在一階段後就能立馬得到釋放,鎖的是凍結欄位。
二階段,如果是提交則真正扣除凍結的 30 元;如果是回滾的話,則把凍結部分加回可用餘額裡。
我們看個具體的客戶案例,網商銀行在使用 TCC 時,劃分了三層,最上一層是具體的業務平臺,承接著外部不斷變化的業務需求;中間是資產交換服務,是事務發起方層,由它來發起和編排各種不同的事務鏈路;最底下一層是事務參與者層,提供最基礎的服務,比如存款核心提供的存入、支出、凍結、解凍服務,借記賬務的各種原子服務等。
看下我們日常生活中常見的幾個金融業務場景,支出、存入、凍結、解凍、提現、手續費和銷戶。提現場景,比如信用卡提現至銀行卡,類似 A 到 B 的轉賬;手續費,跟轉賬類似;銷戶場景,主要操作是校驗餘額、賬戶結清、最後改狀態銷戶,沒有用到分散式事務。
下面重點介紹一下其他 4 個場景:支出(扣款)、存入(記入)、凍結和解凍四個 Case。
首先,看下賬戶表的設計,前面說過,在設計的時候,需要儘可能減少鎖的時間和鎖的粒度,這裡賬戶表有這4個欄位:當前餘額、未達金額、業務凍結金額和預凍結金額。顯示的可用餘額 = 當前金額 - 預凍結 - 業務凍結金額。
支出(扣款)場景
先來看下支出(扣款)場景下,賬戶表裡各欄位的數額變化。初始狀態下,顯示的賬戶餘額,和當前餘額是一致的。TCC 的一階段檢查並預留資源,這裡對應的資源是“預凍結金額”欄位,預凍結金額設定為 100 元,當前餘額不變。因為 100 塊被預凍結了,顯示給使用者的可用餘額現在是 900 元。如果二階段是提交的話,就釋放預凍結金額,扣除當前餘額,賬戶的當前餘額就是 900 元。如果二階段不是提交,是回滾,這裡就是把一階段的資源釋放,也就是把預凍結金額釋放回去,顯式的賬戶餘額重新變成 1000 元。
存入場景
上面是支出(扣款)場景,再來看下存入的場景。初始狀態還是當前餘額和顯式的可用餘額都是1000元。因為是存入,一階段的話就是“未達金額”加 100 元,顯示的可用餘額還是不變。二階段如果是提交,就把未達金額清除,把這部分的錢加到當前餘額,當前餘額就是 1100 元了。如果二階段是回滾,直接清除一階段的未達金額即可。
凍結場景
凍結場景則是在一階段是資源預留,就是預凍結,預凍結金額欄位設定為 100 元,顯示給使用者的可用餘額也要少 100 塊。二階段如果是提交,就是真正凍結,把預凍結金額釋放,新增業務凍結金額。二階段回滾的話,就是把一階段的預凍結釋放。
解凍場景
最後看下解凍場景,一階段檢查賬戶狀態是不是可用,二階段如果提交,就釋放凍結金額,顯示的可用餘額就多了 100 元。二階段如果是回滾狀態,就什麼都不用做。
以上分享了接入 TCC 如何進行二階段設計以及如何進行資源預留,用實際的金融場景分析了下 TCC 一二階段需要做的事情。因為二階段設計是 TCC 接入的關鍵,所以進行了重點闡述。接下來我們繼續看 TCC 設計的其他規範。
- 冪等控制
冪等控制,就是 Try-Confirm-Cancel 三個方法均需要保持冪等性。
- 併發控制
併發控制即當兩個併發執行的分散式事務操作同一個賬號時,凍結的部分是相互隔離的,也就是 T1 凍結金額只能被事務 1 使用,T2 凍結金額只能被事務 2 使用。凍結資源與事務 ID 之間建立關聯關係。
- 允許空回滾
首先對空回滾的定義就是 Try 未執行,Cancel 先執行了。正常是一階段的請求先執行,然後才是二階段的請求。出現空回滾的原因,是網路丟包導致的,呼叫 Try 方法時 RPC timeout 了,分散式事務回滾,觸發 Cancel 呼叫;參與者未收到 Try 請求而收到了 Cancel 請求,出現空回滾。
我們在設計參與者時,要支援這種空回滾。
- 防懸掛
懸掛的定義是 Cancel 比 Try 先執行。不同於空回滾,空回滾是 Try 方法的請求沒有收到。懸掛是 Try 請求到達了,只不過由於網路擁堵,Try 的請求晚於二階段的 Cancel 方法。
整個流程是這樣的:
- 呼叫 TCC 服務 Try 方法,網路擁堵(未丟包),RPC超時;
- 分散式事務回滾;
- TCC 服務 Cancel 被呼叫,執行了空回滾;整個分散式事務結束;
- 被擁堵的 Try 請求到達 TCC 服務,並被執行;出現了二階段 Cancel 請求比一階段 Try 請求先執行的情況,TCC 參與者懸掛;
解決懸掛的問題,可以跟蹤事務的執行,如果已經回滾過了,一階段不應該正常執行,這時候要拒絕 Try 的執行。
FMT 模式在螞蟻金服內的使用
接下來我們來看一下 FMT(Framework-Managerment-Transaction)框架管理事務模式。
之前介紹幾個事務模式的時候,說過 TCC 模式雖然靈活,功能強大,能做很多定製和優化,但是使用難度上比較大,業務系統要進行二階段改造,編碼工作非常多。
針對那些對效能要求並不高,業務體量並不大的中小業務,我們推出了 FMT 模式——框架管理事務,從名字上看,就是大部分工作由框架自動完成,業務只需要關注實現自己的業務 SQL 即可。
FMT 還是基於二階段的模型,業務只需要關注一階段實現自己的業務 SQL,二階段的自動提交回滾由框架來完成。
框架託管的二階段,需要基於對一階段的分析。在一階段中,會執行下面幾個步驟:對 SQL 進行解析,提取表的後設資料,儲存 SQL 執行前的值,執行 SQL,儲存執行後的快照,儲存行鎖。
下面看下每個階段具體做的事:
查詢操作不涉及事務,我們這裡以一個更新操作為例,首先要對操作的 SQL 進行語法語義分析,提取出關於這條記錄的全部資訊,包括是屬於哪張表、查詢條件是什麼、有哪些欄位、這些記錄的主鍵等,這些資訊可以通過 JDBC MetaData api 就能拿到。
然後我們開始儲存執行前的快照資料,把目標記錄的所有欄位的當前值存到 undo log 裡,存完後真正執行 SQL,SQL 執行後原來的一些欄位值就已經產生變化了,我們把新的快照資料存到 redo log 裡。最後把表名稱和記錄主鍵值存到行鎖表,代表當前這個事務正在操作的是哪些記錄。
有了這些資訊後,框架就完全能自己去執行二階段操作了。比如,當事務需要進行二階段提交,因為在一階段裡業務SQL 已經執行了,二階段只需要把產生的中間資料刪掉即可。當二階段回滾時,因為我們儲存了 SQL 執行的快照資料,所以還原回執行前的快照資料即可,同時把中間資料刪掉。
這裡我們知道了 undo 和 redo log 的作用,接下來講講行鎖。行鎖是用來進行併發控制的。當一個事務在操作一條記錄前,會先去行鎖表裡查下有沒有這條記錄的鎖資訊,如果有,說明當前已經有一個事務搶佔了,需要等待那個事務把鎖釋放。圖中,事務 1 在一階段對記錄上鎖,這個時候事務 2 進來,只能等待,等事務 1 二階段提交,把鎖釋放,事務 2 這時候才能加鎖成功。
極致效能優化
最後,我們看看在螞蟻金服內部,針對雙十一、雙十二這種大促,為了達到更好的效能狀態,做的一些優化。
二階段非同步化
一個是二階段非同步化,因為一階段的結果已經能決定整個事務的狀態了,而且資源也都預留好了,剩下的二階段可以等請求峰值過後再去執行。這樣,分散式事務耗時由執行 prepare+commit 或者 prepare+Cancel 縮減成 prepare,提高了吞吐量。雖然結果有延遲的,但最終結果無任何影響。
非同步的二階段方法,在請求洪峰過後,會由事務恢復服務撈起執行。
同庫模式
另一個優化,在事務記錄上。分散式事務在推進過程中,會記錄事務日誌,如果這個事務日誌是放到 Server 這邊的,發起方更新事務狀態時,需要跨 RPC 呼叫到業務方那邊,每次記錄狀態的就是同庫執行的,減少 RPC 呼叫次數。
總結
以上就是本期分享的全部內容,我們先從事務產生的背景入手,在現在分散式架構的體系結構下,跨服務協同呼叫是常態,而網路、資料庫、機器等都具有不可靠性,如果保證這中間操作要麼全部成功,要麼全部失敗,是大家面臨的共同問題,特別是金融場景下,對解決這個問題更有迫切性,螞蟻金服作為一家金融科技公司,在這方面也進行了探索,積累了很多經驗。
在介紹螞蟻金服的分散式事務中介軟體之前,先介紹了一些分散式事務的理論背景,包括兩階段協議和 TCC 協議。基於理論背景,重點介紹了螞蟻金服在分散式事務上的 TCC、FMT 模式的應用,分享了實現原理和設計規範以及 TCC 二階段設計等。最後介紹了針對雙十一雙十二這種大促活動,如何進行二階段非同步化和同庫模式的優化,來支撐零點峰值時的洪峰請求。
以上就是本期分享的全部內容,如果大家對螞蟻金服在分散式事務中的實踐以及 Seata 有問題跟興趣,也可以在群內與我們交流。
本期視訊回顧以及 PPT 檢視地址
公眾號:金融級分散式架構(Antfin_SOFA)