作者 | 陳健斌(funkye) github id: a364176773
來源|阿里巴巴雲原生公眾號
Seata 是一款開源的分布式事務解決方案,star 高達 18100+,社區活躍度極高,致力於在微服務架構下提供高性能和簡單易用的分布式事務服務,本文將剖析 Seata-AT 的實現原理,讓用戶對 AT 模式有更深入的認識。
Seata 事務模式是什么?
1. Seata 對事務的定義
Seata 定義了全局事務的框架。
全局事務定義為若干分支事務的整體協調:
- TM 向 TC 請求發起(Begin)、提交(Commit)、回滾(Rollback)全局事務。
- TM 把代表全局事務的 XID 綁定到分支事務上。
- RM 向 TC 注冊,把分支事務關聯到 XID 代表的全局事務中。
- RM 把分支事務的執行結果上報給 TC。(可選)
- TC 發送分支提交(Branch Commit)或分支回滾(Branch Rollback)命令給 RM。
Seata 的全局事務處理過程,分為兩個階段:
執行階段> :執行分支事務,並保證執行結果滿足是可回滾的(Rollbackable)和持久化的(Durable)。
完成階段> :根據執行階段結果形成的決議,應用通過 TM 發出的全局提交或回滾的請求給 TC,> TC 命令 RM 驅動 分支事務 進行 Commit 或 Rollback。Seata 的所謂事務模式是指:運行在 Seata 全局事務框架下的分支事務的行為模式。> > 准確地講> ,應該叫作> 分支事務模式> 。
不同的事務模式區別在於分支事務使用不同的方式達到全局事務兩個階段的目標。> > 即,回答以下兩個問題:
執行階段> :如何執行並保證執行結果滿足是可回滾的(Rollbackable)和持久化的(Durable)。
完成階段> :收到 TC 的命令后,做到事務的回滾/提交。
2. 其它二階段事務如何在 Seata 事務框架下運轉
1)TCC 事務模式
首先來看下 TCC 事務如何融合在 Seata 事務框架中:
可以發現,其實跟 Seata 的事務框架圖長得非常像,而區別為 RM 負責管理就是一階段的 try 執行和二階段的 confirm/cancel,一樣是由 TM 進行事務的 Begin(發起),RM 被 TM 調用后執行一階段的 Try 方法,等待調用鏈路走完的時候,TM 向 TC 告知二階段決議,此時 TC 對 RM 驅動二階段執行(下發通知,RM 執行 confirm/cancel)。
2)XA 事務模式
如圖所示,XA 模式其實就是 Seata 底層利用了 XA 接口,在一階段二階段時自動處理。如一階段時,XA 的 RM 通過代理用戶數據源,創建 XAConnection,進行開啟 XA 事務(XA start)和 XA-prepare(此時 XA 的任何操作都會被持久化,即便宕機也能恢復),在二階段時,TC 通知 RM 進行 XA 分支的 Commit/Rollback 操作。
AT 模式是什么?
首先來看一個例子。
1. 一階段
業務 sql:update product set name = 'GTS' where name = 'TXC'。
一階段的執行過程對用戶是無感知的,用戶側的業務 sql 保持不變,而 AT 模式下一階段具體發生了什么?接下來,簡單說下。
- 解析 sql 並查詢得到前鏡像:select id, name, since from product where name = 'TXC'。
- 執行業務 sql。
- 查詢執行后的數據作為后鏡像:select id, name, since from product where id = 1。
2. 二階段
提交:僅需把事務相關信息刪除即可(理論上不刪除也沒問題)。
回滾:取出前鏡像進行回滾。
通過上述簡單的例子,其實可以發現,AT 模式就是自動補償式事務,那 AT 具體都做了哪些呢?下文將會講述。
AT 如何保證分布式事務一致性?
先來看這個圖:
可能很多人剛看到上圖會有疑問,其實這個就是無侵入式 AT 模式的做法示意圖。首先用戶還是從接口進入,到達事務發起方,此時對業務開發者來說,這個發起方入口就是一個業務接口罷了,一樣地執行業務 sql,一樣地 return 響應信息給客戶端並沒有什么改變。而背后就是用戶的 sql 被 Seata 代理所托管,Seata-AT 模式能感知到用戶的所有 sql,並對之進行操作,來保證一致性。
Seata-AT 是怎么做到無侵入的呢?
如圖所示,應用啟動時 Seata 會自動把用戶的 DataSource 代理,對 JDBC 操作熟悉的用戶其實對 DataSource 還是比較熟悉的,拿到了 DataSource,就等於掌握了數據源連接,也就能在背后做些“小動作”,此時對用戶來講也是無感知無入侵。
之后業務有請求進來,執行業務 sql 時,Seata 會解析用戶的 sql,提取出表元數據,生成前鏡像,再通過執行業務 sql,保存執行 sql 后的后鏡像(至於后鏡像的介紹之后會講到),生成行鎖之后在注冊分支時攜帶到 Seata-Server,也就是 TC 端。
到此為止,在 Client 端的一階段操作就已經完成了,無感知、無入侵。此時如果思考下,會發現這里其實有一個行鎖,這個行鎖是干什么用的呢?這就是要接着講到 Seata-AT 是如何保證分布式下的事務隔離性,這里直接拿官網的示例來說。
1. 寫隔離
- 一階段本地事務提交前,需要確保先拿到全局鎖 。
- 拿不到全局鎖,不能提交本地事務。
- 拿全局鎖的嘗試被限制在一定范圍內,超出范圍將放棄,並回滾本地事務,釋放本地鎖。
以一個示例來說明:
兩個全局事務 tx1 和 tx2,分別對 a 表的 m 字段進行更新操作,m 的初始值 1000。
tx1 先開始,開啟本地事務,拿到本地鎖,更新操作 m = 1000 - 100 = 900。本地事務提交前,先拿到該記錄的全局鎖,本地提交釋放本地鎖。tx2 后開始,開啟本地事務,拿到本地鎖,更新操作 m = 900 - 100 = 800。本地事務提交前,嘗試拿該記錄的全局鎖,tx1 全局提交前,該記錄的全局鎖被 tx1 持有,tx2 需要重試等待**全局鎖 **。
tx1 二階段全局提交,釋放全局鎖 。tx2 拿到全局鎖提交本地事務。
如果 tx1 的二階段全局回滾,則 tx1 需要重新獲取該數據的本地鎖,進行反向補償的更新操作,實現分支的回滾。
此時如果 tx2 仍在等待該數據的全局鎖,同時持有本地鎖,則 tx1 的分支回滾會失敗。分支的回滾會一直重試,直到 tx2 的全局鎖等鎖超時,放棄全局鎖並回滾本地事務釋放本地鎖,tx1 的分支回滾最終成功。
因為整個過程全局鎖在 tx1 結束前一直是被 tx1 持有的,所以不會發生臟寫的問題。
這個時候隔離性想必大家已經比較明白了,此時一階段的大部分操作相信大家也比較明白了,接下來我們繼續往下一階段解析。
2. AT 模式二階段處理
由上圖可見,在二階段提交時,TC 僅是下發一個通知 :把之前一階段做記錄的 undoLog 刪除,並把相關事務信息如:行鎖刪除,之后讓因為在競爭鎖被阻塞的事務順利進行。
而二階段是回滾時,則要多做一些處理。
首先在 Client 端收到 TC 告知的二階段是回滾時,會去查到對應的事務的 undolog,取出后鏡像,對比當前的數據(因為 SeataAT 是從業務應用層面進行保護分布式事務,如果此時在數據庫層面直接修改了庫內信息,這個時候 SeataAT 的行鎖不起隔離性作用),如果出現了在全局事務以外的數據修改,此時判定為臟寫,而 Seata 因為無法感知這個臟寫如何發生,此時只能打印日志和觸發異常通知,告知用戶需要人工介入(規范修改數據入口可避免臟寫)。
而如果沒有發生臟寫就比較簡單了,拿出前鏡像,眾所皆知事務是需要有原子性的,要么一起發生,要么都不發生,此時前鏡像記錄了發生之前的數據,進行回滾后,就達到了類似本地事務那樣的原子性效果。回滾后,再把事務相關信息,如 undolog,行鎖進行刪除。二階段回滾算是告一段落了。
既然介紹完了 AT 模式的一階段及二階段的原理思想方式,那么 AT 在 Seata 的分布式事務框架下是怎么樣的呢?
可以看到,AT 與其它事務模式在 Seata 事務框架中,會多出一個 undolog 的表(相對其它模式的入侵點),但是除此之外,對業務來說,幾乎是零入侵性,這也就是為什么 AT 模式在 Seata 中受眾廣泛的原因。
3. AT 模式與 Seata 支持的其它二階段模式區別
首先應該明白,目前為止,不存在有任何一種分布式事務的可以滿足所有場景。
無論 AT 模式、TCC 模式還是 Saga 模式,這些模式的提出,本質上都源自 XA 規范對某些場景需求的無法滿足。
目前分為 3 點來做出對比:
- 數據鎖定
AT 模式使用全局鎖保障基本的寫隔離,實際上也是鎖定數據的,只不過鎖在 TC 側集中管理,解鎖效率高且沒有阻塞的問題。
TCC 模式無鎖,利用本地事務排他鎖特性,可預留資源,在全局事務決議后執行相應操作。
XA 模式在整個事務處理過程結束前,涉及數據都被鎖定,讀寫都按隔離級別的定義約束起來。
- 死鎖(協議阻塞)
XA 模式 prepare 后(老版本的數據庫中,需要 XA END 后,再下發 prepare <三階段由來>),分支事務進入阻塞階段,收到 XA commit 或 XA rollback 前必須阻塞等待。
AT 可支持降級,因為鎖存儲在 TC 側,如果 Seata 出現 bug 或者其它問題,可直接降級,對后續業務調用鏈無任何影響。
TCC 無此問題。
- 性能
性能的損耗主要來自兩個方面:一方面,事務相關處理和協調過程,增加單個事務的 RT;另一方面,並發事務數據的鎖沖突,降低吞吐。其實主要原因就是上面的協議阻塞跟數據鎖定造成。
XA 模式它的一階段不提交,在大並發場景由於鎖存儲在多個資源方(數據庫等),加劇了性能耗損。
AT 模式鎖粒度細至行級(需要主鍵),且所有事務鎖存儲在 TC 側,解鎖高效迅速。
TCC 模式性能最優,僅需些許 RPC 開銷,及 2 次本地事務的性能開銷,但是需要符合資源預留場景,且是對業務侵入性較大(需要業務開發者每個接口分為 3 個,一個 try,2 個二階段使用的 confirm 和 cancel )。
可能很多同學對 XA 和 AT 的鎖 & 協議阻塞不是特別理解,那么直接來看下圖:
可以試着猜一下是哪個是 XA?其實下圖的是 XA,因為它帶來的鎖粒度更大,且鎖定時間更久,導致了並發性能相對 AT 事務模型來說,差的比較多,所以至今XA模式的普及度都不很太高。
Seata 近期規划
- 控制台
首先控制台是 Seata 用戶暴露已久的一個問題,沒有一個可視化界面,使得用戶對 Seata 的可靠性出現了懷疑,更由於沒有控制台,局限了很多在 Seata 上可人工介入分布式事務的可能性等問題,所以未來在 1.5.0 的版本會帶來控制台的加入,也歡迎更多的同學加入進來一起共建!
- Raft 集成
Raft 集成的原因,可能大部分用戶不是特別知曉,首先要知道目前 TC 端的事務信息都是存儲在外部存儲器,比如數據庫、redis、mongodb(PR 階段),這就造成了如果外部存儲宕機,Seata-Server 集群的完全不可用。即便 Server 是集群部署,有 10 個甚至更多節點,都會因此而不可用,這是不可接受的。
所以引入 Raft 來讓每個 Seata-Server 的事務信息達到一致,即便某個節點宕機,也不會破壞事務信息准確性,從而也讓分布式事務的一致性得到了更好的保證。(關於 Seata-Server raft 的實現之后會以新篇章來分享。)
- undoLog 壓縮
這個是 1.5.0 AT 模式比較大的性能優化,由於一階段操作的數據多且大,因為 Seata 在背后為用戶插入了 undolog 信息,由此可能也會變得大,有造成了入庫緩慢的可能,所以要把 undolog 進行壓縮,使 undolog 的插入不再成為 AT 事務在分支數據量大的時候成為一個大的心梗開銷。
以下是 Seata 的交流群歡迎大家加入:
-
釘釘群:搜索群號 32033786 進群
-
QQ 群:搜索 216012363 進群
總結
AT 說到底就是實現對資源操作的代理,並記錄原先 & 變更后的狀態,並用鎖保證該數據的隔離性。在調用鏈中出現異常時,還原所有分支數據,達到分布式事務下的“原子性”。
未來呢?redis,mongodb,mq? 盡情期待。
Seata 項目的最核心的價值在於:構建一個全面解決分布式事務問題的標准化平台。
基於 Seata,上層應用架構可以根據實際場景的需求,靈活選擇合適的分布式事務解決方案,非常歡迎大家參與到項目的建設中,共同打造一個標准化的分布式事務平台。