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 模式二階段處理而二階段是回滾時,則要多做一些處理。
首先在 Client 端收到 TC 告知的二階段是回滾時,會去查到對應的事務的 undolog,取出後鏡像,對比當前的資料(因為 SeataAT 是從業務應用層面進行保護分散式事務,如果此時在資料庫層面直接修改了庫內資訊,這個時候 SeataAT 的行鎖不起隔離性作用),如果出現了在全域性事務以外的資料修改,此時判定為髒寫,而 Seata 因為無法感知這個髒寫如何發生,此時只能列印日誌和觸發異常通知,告知使用者需要人工介入(規範修改資料入口可避免髒寫)。
既然介紹完了 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 事務在分支資料量大的時候成為一個大的心梗開銷。
總結AT 說到底就是實現對資源操作的代理,並記錄原先 & 變更後的狀態,並用鎖保證該資料的隔離性。在呼叫鏈中出現異常時,還原所有分支資料,達到分散式事務下的“原子性”。
未來呢?redis,mongodb,mq? 盡情期待。
Seata 專案的最核心的價值在於:構建一個全面解決分散式事務問題的標準化平臺。