# :-: 阿里分布式事務框架Seata原理解析
Seata框架是一個業務層的XA(兩階段提交)解決方案。在理解Seata分布式事務機制前,我們先回顧一下數據庫層面的XA方案。
# 1\. MySQL XA方案
MySQL從5.7開始加入了分布式事務的支持。MySQL XA中擁有兩種角色:
* **RM(Resource Manager):用于直接執行本地事務的提交和回滾。在分布式集群中,一臺MySQL服務器就是一個RM。**
* **TM(Transaction Manager):TM是分布式事務的核心管理者。事務管理器與每個RM進行通信,協調并完成分布式事務的處理。發起一個分布式事務的MySQL客戶端就是一個TM。**
XA的兩階段提交分為Prepare階段和Commit階段,過程如下:
1. 階段一為準備(prepare)階段。即所有的RM鎖住需要的資源,在本地執行這個事務(執行sql,寫redo/undo log等),但不提交,然后向Transaction Manager報告已準備就緒。
2. 階段二為提交階段(commit)。當Transaction Manager確認所有參與者都ready后,向所有參與者發送commit命令。

:-:
MySQL XA擁有嚴重的性能問題。一個數據庫的事務和多個數據庫間的XA事務性能對比可發現,性能差10倍左右。另外,XA過程中會長時間的占用資源(加鎖)直到兩階段提交完成才釋放資源。2. Seata
Seata的分布式事務解決方案是業務層面的解決方案,只依賴于單臺數據庫的事務能力。Seata框架中一個分布式事務包含3中角色:
* **Transaction Coordinator (TC): 事務協調器,維護全局事務的運行狀態,負責協調并驅動全局事務的提交或回滾。**
* **Transaction Manager (TM): 控制全局事務的邊界,負責開啟一個全局事務,并最終發起全局提交或全局回滾的決議。**
* **Resource Manager (RM): 控制分支事務,負責分支注冊、狀態匯報,并接收事務協調器的指令,驅動分支(本地)事務的提交和回滾。**
其中,TM是一個分布式事務的發起者和終結者,TC負責維護分布式事務的運行狀態,而RM則負責本地事務的運行。如下圖所示:
下面是一個分布式事務在Seata中的執行流程:
1. **TM 向 TC 申請開啟一個全局事務,全局事務創建成功并生成一個全局唯一的 XID。**
2. **XID 在微服務調用鏈路的上下文中傳播。**
3. **RM 向 TC 注冊分支事務,接著執行這個分支事務并提交(重點:RM在第一階段就已經執行了本地事務的提交/回滾),最后將執行結果匯報給TC。**
4. **TM 根據 TC 中所有的分支事務的執行情況,發起全局提交或回滾決議。**
5. **TC 調度 XID 下管轄的全部分支事務完成提交或回滾請求。**
## 2.1 為什么Seata在第一階段就直接提交了分支事務?
Seata能夠在第一階段直接提交事務,是因為Seata框架為每一個RM維護了一張UNDO\_LOG表(這張表需要客戶端自行創建),其中保存了每一次本地事務的回滾數據。因此,二階段的回滾并不依賴于本地數據庫事務的回滾,而是RM直接讀取這張UNDO\_LOG表,并將數據庫中的數據更新為UNDO\_LOG中存儲的歷史數據。
如果第二階段是提交命令,那么RM事實上并不會對數據進行提交(因為一階段已經提交了),而實發起一個異步請求刪除UNDO\_LOG中關于本事務的記錄。
> 由于Seata一階段直接提交了本地事務,因此會造成隔離性問題,因此Seata的默認隔離級別為Read Uncommitted。然而Seata也支持Read Committed的隔離級別,我們會在下文中介紹如何實現。
## 2.2 Seata執行流程
下面是一個Seata中一個分布式事務執行的詳細過程:
1. 首先TM 向 TC 申請開啟一個全局事務,全局事務創建成功并生成一個全局唯一的 XID。
2. XID 在微服務調用鏈路的上下文中傳播。
3. RM 開始執行這個分支事務,RM首先解析這條SQL語句,生成對應的UNDO\_LOG記錄。下面是一條UNDO\_LOG中的記錄:
~~~json
{
"branchId": 641789253,
"undoItems": [{
"afterImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "GTS"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"beforeImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "TXC"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"sqlType": "UPDATE"
}],
"xid": "xid:xxx"
}
~~~
可以看到,UNDO\_LOG表中記錄了分支ID,全局事務ID,以及事務執行的redo和undo數據以供二階段恢復。
1. RM在同一個本地事務中執行業務SQL和UNDO\_LOG數據的插入。在提交這個本地事務前,RM會向TC申請關于這條記錄的全局鎖。如果申請不到,則說明有其他事務也在對這條記錄進行操作,因此它會在一段時間內重試,重試失敗則回滾本地事務,并向TC匯報本地事務執行失敗。如下圖所示:

