深度剖析分佈式事務之 AT 與 XA 對比

AT 這種事務模式是阿里開源的 seata 主推的事務模式,本文會詳解 AT 的原理,並將它與 XA 模式進行比較

原理

AT 從原理上面看,與 XA 的設計有很多相近之處。XA 是數據庫層面實現的二階段提交, AT 則是應用 / 驅動層實現的二階段提交。建議您瞭解了 XA 相關的知識後,來閱讀這篇文章,這樣能夠更快更好的掌握 AT 的原理與設計。

AT 的角色和 XA 一樣分爲 3 個,但是起了不一樣的名稱,大家注意分辨:- RM 資源管理器,是業務服務,負責本地數據庫的管理,與 XA 中的 RM 一致 - TC 事務協調器,是 Seata 服務器,負責全局事務的狀態管理,負責協調各個事務分支的執行,相當於 XA 中的 TM - TM 事務管理器,是業務服務,負責全局事務的發起,相當於 XA 中的 APP

AT 的第一階段爲 prepare,它在這一階段會完成以下事情:1. RM 側,用戶開啓本地事務 2. RM 側,用戶每進行一次業務數據修改,假設是一個 update 語句,那麼 AT 會做以下內容:1. 根據 update 的條件,查詢出修改前的數據,該數據稱爲 BeforeImage 2. 執行 update 語句,根據 BeforeImage 中的主鍵,查詢出修改後的數據,該數據稱爲 AfterImage 3. 將 BeforeImage 和 AfterImage 保存到一張 undolog 表 4. 將 BeforeImage 中的主鍵以及表名,該數據稱爲 lockKey,記錄下來,留待後續使用 3. RM 側,用戶提交本地事務時,AT 會做以下內容:1. 將 2.4 中記錄的所有的 lockKey,註冊到 TC(即事務管理器 seata)上 2. 3.1 中的註冊處理會檢查 TC 中,是否已存在衝突的主鍵 + 表名,如果有衝突,那麼 AT 會睡眠等待後重試,沒有衝突則保存 3. 3.1 成功完成後,提交本地事務

如果 AT 的第一階段所有分支都沒有錯誤,那麼會進行第二階段的 commit,AT 會做以下內容:1. TC 會將當前這個全局事務所有相關的 lockKey 刪除 2. TC 通知與當前這個全局事務相關的所有業務服務,告知全局事務已成功,可以刪除 undolog 中保存的數據 3. RM 收到通知後,刪除 undolog 中的數據

如果 AT 的第一階段有分支出錯,那麼會進行第二階段的 rollback,AT 會做以下內容:1. TC 通知與當前這個全局事務相關的所有業務服務,告知全局事務失敗,執行回滾 2. RM 收到通知後,對本地數據的修改進行回滾,回滾原理如下:1. 從 undolog 中取出修改前後的 BeforeImage 和 AfterImage 2. 如果 AfterImage 與數據庫中的當前記錄校驗一致,那麼使用 BeforeImage 中的數據覆蓋當前記錄 3. 如果 AfterImage 與數據庫中的當前記錄不一致,那麼這個時候發生了髒回滾,此時需要人工介入解決 3. TC 待全局事務所有的分支,都完成了回滾,TC 將此全局事務所有的 lockKey 刪除

問題分析

AT 模式的一個突出問題是 rollback 中 2.3 的髒回滾難以避免。以下步驟能夠觸發該髒回滾:1. 全局事務 g1 對數據行 A1 進行修改 v1 -> v2 2. 另一個服務將對數據行 A1 進行修改 v2 -> v3 3. 全局事務 g1 回滾,發現數據行 A1 的當前數據爲 v3,不等於 AfterImage 中的 v2,回滾失敗

這個髒回滾一旦發生,那麼分佈式事務框架沒有辦法保證數據的一致性了,必須要人工介入處理。想要避免髒回滾,需要把所有對這個表的寫訪問,都加上特殊處理(在 Seata 的 Java 客戶端中,需要加上 GlobalLock 註解)。這種約束對於一個上了一定規模的複雜系統,是非常難以保證的。

