上一篇內容《從2PC和容錯共識演算法討論zookeeper中的Create請求》介紹了保證分散式事務提交的兩階段提交協議,而XA是針對兩階段提交提出的介面實現標準,本文則對XA進行介紹 ...
上一篇內容《從2PC和容錯共識演算法討論zookeeper中的Create請求》介紹了保證分散式事務提交的兩階段提交協議,而XA是針對兩階段提交提出的介面實現標準,本文則對XA進行介紹。
1. XA
XA (eXtended Architecture 擴展架構)是X/Open組織提出的跨異構技術實現兩階段提交的介面標準。
分散式事務包含兩種類型:資料庫內部的分散式事務,在這種情況下,所有參與事務的節點都運行相同的資料庫軟體;異構分散式事務,參與者由兩種或兩種以上的不同資料庫軟體組成。
它於 1991 年推出並得到了廣泛的實現:許多傳統關係資料庫包括 PostgreSQL、MySQL、DB2、SQL Server 和 Oracle;消息代理包括 ActiveMQ、HornetQ、MSMQ 和 IBM MQ都支持 XA。它不是一個網路協議而是定義了事務管理器(Transaction Manager)、應用程式(Application Program)和資源管理器(Resource Manager)之間交互的CAPI(Common Application Programming Interface)介面標準,如下圖所示,但是標準中並沒有指明該如何實現,例如在Java EE中,XA使用的是Java事務API(JTA, Java Transaction API)實現的。
CAPI: 國際標準的通用應用交互介面。
其中三個組件的職責如下:
應用程式(Application Program):負責定義事務的開啟、提交或中止,並能夠訪問事務內的資源(資料庫等)
資源管理器(Resource Manager):負責對資源進行管理,相當於兩階段提交中的參與者,能夠與事務管理器通過介面交互來傳遞必要的事務信息
事務管理器(Transaction Manager):負責管理全局事務,分配事務ID,監控事務的執行進度,並負責事務的開啟、提交和回滾等,相當於兩階段提交中的協調者
XA同樣也分為準備階段和提交階段,它對分散式事務管理的流程如下
-
準備階段:AP與TM交互,開啟一個全局分散式事務,併發送請求到每個RM,執行數據變更邏輯,此時每個RM會向TM發送請求註冊分支事務,在執行完業務邏輯後報告準備提交的狀態(事務執行完未提交),之後AP會根據RM的響應在提交階段做出反饋
-
提交階段:如果所有的RM都回覆“是”,表示它們已經準備好提交,那麼AP會在該階段向TM發出提交請求,分散式事務提交;否則,AP會向TM發出中止請求,分散式事務回滾
2. Seata的XA的模式
Seata中有三個角色事務管理器(Transaction Manager)、資源管理器(Resource Manager)和事務協調者(Transaction Coordinator)。在XA模式下,利用事務資源(資料庫、消息服務等)對XA協議的支持,來對分散式事務進行管理。
但是,在Seata中三個角色的定義與XA協議標準中角色的定義有所區別:事務管理器(Transaction Manager)應該對應XA協議中的應用程式(Application Program);事務協調者(Transaction Coordinator)對應XA協議中的事務管理器(Transaction Manager)。我認為它們只是在命名上的區別,為了上下文各名詞的統一,避免發生因名詞不一致而理解混淆的問題,決定以XA標準協議中的定義進行講解,特此強調。
下圖是Seata管理分散式事務的流程圖,與第一小節中所述事務流程相同,不再贅述。
2.1 XA模式實戰分析
有了理論基礎,我們以商城系統為例進行簡單地演示:訂單、商品和購物車分別為三個微服務系統,在執行下單流程時會修改商品庫存,生成訂單和刪除購物車中的商品,業務流程代碼如下,註意其中包含事務異常回滾的代碼
@GlobalTransactional
@Transactional(rollbackFor = Exception.class)
public void saveOrder(OrderSaveParam orderSaveParam) {
// 參數校驗等必要操作
// ...
// 校驗商品庫存和上架狀態
checkGoodsStatusAndStock(goodsList, goodsCountMap);
// 修改庫存
reduceGoodsCount(goodsCountMap);
// 生成訂單
saveOrder(goodsList, goodsCountMap, orderSaveParam.getAddressId());
// 刪除購物車中商品
shoppingCartService.deleteShoppingCartItem(orderSaveParam.getCartItemIds(), SecurityConstants.INNER);
// 異常回滾事務
int i = 1 / 0;
}
分散式事務執行的準備階段,流程圖如下
-
Order Server 在創建訂單之前,會向 Seata Server(TM) 註冊全局事務,並分配事務ID,對應的控制台日誌如下
2023-06-17 22:07:06.479 INFO 74703 --- [io-29009-exec-2] i.seata.tm.api.DefaultGlobalTransaction : Begin new global transaction [127.0.0.1:8091:36427221250506976]
-
Order Server REST調用 Goods Server 扣減商品數量,Goods Server 在執行數據修改邏輯前會向 Seata Server 註冊分支事務,執行完業務邏輯後,並不執行事務提交
-
Order Server REST調用 ShoppingCart Server 刪除購物車中的商品,ShoppingCart Server 在執行數據修改邏輯前會向 Seata Server 註冊分支事務,執行完業務邏輯後,同樣不執行事務提交
-
Order Server 本地執行生成訂單和其他邏輯
接下來是分散式事務執行的提交階段,因生成訂單中代碼邏輯拋出異常,所以該分散式事務會回滾,OrderServer中對應日誌如下
2023-06-17 22:07:07.029 INFO 74703 --- [io-29009-exec-2] i.s.rm.datasource.xa.ConnectionProxyXA : 127.0.0.1:8091:36427221250506976-36427221250506978 was rollbacked
2023-06-17 22:07:07.220 INFO 74703 --- [io-29009-exec-2] i.seata.tm.api.DefaultGlobalTransaction : Suspending current transaction, xid = 127.0.0.1:8091:36427221250506976
2023-06-17 22:07:07.220 INFO 74703 --- [io-29009-exec-2] i.seata.tm.api.DefaultGlobalTransaction : [127.0.0.1:8091:36427221250506976] rollback status: Rollbacked
-
Order Server 向 Seata Server 發送中止請求,隨後 Seata Server 向 Goods Server 和 ShoppingCart Server 發送事務回滾請求
-
Goods Server 和 ShoppingCart Server 收到事務回滾請求後,將各自註冊的分支事務回滾,最終全局分散式事務回滾,以保證數據的一致性。Goods Server 分支事務回滾對應的日誌如下,可以發現分支事務的ID為全局事務ID-分支ID,並顯示PhaseTwo_Rollbacked 在階段二回滾
2023-06-17 22:07:07.081 INFO 74680 --- [h_RMROLE_1_4_24] i.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:xid=127.0.0.1:8091:36427221250506976,branchId=36427221250506986,branchType=XA,resourceId=jdbc:mysql://127.0.0.1:3306/fy_mall_goods,applicationData=null
2023-06-17 22:07:07.081 INFO 74680 --- [h_RMROLE_1_4_24] io.seata.rm.AbstractRMHandler : Branch Rollbacking: 127.0.0.1:8091:36427221250506976 36427221250506986 jdbc:mysql://127.0.0.1:3306/fy_mall_goods
2023-06-17 22:07:07.096 INFO 74680 --- [h_RMROLE_1_4_24] i.s.rm.datasource.xa.ResourceManagerXA : 127.0.0.1:8091:36427221250506976-36427221250506986 was rollbacked
2023-06-17 22:07:07.096 INFO 74680 --- [h_RMROLE_1_4_24] io.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
註:如果有朋友想試試Seata的XA模式,可以參考示例代碼倉庫FangYuan33/book-spring-cloud,對應的方法入口為
/saveOrder
3. 對XA的思考
XA能夠保持多個參與者數據相互一致,但是同時也引入了比較嚴重的運維問題。
因為如果協調者宕機,那麼其中已經準備但未提交事務的所有參與者都會被阻塞。被阻塞的根本是鎖,例如在讀已提交隔離級別上,資料庫事務通常會獲取到待修改行數據的行級排他鎖來防止臟寫。在分散式事務提交或中止前,參與者資料庫不能釋放這些鎖,因此協調者宕機多久,這些鎖就要持有多久(在沒有認為干預的情況下)。這些鎖被持有的期間,導致其他事務不能修改這些數據(根據資料庫的不同,讀取操作也可能被阻塞),所以這些數據相關的業務都會被阻塞,導致應用大面積的不可用,直至存疑事務被解決(提交/中止)。
理論上,如果協調者崩潰並重新啟動,它應該從日誌中恢復事務的狀態,並解決現存的疑慮事務,但是在實際生產中,仍然會有疑慮事務的出現(可能是事務日誌被破壞)。也許你可能會考慮將相關應用的資料庫伺服器重啟,但是在2PC正確的實現中,為了原子性的保證,重啟後也必須持有存疑事務的鎖。那麼這樣唯一的解決方案是讓管理員手動提交還是回滾事務,這是引入運維問題的所在。不過,許多XA事務的實現都有一個叫做啟髮式決策的逃生出口,允許參與者單方面決定提交或放棄一個存疑事務,而無需等待協調者的決定,但是這也正是避免災難性情況的手段,而不是為了日常的使用,因為這種方式有可能會破壞事務的原子性。
所以,協調者的高可用是需要我們考慮的問題,它本身也是一種資料庫(保存了事務的結果),需要像其他應用資料庫服務一樣被認真的對待。
巨人的肩膀
-
《數據密集型應用系統設計》:第九章 一致性與共識
-
原文收錄:GitHub-Enthusiasm
作者:京東物流 王奕龍
來源:京東雲開發者社區 自猿其說Tech 轉載請註明來源