前言
在上兩篇博文(分散式事務與Seate框架(1)——分散式事務理論、分散式事務與Seate框架(2)——Seata實踐)中已經介紹並實踐過Seata AT模式,這裡一些例子與概念來自這兩篇(特別是第一篇理論部分),如果有不懂的小夥伴可以先看看,這裡主要是講解Seata AT模式的實現原理。
又好久沒有記錄博文了,這篇其實是很早之前就記錄好了的,但是一直沒時間去寫出來,今天發出來算是再次對Seata分散式有個加深!
一、AT模式介紹
同樣地,還是得先複習下分散式事務的相關理論部分:AT模式是Seata最主推的分散式事務且基於XA演進而來的解決方案,主要有三個角色:TM、RM和TC,其中TM和RM作為Seata的客戶端和業務整合,TC作為Seata伺服器獨立部署。TM向TC註冊一個全域性事務,並生成全域性唯一的XID;在AT模式下,資料庫資源被當做RM,訪問RM時,Seata會對請求進行攔截;每個本地事務提交時,RM會向TC(Transaction Coordinator,事務協調器)註冊一個分支事務。
從三個角色TM、RM和TC的角度來記錄具體的流程的話,可以總結如下:
- TM向TC註冊全域性事務,並生成全域性唯一XID;
- RM向TC註冊分支事務,並將其納入到該XID對應的全域性事務範圍。
- RM向TC彙報資源的準備狀態
- TC彙總所有事務參與者的執行狀態,決定分散式事務全部回滾還是提交
- TC通知所有的RM提交/回滾事務
從具體的微服務角度總結,假設分別有三個:MicroService-1, MicroService-2, MicroService-3,流程圖如下:
二、Seata AT模式的實現原理
1. AT模式的第一階段實現
解析業務SQL,更新業務資料,儲存更新前後映象到undo_log。在業務運算元據庫流程中,Seata會基於資料來源代理(0.9以後支援自動代理)對原執行SQL進行解析。之後通過本地事務的ACID特性,將這兩步操作(儲存業務資料前後映象到undo_log, 更新業務資料)在本地事務中進行提交。
比如(引用第二篇博文中的例子),我們對庫存表庫存進行減一,執行:
UPDATE repo SET amount=amount-1 WHERE id=1
則第一階段需要做的步驟:
(1)通過DataSourceProxy對業務SQL進行解析,得到SQL的表、WHEER條件等,之後通過查詢修改之前的資料:
SELECT id, name, amount, price FROM repo WHERE id=1;
這樣就得到了修改之前的映象,此時amount=100;
(2)執行更新業務:
即執行update語句,此時amount=99;
(3)查詢修改之後的資料映象,此時是通過第二步獲取到的主鍵查詢到修改之後的資料的
SELECT id, name, amount, price FROM repo WHERE id=1;
這樣就有了修改之後的資料映象了。
(4)插入回滾日誌
有了修改前後的業務映象資料之後,可以將業務映象資料與業務相關的SQL資訊構造成一條undo_log記錄,插入undo_log表中:
(如果要看到undo_log記錄,則需要在debug模式下或者日誌列印輸出,否則事物回結束後會刪除該記錄)
(5)向TC註冊分支事務,並且申請關於id=1記錄的全域性鎖
(6)本地事務開始提交,將undo_log資料插入與更新業務資料一併提交(只有釋放全域性鎖)
(7)將本地事務提交的結果告訴TC。
從AT模式的第一階段來看,不同於XA模式的主要有以下幾點(或者說優勢):
- 分支事務在第一階段提交完成之後就馬上釋放本地事務鎖定的資源。而XA模式一般是在鎖定資源的持續到第二階段的提交或者回滾後才釋放資源。
- AT模式第一階段就釋放了資源,這樣就會讓AT模式提升了分散式事務處理效率;
- 之所以在第一階段就釋放了資源,這是因為AT模式下,在第一階段就記錄了undo_log回滾日誌,所以不怕第二階段出現異常。
2.AT模式第二階段的原理分析
TC服務接收到事務分支的事務狀態彙報之後,決定對全域性事務進行提交或者回滾,這就是第二階段。
2.1.事務提交
如果在第一階段所有的分支事務已經提交,則TC決定全域性事務提交,此時只需要清理UNDO_LOG日誌即可,相比較XA模式,不需要TC觸發所有分支事務的提交。
具體的流程為:
(1) 分支事務收到TC的提交請求之後放入非同步佇列中,馬上返回提交成功的結果;這裡不需要同步返回的原因是:TC不需要知道分支事務的結果,因為僅僅只是一步刪除UNDO_LOG記錄的操作,即使不成功也不會結果造成影響,所以採用非同步是有效的方式。
(2) 從非同步佇列中執行分支提交請求,清理undo_log日誌,這裡並不需要分支事務的提交了,因為第一階段中已經提交過了。
理解起來就是:AT模式下的全域性事務的提交只需要清理UNDO_LOG記錄就行,不需要管分支(本地)事務的提交結果!
2.2.事務回滾
在第一階段的分支事務中任何一個分支事務執行失敗,都會進入全域性事務回滾流程,顯而易見全域性事務的回滾主要是依賴UNDO_LOG中的記錄進行補償的。
具體的流程如下:
接收到TC的回滾請求後,開啟本地事務用來執行回滾操作
(1)本地事務分支開始進行對查詢UNDO_LOG記錄
通過XID+branch ID查到UNDO_LOG記錄;
(2)資料校驗(對比更新後映象資料與當前資料)
拿到rollback_for中afterImage映象資料與當前業務表中資料比較,不同的話,比如afterImage映象資料拿出的amount理論上為99,但是實際上amount=98,則說明已經被當前全域性事務外的某個操作做了修改(實際上由於全域性鎖的存在,並不會存在其他全域性事務對業務資料進行更新),那麼事務不進行回滾。
(3)第二步中對比結果是相等的話,就採用beforeImage和SQL的相關資訊進行回滾,即:
UPDATE rep SET acoumt=100 WHERE id=1
(4)刪除UNDO_LOG記錄
(5)提交本地事務
(6)將本地事務的回滾執行結果報告給TC
3.AT模式下事務的隔離性保證
在AT模式下,多個全域性事務操作同一張表時,它的事務隔離性保證是基於全域性鎖來實現的。
(1)寫隔離
在第一階段本地事務提交之前,必須確保拿到全域性鎖,如果拿不到全域性鎖則一直等待嘗試,超出最大嘗試次數則放棄全域性鎖的獲取,並回滾釋放本地鎖(在本地事務開始之前就獲到本地鎖,這裡的本地鎖概念是資料庫鎖,比如對某行記錄的行鎖)
(2)讀隔離
資料庫不設定事務隔離級別下發生三種情況(相當於複習下資料庫隔離級別相關理論知識):
髒讀:髒讀是指一個事務在處理資料的過程中,讀取到另一個未提交事務的資料。
不可重複讀:不可重複讀是指對於資料庫中的某個資料,一個事務範圍內的多次查詢卻返回了不同的結果,這是由於在查詢過程中,資料被另外一個事務修改並提交了。
幻讀:一個事務按相同的查詢條件重新讀取以前檢索過的資料,卻發現其他事務插入了滿足其查詢條件的新資料,這種現象就稱為幻讀。也就是第一個事務讀取的時候發現其他事務插入(或刪除)的事務,就跟幻覺一樣。
幻讀跟不可重複讀都是讀取了另一個事務提交的結果,而不可重複讀的重點是修改,幻讀的重點在於新增或者刪除。
在資料庫本地事務隔離級別為RR或者以上時,Seata AT事務模式的預設全域性隔離級別是Read Uncommit,在這種隔離級別下,所有事務都可以看到其它未提交事物的執行結果產生髒讀,這在最終一致性的事務模型是被允許的,並且大部分是分散式事務是接受髒讀的。