AT vs XA

上述髒回滾問題,在 XA 事務中不會出現,因爲 XA 事務是在數據庫層面實現的,當另一個服務對爲數據行 A1 進行修改時,會因爲行鎖被阻塞,與普通事務的表現完全一樣,不會產生問題。

另外 XA 不會發生髒讀,而 AT 會發生髒讀,考慮 AT 下的如下執行步驟:1. 全局事務 g1 對數據行 A1 進行修改 v1 -> v2 2. 另一個服務將讀取數據行 A1,獲得數據 v2 3. 全局事務 g1 回滾,將數據行 A1 改回 v2 -> v1

這裏面步驟 2 讀取的數據是 v2,是一箇中間態數據。在 Seata 的手冊中,雖然也有一些方法能夠避免 AT 模式下,但是涉及到註解和 sql 改寫,並不優雅。而在 XA 模式下,由於還沒有進行 xa commit,那麼步驟 2 根據MVCC讀取到的數據依然是 v1,沒有 AT 模式中的髒讀的困擾。

性能分析

從原理的詳細步驟看,XA 事務的性能高於 AT,分析如下:

AT 模式下,RM 側,上述原理過程中,執行的 SQL 如下:1. 開啓事務 2. 查詢 BeforeImage 數據 3. 執行 update 4. 查詢 AfterImage 數據 5. 將 BeforeImage,AfterImage 插入到 undolog 中 6. 提交事務 7. 事務完成後,刪除 BeforeImage 和 AfterImage

而 XA 模式下,RM 側,執行的 SQL 如下:1. xa begin 2. 執行 update 3. xa end 4. xa prepare 5. xa commit

兩者對比,相關的開啓 / 提交事務是兩個模式都需要的,性能差異不大。但是從執行的 DML 操作來看,AT 下的 SQL 數量爲:3 writes,2 read,比 XA 下僅一個 update 多出許多,因此在性能上會有較大的差距

從上述理論分析,XA 事務性能會大幅高於 AT,應當可以在 postgres 數據庫上驗證出來;而 mysql 數據庫,在當前的 5.8 版本上,由於 xa prepare 後,需要將當前連接斷開才能夠在其他連接上 xa commit,所以會有一個重新創建連接的開銷,最終性能對比參考下一節。

性能實測

上述進行了理論上的性能分析,我同時也做了性能實測,詳細的測試過程和結果數據,參考 xa-at bench

dtm 實現的 XA 事務,爲了在極端情況下,也能保證 XA 事務能夠正確的被清理,會在業務事務中對子事務屏障表進行插入,因此會比上述理論分析中,多一個 sql 寫入。

我們可以看到,最終的結果 XA 性能優於 AT。如果未來 Mysql 完善了 XA 的實現,可以不用關閉當前連接也能夠允許其他連接提交 xa 事務,那麼 XA 的性能還能夠提升一大截。

AT 的意義

mysql 在版本 5.6 中,xa 相關 API 存在 bug。如果當前連接在 xa prepare 之後,連接斷開,那麼這個連接未完成的事務會被自動回滾。這樣的 bug 導致 mysql 的 XA 模式是無法保證正確性的,在各種應用 crash 中,可能導致數據不一致。因此 AT 在 mysql 的 5.6 版本及更低版本使用中,是具有很高應用價值的。

另外部分大廠的數據庫是禁止使用 XA 事務的,這種特定場景下,選型 AT 模式,也是合理的。

對於其他場景,建議優先考慮 XA 事務。

小結

作者對 AT 模式的完整實現源碼,並未完整閱讀。上述的相關原理是根據自己閱讀相關資料,並參考了 seata-golang 的源代碼而寫。文中如果不準確之處,希望各位讀者幫忙指正

歡迎訪問 https://github.com/dtm-labs/dtm 並 star 支持我們

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/E1UfP_pbTaCrGD6xDD7Umw