1.SeaTa簡介
SeaTa 是阿里開源的可供商用的分佈式事務框架 前身Fescar , java程序
1.1亮點
- 應用層基於SQL解析實現了自動補償,從而最大程度的降低業務侵入性;
- 將分佈式事務中TC(事務協調者)獨立部署,負責事務的註冊、回滾;
- 通過全局鎖實現了寫隔離與讀隔離
- 多種事務模式 : AT、TCC、SAGA 事務模式
1.2 SeaTa相關概念
- TC :事務協調器,維護全局事務的運行狀態,負責協調並驅動全局事務的提交或回滾。
- TM:控制全局事務的邊界,負責開啓一個全局事務,並最終發起全局提交或全局回滾的決議。
- RM:控制分支事務,負責分支註冊、狀態彙報,並接收事務協調器的指令,驅動分支(本地)事務的提交和回滾。
- TC(Server端)爲單獨服務端部署,TM和RM(Client端)由業務系統集成。
1.3執行流程
- TM 向 TC 申請開啓一個全局事務,TC 創建全局事務後返回全局唯一的 XID,XID 會在全局事務的上下文中傳播;
- RM 向 TC 註冊分支事務,該分支事務歸屬於擁有相同 XID 的全局事務;
- TM 向 TC 發起全局提交或回滾;
- TC 調度 XID 下的分支事務完成提交或者回滾
1.4 詳細參數
https://seata.io/zh-cn/docs/user/configurations.html
1.5性能損耗
- 一條Update的SQL,則需要全局事務xid獲取(與TC通訊)
- before image(解析SQL,查詢一次數據庫)
- after image(查詢一次數據庫)
- insert undo log(寫一次數據庫)
- before commit(與TC通訊,判斷鎖衝突)
這些操作都需要一次遠程通訊RPC,而且是同步的。另外undo log寫入時blob字段的插入性能也是不高的。每條寫SQL都會增加這麼多開銷,粗略估計會增加5倍響應時間(二階段雖然是異步的,但其實也會佔用系統資源,網絡、線程、數據庫)
2.準備工作
1.server端工作
1.TC (Seate-server ) 下載
- https://github.com/seata/seata/releases
- 官方釘釘羣(羣號:23171167,1羣5000人已滿,2羣),qq羣(羣號:254657148)羣文件共享下載
2.建表
- TC需要新建三張表
- db_store.sql
- 各個表對應功能
- 全局事務---global_table
- 分支事務---branch_table
- 全局鎖-----lock_table
3.修改配置文件
- seata/conf/file.conf server服務 的日誌記錄方式,數據庫連接信息
## transaction log store, only used in seata-server
store {
## store mode: file、db 事務日誌存儲模式
mode = "db"
# 服務端配置
service {
# 分組名稱 需要和client端一致 chuangqi-steata
vgroup_mapping.chuangqi-steata = "chuangqi-steata"
chuangqi-steata.grouplist = "127.0.0.1:8091"
# 降級開關 默認關閉
enableDegrade = false
disable = false
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
}
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "dbcp"
## mysql/oracle/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/seata"
user = "root"
password = "root"
minConn = 1
maxConn = 10
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
- seata/conf/registry.conf 不同註冊中心和配置中心 file 單機本地版
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "file"
nacos {
serverAddr = "localhost"
namespace = ""
cluster = "default"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = "0"
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "file"
nacos {
serverAddr = "localhost"
namespace = ""
group = "SEATA_GROUP"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
namespace = "application"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
4.啓動
seata/bin/seata-server.sh
nohup sh seata-server.sh -h xx.xx.xx.xx -p 8091 -m db -n 1 -e test &
-h: 註冊到註冊中心的ip
-p: Server rpc 監聽端口
-m: 全局事務會話信息存儲模式,file、db,優先讀取啓動參數
-n: Server node,多個Server時,需區分各自節點,用於生成不同區間的transactionId,以免衝突
-e: 多環境配置參考 http://seata.io/en-us/docs/ops/multi-configuration-isolation.html
2.client端工作
2.1項目添加依賴 (單選)
- 依賴seata-all 手動配置較多
- 依賴seata-spring-boot-starter,支持yml配置
- 依賴spring-cloud-alibaba-seata,內部集成了seata,並實現了xid傳遞
- client 版本與 server端版本一致
2.2項目新建undo_log表
2.3 增加配置文件 (1.1.0版本)
- yml文件
seata:
enabled: true
application-id: account-api # 項目標識
tx-service-group: chuangqi-steat # seata分組名稱
enable-auto-data-source-proxy: true # 開啓數據源自動代理
use-jdk-proxy: false # 使用的代理方式
client:
rm:
async-commit-buffer-limit: 1000
report-retry-count: 5
table-meta-check-enable: false
report-success-enable: false
lock:
retry-interval: 10
retry-times: 30
retry-policy-branch-rollback-on-conflict: true
tm:
commit-retry-count: 5
rollback-retry-count: 5
undo:
data-validation: true
log-serialization: jackson
log-table: undo_log
log:
exceptionRate: 100
service:
vgroup-mapping:
my_test_tx_group: default
grouplist:
default: 127.0.0.1:8091
#enable-degrade: false
#disable-global-transaction: false
transport:
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
server-executor-thread-prefix: NettyServerBizHandler
share-boss-worker: false
client-selector-thread-prefix: NettyClientSelector
client-selector-thread-size: 1
client-worker-thread-prefix: NettyClientWorkerThread
worker-thread-size: default
boss-thread-size: 1
type: TCP
server: NIO
heartbeat: true
serialization: seata
compressor: none
enable-client-batch-send-request: true
config:
type: file
consul:
server-addr: 127.0.0.1:8500
apollo:
apollo-meta: http://192.168.1.204:8801
app-id: seata-server
namespace: application
etcd3:
server-addr: http://localhost:2379
nacos:
namespace:
serverAddr: localhost
group: SEATA_GROUP
zk:
server-addr: 127.0.0.1:2181
session-timeout: 6000
connect-timeout: 2000
username: ""
password: ""
registry:
type: file
consul:
cluster: default
server-addr: 127.0.0.1:8500
etcd3:
cluster: default
serverAddr: http://localhost:2379
eureka:
application: default
weight: 1
service-url: http://localhost:8761/eureka
nacos:
cluster: default
server-addr: localhost
namespace:
redis:
server-addr: localhost:6379
db: 0
password:
cluster: default
timeout: 0
sofa:
server-addr: 127.0.0.1:9603
application: default
region: DEFAULT_ZONE
datacenter: DefaultDataCenter
cluster: default
group: SEATA_GROUP
addressWaitTime: 3000
zk:
cluster: default
server-addr: 127.0.0.1:2181
session-timeout: 6000
connect-timeout: 2000
username: ""
password: ""
2.4 開啓數據源代理
- 關閉spring自動代理
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
- 注入數據源
@Bean
@Autowired
public SqlSessionFactory sqlsessionfactory(HikariDataSource dataSource, Configuration configuration) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setPlugins(new Interceptor[]{new PageInterceptor()});
sqlSessionFactoryBean.setConfiguration(configuration);
return sqlSessionFactoryBean.getObject();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariDataSource dataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Primary
@Bean("dataSource")
public DataSourceProxy dataSource(DataSource druidDataSource){
return new DataSourceProxy(druidDataSource);
}
2.4 初始化GlobalTransactionScanner
- 手動
public GlobalTransactionScanner globalTransactionScanner() {
String applicationName = this.applicationContext.getEnvironment().getProperty("spring.application.name");
String txServiceGroup = this.seataProperties.getTxServiceGroup();
if (StringUtils.isEmpty(txServiceGroup)) {
txServiceGroup = applicationName + "-fescar-service-group";
this.seataProperties.setTxServiceGroup(txServiceGroup);
}
return new GlobalTransactionScanner(applicationName, txServiceGroup);
}
- 自動,引入seata-spring-boot-starter、spring-cloud-alibaba-seata等jar
2.5根據項目架構 配置 XID 傳遞
- 手動 參考源碼integration文件夾下的各種rpc實現 module
- 自動 springCloud用戶可以引入spring-cloud-alibaba-seata,內部已經實現xid傳遞
2.6 使用
- 1.@GlobalTransaction 全局事務註解 項目中要實現分佈式事務的接口加入
- 2.@GlobalLock 防止髒讀和髒寫,又不想納入全局事務管理時使用。(不需要rpc和xid傳遞等成本)
3.注意事項
1.server端
- file模式爲單機模式,全局事務會話信息內存中讀寫並持久化本地文件root.data,性能較高;
- db模式爲高可用模式,全局事務會話信息通過db共享,相應性能差些
- file模式可直接啓動 , 無需多餘配置
2.client端
- restTemplate RPC調用時 : SeataFilter ,SeataRestTemplateAutoConfiguration 需要交給spring管理 , 掃描包路徑記得添加
4.各個模式介紹
1.AT模式
1.0簡介
- 簡單上手
- 二階段提交
1.1 前提
- 基於支持本地 ACID 事務的關係型數據庫。
- Java 應用,通過 JDBC 訪問數據庫
1.2實現
- 二階段提交演變 , 整體基於本地事務 + 全局鎖 + 本地鎖 實現 回滾通過undo_log進行反向補償
- 一階段:業務數據和回滾日誌記錄在同一個本地事務中提交,釋放本地鎖和連接資源。
- 二階段:
- 提交異步化,非常快速地完成。
- 回滾通過一階段的回滾日誌進行反向補償。
1.3 步驟
- 一階段開啓事務時 首先獲取本地鎖 然後開始走業務邏輯
- 本地鎖獲取失敗 不斷重試
- 一階段提交前 , 首先嚐試獲取該條記錄的全局鎖
- 獲取全局鎖失敗 不能提交本地事務 , 重試獲取全局鎖 (獲取 默認 10ms一次 , 重試30次)
- client.rm.lock.retryInterval 校驗或佔用全局鎖重試間隔 默認10 單位ms
- client.rm.lock.retryTimes 校驗或佔用全局鎖重試次數 默認30
- 重試後仍未獲取到全局鎖 , 回滾本地事務 , 釋放本地鎖
- 獲取成功 一階段事務執行
- 獲取全局鎖失敗 不能提交本地事務 , 重試獲取全局鎖 (獲取 默認 10ms一次 , 重試30次)
- 二階段事務執行
- commit : 直接執行
- rollback : 首先重新獲取 該條記錄的本地鎖 , 然後執行反向補償操作 實現回滾
1.4 髒寫,髒讀問題
1.寫隔離
- 上述案例, 在tx1 二階段提交之前 tx2可以獲取到tx1還未全局提交的數據 ,tx2提交事務會髒寫
- 解決 :
- tx1二階段提交前一直擁有全局鎖 , 回滾時需要獲取本地鎖 ,
- tx2 擁有本地鎖 , 一階段提交前需要獲取到全局鎖
- tx1回滾獲取本地鎖會不斷重試 , 那麼tx2獲取全局鎖會超時
- tx2超時 -> 回滾本地事務 , 釋放本地鎖
- tx1獲取到本地鎖 -> 事務回滾
2.讀隔離
- 上述案例 , tx2是否可以讀到 tx1還未全局提交的數據
- 在數據庫本地事務隔離級別 讀已提交(Read Committed) 或以上的基礎上,Seata(AT 模式)的默認全局隔離級別是 讀未提交(Read Uncommitted) 。
- 通過 SELECT FOR UPDATE實現
- SELECT FOR UPDATE 會去申請全局鎖
- 獲取失敗 則重試獲取 直到全局鎖獲取到
- 對性能消耗較大
- @GlobalLock 註解解決髒讀幻讀問題 (不生成undo_log)
1.5工作原理
# 分支事務的sql
update product set name = 'GTS' where name = 'TXC';
一階段
- 解析sql , 通過條件獲取到查詢前鏡像數據
- select id, name, since from product where name = 'TXC'; 獲取到查詢前鏡像
- 執行sql
- 獲取執行後鏡像 , 根據查詢前鏡像結果 , 通過主鍵 定位查詢後鏡像
- select id, name, since from product where id = 1`;
- 把鏡像前後數據 以及業務sql等信息 組成一條 回滾記錄 插入到undo_log中
{
"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"
}
- 提交前,向 TC 註冊分支:申請 product 表中,主鍵值等於 1 的記錄的 全局鎖 。
- 本地事務提交:業務數據的更新和前面步驟中生成的 UNDO LOG 一併提交。
- 將本地事務提交的結果上報給 TC。
二階段-回滾
- 收到 TC 的分支回滾請求,開啓一個本地事務,執行如下操作。
- 通過 XID 和 Branch ID 查找到相應的 UNDO LOG 記錄。
- 數據校驗:拿 UNDO LOG 中的後鏡與當前數據進行比較,如果有不同,說明數據被當前全局事務之外的動作做了修改。這種情況,需要根據配置策略來做處理
- 根據 UNDO LOG 中的前鏡像和業務 SQL 的相關信息生成並執行回滾的語句:
update product set name = 'TXC' where id = 1;
- 提交本地事務。並把本地事務的執行結果(即分支事務回滾的結果)上報給 TC。
二階段-提交
- 收到 TC 的分支提交請求,把請求放入一個異步任務的隊列中,馬上返回提交成功的結果給 TC。
- 異步任務階段的分支提交請求將異步和批量地刪除相應 UNDO LOG 記錄。
1.6特點
- 改造成本低 , cloud項目基本只需要添加配置文件 , 新增註解
- 普通springboot項目 - 添加配置文件 , 實現XID傳遞
- 隔離性
2.TCC模式
2.0簡介
- 二階段提交
2.1前提
- 每個分支事務需要具備
- 一階段 prepare 方法 (本地提交)
- 二階段 commit 或 rollback 方法
- TCC模式不依賴底層數據源的事務支持
- 一階段 prepare 行爲:調用 自定義 的 prepare 邏輯。
- 二階段 commit 行爲:調用 自定義 的 commit 邏輯。
- 二階段 rollback 行爲:調用 自定義 的 rollback 邏輯。
- TCC其實就是 自定義本地事務 加入了全局事務的管理
2.2特點
- 性能好 , 沒有多餘的操作, 只有TC管理
- 隔離性
- 業務改動大 , 開發困難
3.Saga模式
3.0簡介
- SEATA提供的長事務解決方案
- 業務流程中每個參與者都提交本地事務
- 當出現某一個參與者失敗則補償前面已經成功的參與者
- 一階段正向服務 和 二階段補償服務都由業務開發實現
3.1前提
- 開發一階段正向業務 和 二階段補償業務
3.2特點
- 適合長事務
- 參與者包含其它公司或遺留系統服務,無法提供 TCC 模式要求的三個接口
- 一階段提交本地事務,無鎖,高性能
- 事件驅動架構,參與者可異步執行,高吞吐
- 不保證隔離性
3.3實現