title: date: 2025-12-25 12:36:28 categories:
- 事務
- 分佈式 tags:
- 事務
- 分佈式
Seata是一款開源的分佈式事務解決方案,支持AT、TCC、SAGA和XA事務模式。本文介紹了Seata的三種安裝部署方式:二進制安裝、Docker安裝和Kubernetes部署。同時也介紹了Seata説支持的四種事務模式:AT模式、TCC模式、XA模式和Saga模式
Seata官方文檔
Seata Github
Seata 是一款開源的分佈式事務解決方案,致力於提供高性能和簡單易用的分佈式事務服務。Seata 將為用户提供了 AT、TCC、SAGA 和 XA 事務模式,為用户打造一站式的分佈式解決方案。
1、Seata的安裝部署
1.1、二進制安裝
Github地址
安裝手冊
從發佈版本中下載目標版本的二進制安裝包,2.0.0版本之後只提供源碼,需要自己編譯。為了省事這裏下載的是2.0.0版本
1.1.1、目錄結構
下載之後解壓的目錄結構如下:
- bin:啓動腳本,提供了windows和linux的啓動腳本
- conf:配置文件目錄
- target:seata-server.jar存放目錄
- logs:日誌目錄
1.1.2、啓動
執行下面命令啓動:
./bin/seata-server.sh
看到下面提示表示服務啓動成功
可以啊看到,這裏提示服務啓動成功,日誌目錄在對應標註的位置
查看日誌:
1.1.3、UI控制枱
從上面的啓動日誌可以看到這裏提示還有一個控制枱,必須得看一眼,於是輸入真實的IP和端口號就可以看到
用默認的賬號和密碼進去:賬號(seata),密碼(seata)
這個控制枱主要就是提供了可視化的事務管理功能,包括事務信息、全局鎖信息和saga狀態機的設計器
1.2、docker安裝
1.2.1、鏡像構建
從下載的解壓包可以看到一個Dockerfile文件,通過這個文件就可以構建自己的seata-server的鏡像
Dockerfile內容如下:
FROM openjdk:8u342
# set label
LABEL maintainer="Seata <seata.io>"
WORKDIR /$BASE_DIR
# ADD FORM distribution
ADD bin/ /seata-server/bin
ADD ext/ /seata-server/ext
ADD target/ /seata-server/target
ADD lib/ /seata-server/lib
ADD conf/ /seata-server/conf
ADD LICENSE /seata-server/LICENSE
# set extra environment
ENV LOADER_PATH="/seata-server/lib"
ENV TZ="Asia/Shanghai"
CMD ["bash","-c","/seata-server/bin/seata-server.sh && tail -f /dev/null"]
通過docker build . -t xxx就可以構建自己的鏡像了,當然,也可以用別人已經構建好的鏡像,直接拉取下來用就行
1.2.2、docker-compose啓動
在解壓目錄下的script/server/docker-compose目錄下有一個docker-compose.yaml文件:
version: "3"
services:
seata-server:
image: seataio/seata-server
hostname: seata-server
ports:
- "8091:8091"
environment:
- SEATA_PORT=8091
- STORE_MODE=file
找到這個文件,直接運行docker-compose up -d就可以啓動seata-server了,這裏會自動拉取seataio/seata-server:latest最新鏡像,如果要用自己構建的鏡像,就需要替換這個鏡像名稱
1.3、k8s安裝
下面腳本是部署在middleware命名空間下的,如果命名空間不存在,要麼創建一個,要麼改成自己k8s集羣已經存在的命名空間
1.3.1、配置文件cm
application-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: seata-server-config
namespace: middleware
data:
application.yml: |
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: 'Seata!@#'
seata:
config:
type: nacos
nacos:
server-addr: 192.168.0.101:8848
#namespace: prod
group: SEATA_GROUP
username: nacos
password: nacos
data-id: seataServer.properties
registry:
type: nacos
nacos:
application: seata-server
server-addr: 192.168.0.101:8848
group: SEATA_GROUP
#namespace: prod
cluster: default
username: nacos
password: nacos
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850aaaa
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
這裏指定了console.user.username、console-user.password,同時配置了註冊中心和配置中心為nacos,具體配置説明可以參考官方文檔
1.3.2、deploy
deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: seata-server
namespace: middleware
labels:
k8s-app: seata-server
spec:
replicas: 3
selector:
matchLabels:
k8s-app: seata-server
template:
metadata:
labels:
k8s-app: seata-server
spec:
containers:
- name: seata-server
image: registry.cn-hangzhou.aliyuncs.com/xhsx/seata-server:latest
imagePullPolicy: IfNotPresent
ports:
- name: http-7091
containerPort: 7091
protocol: TCP
- name: http-8091
containerPort: 8091
protocol: TCP
volumeMounts:
- name: seata-config
mountPath: /seata-server/resources/application.yml
subPath: application.yml
volumes:
- name: seata-config
configMap:
name: seata-server-config
我這裏用的鏡像是從官方拉取然後保存在阿里雲鏡像倉庫裏的,需要根據具體情況替換
1.3.3、運行
有了配置文件application-cm.yaml和deploy.yaml之後就可以執行k8s命令啓動了
kubectl -f application-cm.yaml
kubectl -f deploy.yaml
查看容器是否正常啓動
ubuntu@ubuntu-server-01:~/k8s/seata$ sudo kubectl -n middleware get po | grep seat
seata-server-65649cbfd-5b5fd 1/1 Running 28 (44d ago) 252d
seata-server-65649cbfd-dzxc8 1/1 Running 28 (44d ago) 252d
seata-server-65649cbfd-l9676 1/1 Running 28 (44d ago) 252d
1.3.4、service
我們都知道,k8s集羣部署的容器是無法直接在外部使用的,要麼將要訪問seata-server的程序也部署到k8s裏邊,要麼就要通過service將seata-server服務暴露出來,這裏可以用到nodeport類型的service
apiVersion: v1
kind: Service
metadata:
name: seata-server
namespace: middleware
labels:
k8s-app: seata-server
spec:
type: NodePort
ports:
- port: 8091
nodePort: 30893
protocol: TCP
name: seata-8091
- port: 7091
nodePort: 30793
protocol: TCP
name: seata-7091
selector:
k8s-app: seata-server
通過k8s集羣的節點IP+30793訪問UI控制枱可以看到也能正常訪問,賬號和密碼是我們上面application-cm.yaml配置的seata和Seata!@#
2、Seata提供的四種事務模式
2.1、AT模式
2.1.1、AT模式事務執行流程
Seata的AT模式是基於數據庫本身的ACID基礎上實現的,也是默認的模式,AT 模式是 Seata 創新的一種非侵入式的分佈式事務解決方案,Seata 在內部做了對數據庫操作的代理層,它對我們的數據源進行了進一步包裝,提供了一個DatasourceProxy數據源代理
AT模式的兩階段提交:
- 一階段:業務數據和回滾日誌記錄在同一個本地事務中提交,釋放本地鎖和連接資源(需要在本地數據庫中創建一張表,
undo_log表,2.x版本的sql腳本地址:sql)。
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
undo_log的內容大概如下,記錄了修改前和修改後的完整內容,如果事務要進行回滾,則會根據sqlType通過sql解析器生成一個逆向sql去回滾
{
"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"
}
- 二階段:
- 提交事務,異步化,非常快速地完成。
- 事務回滾,通過一階段的回滾日誌進行反向補償。
2.1.2、寫隔離
Seata的AT模式寫隔離是基於全局鎖來實現的,以官網的一個示例來説明:
兩個全局事務 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.1.2、讀隔離
在數據庫本地事務隔離級別 讀已提交(Read Committed) 或以上的基礎上,Seata(AT 模式)的默認全局隔離級別是 讀未提交(Read Uncommitted) 。
如果應用在特定場景下,必需要求全局的 讀已提交 ,目前 Seata 的方式是通過 SELECT FOR UPDATE 語句的代理。
SELECT FOR UPDATE 語句的執行會申請 全局鎖 ,如果 全局鎖 被其他事務持有,則釋放本地鎖(回滾 SELECT FOR UPDATE 語句的本地執行)並重試。這個過程中,查詢是被 block 住的,直到 全局鎖 拿到,即讀取的相關數據是 已提交 的,才返回。
出於總體性能上的考慮,Seata 目前的方案並沒有對所有 SELECT 語句都進行代理,僅針對 FOR UPDATE 的 SELECT 語句。
2.2、TCC模式
TCC模式是一種代碼侵入的分佈式事務解決方案,它不依賴底層數據庫,完全由程序員自己通過代碼控制,我們需要給分佈式事務方法除了提供具體的業務方法之外,還需要手動去實現業務邏輯正常執行的時候的事務提交的方法以及業務邏輯執行失敗的時候的事務回滾方法,也就是Try、Confirm、Cancel這三個方法都需要我們去實現
public interface TccActionOne {
@TwoPhaseBusinessAction(name = "DubboTccActionOne", commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") String a);
public boolean commit(BusinessActionContext actionContext);
public boolean rollback(BusinessActionContext actionContext);
}
2.2.1、TCC模式事務執行流程
上圖TM為事務管理器,簡單理解就是我們開啓全局事務的地方,RM是資源管理器,比如數據庫,TC為事務協調器,也就是seata-server。上述流程概括就是:
- TM開啓全局事務,向TC註冊一個全局事務,並會得到一個全局事務ID
- 全局事務裏邊包含兩個子業務,這兩個子業務分別會向TC也就是
seata-server註冊一個分支事務 - 兩個子業務執行結束預提交,並且向
seata-server主動上報prepare階段的分支事務狀態 - 如果所有的分支事務都執行成功,TM會通知TC提交全局事務,TC會通知各分支事務進行提交。反之則會通知進行回滾
2.2.2、TCC模式下的空回滾問題
由於TCC是基於補償的機制,件一個事務拆分成了三個環節。假如現在因為網絡問題,Try階段因為超時導致了事務執行失敗,按照邏輯全局事務就會執行Cancel邏輯進行回滾。
當時由於我們的try都沒有執行,比如是A往B轉100塊錢,那麼回滾的話就會B往A轉100塊錢。當時現在前一個邏輯沒有執行,這樣一來B不就虧大發了嗎?
則就是TCC的空回滾問題,怎麼解決?
方案:本地事務控制表
設計一張表,記錄下本次事務相關的信息,與try階段的本地事務一起寫入,在cancel階段 去這張事務控制表去查,如果有對應的記錄,則説明try階段執行成功了,反之則説明try階段沒有執行
2.2.3、TCC模式下的冪等性問題
在TC通知RM提交分支事務的時候,如果因為網絡超時沒有收到對方的響應,它會重複發送提交或者回滾的通知。如何這個時候不考慮消息的冪等性,則會出現數據的不一致問題
Confirm和Cancel則兩個環節的冪等性如何保障呢?
方案:狀態機
設計一個狀態機,方法的執行與否會根據事務的狀態去判斷,並且狀態是不可逆的,這樣當指定狀態已經發出一次修改之後,就無法再通過該狀態修改同一條數據了
2.2.4、TCC模式下的空懸掛問題
我們知道分佈式場景下網絡是不可靠的,假如我們前面已經開啓了允許空回滾問題,這個時候如果是因為網絡延遲,try執行超時導致了回滾,這個時候cancel先執行了,當時實際上try階段的請求已經發送出去了,這個時候try會在cancel之後執行,這種情況同樣會出現數據一致性問題,這就是空懸掛問題。
怎麼解決呢?
方案:本地事務控制表
方案還是通過一張本地事務控制表來控制。前面可以通過這張表來判斷cancel是否需要執行,那麼反過來同樣也可以通過這張表來控制try是否能夠執行。也就是説在cancel階段寫入這張表,然後在try方法執行的時候去這張表裏去查,如果對應事務的cancel操作執行了,則不允許在執行try操作
2.3、XA模式
XA 模式是從 1.2 版本支持的強一致性事務模式。XA 規範 是 X/Open 組織定義的分佈式事務處理(DTP,Distributed Transaction Processing)標準。Seata XA 模式是利用事務資源(數據庫、消息服務等)對 XA 協議的支持,以 XA 協議的機制來管理分支事務的一種事務模式。
缺點:
XA prepare 後,分支事務進入阻塞階段,收到 XA commit 或 XA rollback 前必須阻塞等待。事務資源長時間得不到釋放,鎖定週期長,而且在應用層上面無法干預,性能差
2.4、saga模式
Saga 模式是 SEATA 提供的長事務解決方案,在 Saga 模式中,業務流程中每個參與者都提交本地事務,當出現某一個參與者失敗則補償前面已經成功的參與者,一階段正向服務和二階段補償服務都由業務開發實現。
前面看UI控制枱的時候可以看到一個Saga狀態機設計器菜單,實際上seata的saga模式是基於狀態機引擎來實現的:
- 1、通過狀態圖來定義服務調用的流程並生成 json 狀態語言定義文件
- 2、狀態圖中一個節點可以是調用一個服務,節點可以配置它的補償節點
- 3、狀態圖 json 由狀態機引擎驅動執行,當出現異常時狀態引擎反向執行已成功節點對應的補償節點將事務回滾
注意: 異常發生時是否進行補償也可由用户自定義決定
- 4、可以實現服務編排需求,支持單項選擇、併發、子流程、參數轉換、參數映射、服務執行狀態判斷、異常捕獲等功能
3、AT模式示例
https://github.com/apache/incubator-seata-samples/tree/master/at-sample
以SpringBoot+Dubbo+Seata為例
3.1、引入依賴
<dependency>
<groupId>org.apache.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
其它集成所需要的依賴這裏沒有列出
3.2、配置文件
seata.tx-service-group=my_test_tx_group
seata.enabled=true
seata.service.vgroup-mapping.my_test_tx_group=default
seata.service.grouplist.default=${seata.address:127.0.0.1}:8091
seata.registry.type=file
seata.config.type=file
這裏會根據seata.tx-service-group對應的值去找seata.service.vgroup-mapping為前綴的配置項的值default,這個default就是seata-server的集羣分組,程序會再去找seata.service.grouplist+default對應的seata-server的地址。
註冊中心和配置中心的配置方式參考官方文檔。
3.3、開啓事務
在需要開啓全局事務的方法上打上註解
@GlobalTransactional(timeoutMills = 300000, name = "spring-dubbo-tx")
AT模式在使用起來應該是非常簡單的了,為了我們的應用數據的安全穩定,不妨把seata用起來吧