* RM在事務提交前,申請到了相關記錄的全局鎖,因此直接提交本地事務,并向TC匯報本地事務執行成功。此時全局鎖并沒有釋放,全局鎖的釋放取決于二階段是提交命令還是回滾命令。
* TC根據所有的分支事務執行結果,向RM下發提交或回滾命令。
* RM如果收到TC的提交命令,首先立即釋放相關記錄的全局鎖,然后把提交請求放入一個異步任務的隊列中,馬上返回提交成功的結果給 TC。異步隊列中的提交請求真正執行時,只是刪除相應 UNDO LOG 記錄而已。
## 2.3 Seata隔離級別
Seata由于一階段RM自動提交本地事務的原因,默認隔離級別為Read Uncommitted。如果希望隔離級別為Read Committed,那么可以使用`SELECT...FOR UPDATE`語句。Seata引擎重寫了`SELECT...FOR UPDATE`語句執行邏輯,`SELECT...FOR UPDATE` 語句的執行會申請 全局鎖 ,如果 全局鎖 被其他事務持有,則釋放本地鎖(回滾 `SELECT...FOR UPDATE` 語句的本地執行)并重試。這個過程中,查詢是被 block 住的,直到 全局鎖 拿到,即讀取的相關數據是已提交的才返回。
出于總體性能上的考慮,Seata 目前的方案并沒有對所有 SELECT 語句都進行代理,僅針對 FOR UPDATE 的 SELECT 語句。
# 3\. Seata支持的模式
上文中我們提到的Seata流程只是Seata支持的一種分布式事務模式,稱為AT模式。它依賴于RM擁有本地數據庫事務的能力,對于客戶業務無侵入性。如圖所示:
AT模式中業務邏輯不需要關注事務機制,分支與全局事務的交互過程自動進行。
另外,Seata還支持MT模式。MT模式本質上是一種TCC方案,業務邏輯需要被拆分為 Prepare/Commit/Rollback 3 部分,形成一個 MT 分支,加入全局事務。如圖所示:
# 4\. XA和Seata AT的對比
如圖所示,**XA 方案的 RM 實際上是在數據庫層,RM 本質上就是數據庫自身(通過提供支持 XA 的驅動程序來供應用使用)。而 Seata 的 RM 是以二方包的形式作為中間件層部署在應用程序這一側的,不依賴與數據庫本身對協議的支持,當然也不需要數據庫支持 XA 協議**。這點對于微服務化的架構來說是非常重要的:應用層不需要為本地事務和分布式事務兩類不同場景來適配兩套不同的數據庫驅動。
另外,\*\*XA方案無論 Phase2 的決議是 commit 還是 rollback,事務性資源的鎖都要保持到 Phase2 完成才釋放。而對于Seata,將鎖分為了本地鎖和全局鎖,本地鎖由本地事務管理,在分支事務Phase1結束時就直接釋放。而全局鎖由TC管理,在決議 Phase2 全局提交時,全局鎖馬上可以釋放。\*\*只有在決議全局回滾的情況下,全局鎖 才被持有至分支的 Phase2 結束。因此,Seata對于資源的占用時間要少的多。對比如下圖所示
- 項目介紹
- 項目聲明
- 項目簡介
- 架構設計
- 項目亮點功能介紹
- 技術棧介紹
- 核心功能
- 運行環境
- 項目更新日志
- 文檔更新日志
- F&Q
- 部署教程
- 環境準備
- JDK安裝
- JDK1.8,17共存
- maven
- 分布式緩存Redis
- 單機版
- 集群
- 注冊&配置中心alibaba/nacos
- 介紹
- Nacos安裝
- Nacos配置中心
- Nacos注冊發現
- Nacos生產部署方案
- 服務監控-BootAdmin
- 基本介紹
- 如何使用
- 整合Admin-Ui
- 客戶端配置
- 鏈路追蹤
- 基本介紹
- SkyWalking-1
- Skywalking-1
- 消息隊列
- Kafka
- docker安裝kafka
- Linux集群
- Maven私服
- nexus安裝部署
- nexus使用介紹
- 全文搜索elasticsearch
- windows集群搭建
- docker安裝es
- ElasticHD
- linux集群部署
- 統一日志解決方案
- 日志解決方案設計
- 介紹與相關資料
- ELK安裝部署
- elasticsearch 7.5
- logstash-7.5
- kibana-7.5
- filebeat
- 服務監控-Prometheus
- Prometheus安裝配置
- Prometheus介紹
- grafana
- 持續集成部署CICD
- 自動化部署Jenkins
- 安裝部署win
- 打包發布遠程執行
- 安裝部署linux
- jenkins+gitlab+docker容器化工程自動化部署
- Git
- CICD說明
- 阿里云效
- CentOS_MYSQL安裝
- docker
- 安裝
- Docker安裝Nginx
- Docker部署啟動springboot
- dockerCompose
- harbor
- Docker私有鏡像倉庫
- Portainer
- Docker遠程連接設置
- 打包工程
- 必要啟動模塊
- 核心模塊
- 登錄認證
- 緩存功能
- 日志模塊
- 分布式鎖
- 消息隊列
- 異常處理
- 系統接口
- 參數驗證
- es檢索
- 數據導出
- 系統設計
- 系統總體架構
- 擴展模塊(可選)
- 限流熔斷alibaba/sentinel
- 使用Sentinel實現gateway網關及服務接口限流
- Sentinel使用Nacos存儲規則及同步
- 服務調用Feign
- Feign基本介紹
- 如何使用
- 負載均衡
- 請求超時
- 請求攔截器
- 分布式任務調度
- XXL-JOB
- 分布式事務
- TX-LCN
- Seata
- Seata原理解析
- 數據庫分庫分表
- swagger文檔
- 分布式ID生成器解決方案
- 服務網關CloudGateway
- 基本介紹
- 使用網關
- 路由配置
- 全局過濾器
- 服務認證授權架構設計
- 認證服務流程
- 授權服務流程
- 系統冪等性設計與實踐
- 分布式日志鏈路跟蹤
- 實時搜索系統設計
- 應用性能
- 壓力測試工具
- Apache JMeter介紹和安裝
- ApacheJMeter使用
- JVM
- JVM性能調優
- 常見JVM內存錯誤及解決方案
- JVM 分析工具詳解
- Spring Cloud性能調優
- Linux運維
- Linux 常用命令
- Linux開啟端口