Spring Cloud Alibaba - Seata


Seata

簡介

分布式事務

事務是數據庫的概念,數據庫事務(ACID:原子性、一致性、隔離性和持久性);

分布式事務的產生,是由於數據庫的拆分和分布式架構(微服務)帶來的,在常規情況下,我們在一個進程中操作一個數據庫,這屬於本地事務,如果在一個進程中操作多個數據庫,或者在多個進程中操作一個或多個數據庫,就產生了分布式事務;

(1)數據庫分庫分表就產生了分布式事務;

img

(2)項目拆分服務化也產生了分布式事務;

img

Seata

Seata是一款開源的分布式事務解決方案,致力於在微服務架構下提供高性能和簡單易用的分布式事務服務;

Seata為用戶提供了AT、TCC、SAGA和XA事務模式,為用戶打造一站式的分布式解決方案;

四種事務模式中,XA模式正在開發中...,其他事務模式已經實現;

目前使用的流行度情況是:AT > TCC > Saga;

我們可以參看seata各公司使用列表:

https://github.com/seata/seata/issues/1246 大部分公司都采用的AT事務模式;

Seata已經在國內很多團隊開始落地,其中不乏有大公司;

Github:https://github.com/seata/seata

官網:http://seata.io/

在Seata的架構中,一共有三個角色:

img

TC (Transaction Coordinator) - 事務協調者

維護全局和分支事務的狀態,驅動全局事務提交或回滾;

TM (Transaction Manager) - 事務管理器

定義全局事務的范圍:開始全局事務、提交或回滾全局事務;

RM (Resource Manager) - 資源管理器

管理分支事務處理的資源,與TC交互以注冊分支事務和報告分支事務的狀態,並驅動分支事務提交或回滾;

其中TC為單獨部署的 Server 服務端,TM和RM為嵌入到應用中的 Client 客戶端;

在Seata中,一個分布式事務的生命周期如下:

img

TM請求TC開啟一個全局事務,TC會生成一個XID作為該全局事務的編號,XID會在微服務的調用鏈路中傳播,保證將多個微服務的子事務關聯在一起;

RM請求TC將本地事務注冊為全局事務的分支事務,通過全局事務的XID進行關聯;

TM請求TC告訴XID對應的全局事務是進行提交還是回滾;

TC驅動RM將XID對應的自己的本地事務進行提交還是回滾;


TC Server(Seata)運行環境部署

我們先部署單機環境的 Seata TC Server,用於學習或測試,在生產環境中要部署集群環境;

因為TC需要進行全局事務和分支事務的記錄,所以需要對應的存儲,目前,TC有三種存儲模式( store.mode ):

file模式:適合單機模式,全局事務會話信息在內存中讀寫,並持久化本地文件 root.data,性能較高;

db模式:適合集群模式,全局事務會話信息通過 db 共享,相對性能差點;

redis模式:解決db存儲的性能問題;

我們先采用file模式,最終我們部署單機TC Server如下圖所示:

img

下載Seata:http://seata.io/zh-cn/blog/download.html

解壓:tar -zxvf seata-server-1.3.0.tar.gz

切換cd seata

img

默認seata-server.sh腳本設置的jvm內存參數2G,我們再虛擬機里面做實驗,可以改小一點;

img

在bin目錄下啟動:./seata-server.sh

默認配置下,Seata TC Server 啟動在 8091 端口;

因為我們沒有修改任何配置文件,默認情況seata使用的是file模式進行數據持久化,所以可以看到用於持久化的本地文件 root.data;


AT模式事務案例-單體應用多數據源分布式事務

img

在Spring Boot單體項目中,如果使用了多數據源,就需要考慮多個數據源的數據一致性,即產生了分布式事務的問題,我們采用Seata的AT事務模式來解決該分布式事務問題;

以電商購物下單為例:

img

1、准備數據庫表和數據;

其中每個庫中的undo_log表,是 Seata AT模式必須創建的表,主要用於分支事務的回滾;

2、開發一個SpringBoot單體應用;

img

測試:http://localhost:8080/order?userId=1&productId=1


AT模式事務案例-微服務的分布式事務

img

tips:異常部分如果使用try-catch進行捕獲,則Seata不會檢測到異常也就不會進行事務回滾


AT事務模式分布式事務工作機制

前提

基於支持本地 ACID 事務的關系型數據庫;(mysql、oracle)

Java 應用,通過JDBC訪問數據庫;

整體機制

就是兩階段提交協議的演變:

一階段:

“業務數據“和“回滾日志記錄“在同一個本地事務中提交,釋放本地鎖和連接資源;

二階段:

如果沒有異常異步化提交,非常快速地完成;

如果有異常回滾通過一階段的回滾日志進行反向補償;

具體舉例說明整個AT分支的工作過程:

業務表:product

*Field* *Type* *Key*

id bigint(20) PRI

name varchar(100)

since varchar(100)

AT分支事務的業務邏輯:

update product set name = 'GTS' where name = 'TXC';

一階段過程:

1、解析SQL,得到SQL的類型(UPDATE),表(product),條件(where name = 'TXC')等相關的信息;

2、查詢前鏡像:根據解析得到的條件信息,生成查詢語句,定位數據;

select id, name, since from product where name = 'TXC';

得到前鏡像:

id name since

1 TXC 2014

3、執行業務 SQL:更新這條記錄的 name 為 'GTS';

4、查詢后鏡像:根據前鏡像的結果,通過 主鍵 定位數據;

select id, name, since from product where id = 1;

得到后鏡像:

id name since

1 GTS 2014

5、插入回滾日志:把前后鏡像數據以及業務SQL相關的信息組成一條回滾日志記錄,插入到 UNDO_LOG 表中;

img

6、分支事務提交前,向TC注冊分支,申請product表中,主鍵值等於1的記錄的全局鎖(在當前的同一個全局事務id范圍內是可以申請到全局鎖的,不同的全局事務id才會排斥);

7、本地事務提交:業務數據的更新和前面步驟中生成的 UNDO LOG 一並提交;

8、將本地事務提交的結果上報給TC;

二階段-回滾

1、收到 TC 的分支回滾請求,開啟一個本地事務,執行如下操作;

2、通過 XID 和 Branch ID 查找到相應的 UNDO LOG 記錄;

3、數據校驗:拿 UNDO LOG 中的后鏡像與當前數據進行比較,如果有不同,說明數據被當前全局事務之外的動作做了修改,這種情況,需要人工來處理;

4、根據 UNDO LOG 中的前鏡像和業務 SQL 的相關信息生成並執行回滾的語句:

update product set name = 'TXC' where id = 1;

5、提交本地事務,並把本地事務的執行結果(即分支事務回滾的結果)上報給 TC;

二階段-提交

1、收到TC的分支提交請求,把請求放入一個異步任務的隊列中,馬上返回提交成功的結果給TC;

2、異步任務階段的分支提交請求將異步和批量地刪除相應UNDO LOG記錄;

回滾日志表:

*Field* *Type*

branch_id bigint PK

xid varchar(100)

context varchar(128)

rollback_info longblob

log_status tinyint

log_created datetime

log_modified datetime

SQL建表語句:

CREATE TABLE `undo_log` (
 `id` bigint NOT NULL AUTO_INCREMENT,
 `branch_id` bigint NOT NULL,
 `xid` varchar(100) NOT NULL,
 `context` varchar(128) NOT NULL,
 `rollback_info` longblob NOT NULL,
 `log_status` int NOT NULL,
 `log_created` datetime NOT NULL,
 `log_modified` datetime NOT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

AT事務模式運行機制解讀

AT 模式的前提:

1、基於支持本地 ACID 事務的關系型數據庫;

2、Java 應用,通過 JDBC 訪問數據庫;

整體機制是兩階段提交協議的演變:

一階段

​ 業務數據和回滾日志記錄在同一個本地事務中提交,釋放本地鎖和連接資源;(本地事務,就已經在數據庫持久化了)

二階段:

​ 如果沒有異常提交異步化,非常快速地完成;(正常情況,那就提交了,同步一下TC Server的狀態,刪除回滾日志)

如果有異常回滾通過一階段的回滾日志進行反向補償;(比如訂單刪除,庫存加回去,余額加回去);


AT事務模式數據隔離

寫隔離

一階段本地事務提交前,需要確保先拿到全局鎖;

拿不到 全局鎖 ,不能提交本地事務;

拿 全局鎖 的嘗試被限制在一定范圍內,超出范圍將放棄,並回滾本地事務,釋放本地鎖;

以一個示例來說明:

兩個或者多個全局事務 tx1 和 tx2,分別並發對 a 表的 m 字段進行更新操作,m 的初始值 1000;

假設tx1 先開始,開啟本地事務,拿到本地鎖,更新操作 m = 1000 - 100 = 900,本地事務提交前,先拿到該記錄的 全局鎖 ,拿到了全局鎖,本地提交並釋放本地鎖;

tx2后開始,開啟本地事務,拿到本地鎖,更新操作 m = 900 - 100 = 800,本地事務提交前,嘗試拿該記錄的 全局鎖 ,tx1全局提交前,該記錄的全局鎖一直會被 tx1 持有,tx2 需要重試等待 全局鎖 ;

img

tx1 二階段全局提交,釋放 全局鎖 ,tx2 拿到 全局鎖 提交本地事務;

img

如果 tx1 的二階段全局回滾,則 tx1 需要重新獲取該數據的本地鎖,進行反向補償的更新操作,實現分支的回滾;

此時,如果 tx2 仍在等待該數據的 全局鎖,同時持有本地鎖,則 tx1 的分支回滾會失敗。分支的回滾會一直重試,直到 tx2 的 全局鎖 等鎖超時,放棄 全局鎖 並回滾本地事務釋放本地鎖,tx1 的分支回滾最終成功;

因為整個過程 全局鎖 在 tx1 結束前一直是被 tx1 持有的,所以不會發生 臟寫 的問題;

讀隔離

在數據庫本地事務隔離級別 讀已提交(Read Committed) 或以上的基礎上,Seata(AT 模式)的默認全局隔離級別是讀未提交(Read Uncommitted)

如果應用在特定場景下,必需要求全局的 讀已提交 ,目前 Seata 的方式是通過 SELECT FOR UPDATE 語句的代理;

img

SELECT FOR UPDATE 語句的執行會申請 全局鎖 ,如果 全局鎖 被其他事務持有,則釋放本地鎖(回滾 SELECT FOR UPDATE 語句的本地執行)並重試,這個過程中,查詢是被 block 住的,直到 全局鎖 拿到,即讀取的相關數據是 已提交 的,才返回;

出於總體性能上的考慮,Seata目前的方案並沒有對所有SELECT語句都進行代理,僅針對 FOR UPDATE 的 SELECT 語句;


Seata TC Server集群部署

生產環境下,需要部署集群 Seata TC Server,實現高可用,在集群時,多個 Seata TC Server 通過 db 數據庫或者redis實現全局事務會話信息的共享;

每個Seata TC Server注冊自己到注冊中心上,應用從注冊中心獲得Seata TC Server實例,這就是Seata TC Server的集群;

img

Seata TC Server 對主流的注冊中心都提供了集成,Naco作為注冊中心越來越流行,這里我們就采用Nacos;

Seata TC Server集群搭建具體步驟:

1、下載並解壓兩個seata-server-1.3.0.tar.gz;

2、初始化 Seata TC Server 的 db 數據庫,在 MySQL 中,創建 seata 數據庫,並在該庫下執行如下SQL腳本:

使用seata-1.3.0\script\server\db腳本(網盤有共享)

3、修改 seata/conf/file.conf 配置文件,修改使用 db 數據庫,實現 Seata TC Server 的全局事務會話信息的共享;

(1)mode = "db"

(2)數據庫的連接信息

driverClassName = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://39.99.163.122:3306/seata"
user = "mysql"
password = "UoT1R8[09/VsfXoO5>6YteB"

4、設置使用 Nacos 注冊中心;

修改 seata/conf/registry.conf 配置文件,設置使用 Nacos 注冊中心;

(1)type = "nacos"

(2)Nacos連接信息:

nacos {
  application = "seata-server"
  serverAddr = "127.0.0.1:8848"
  group = "SEATA_GROUP"
  namespace = ""
  cluster = "default"
  username = ""
  password = ""
}

5、啟動數據庫和nacos;

6、啟動兩個 TC Server

執行 ./seata-server.sh -p 18091 -n 1 命令,啟動第一個TC Server;

-p:Seata TC Server 監聽的端口;

-n:Server node,在多個 TC Server 時,需區分各自節點,用於生成不同區間的 transactionId 事務編號,以免沖突;

執行 ./seata-server.sh -p 28091 -n 2 命令,啟動第二個TC Server;

7、打開Nacos注冊中心控制台,可以看到有兩個Seata TC Server 實例;

8、應用測試;

img

對於SpringBoot單體應用:

1、添加nacos客戶端依賴;

<!-- nacos-client -->
<dependency>
  <groupId>com.alibaba.nacos</groupId>
  <artifactId>nacos-client</artifactId>
  <version>1.3.1</version>
</dependency>

2、配置application.properties文件

#----------------------------------------------------------# Seata**應用編號,默認為**${spring.application.name}**
seata.application-id=springcloud-order-seata
# Seata**事務組編號,用於**TC**集群名**
seata.tx-service-group=springcloud-order-seata-group
#* *虛擬組和分組的映射**
seata.service.vgroup-mapping.springcloud-order-seata-group=default
#seata-spring-boot-starter 1.1**版本少一些配置項**
seata.enabled=true
seata.registry.type=nacos
seata.registry.nacos.cluster=default
seata.registry.nacos.server-addr=192.168.172.128:8848
seata.registry.nacos.group=SEATA_GROUP
seata.registry.nacos.application=seata-server
#----------------------------------------------------------

對於Spring Cloud Alibaba微服務應用:

則不需要加nacos的jar包依賴,application.properties文件配置完全一樣;


TCC事務模式執行機制

AT模式基本上能滿足我們使用分布式事務大部分需求,但涉及非關系型數據庫與中間件的操作、跨公司服務的調用、跨語言的應用調用就需要結合TCC模式;

img

一個分布式的全局事務,整體是兩階段提交(Try - [Comfirm/Cancel])的模型,在Seata中,AT模式與TCC模式事實上都是基於兩階段提交,它們的區別在於:

AT模式基於支持本地ACID事務的關系型數據庫:

1、一階段prepare行為:在本地事務中,一並提交“業務數據更新“和”相應回滾日志記錄”;

2、二階段 commit 行為:馬上成功結束,自動異步批量清理回滾日志;

3、二階段 rollback 行為:通過回滾日志,自動生成補償操作,完成數據回滾;

而TCC 模式,需要我們人為編寫代碼實現提交和回滾:

1、一階段 prepare 行為:調用自定義的 prepare 邏輯;(真正要做的事情,比如插入訂單,更新庫存,更新余額)

2、二階段 commit 行為:調用自定義的 commit 邏輯;(自己寫代碼實現)

3、二階段 rollback 行為:調用自定義的 rollback 邏輯;(自己寫代碼實現)

所以TCC模式,就是把自定義的分支事務的提交和回滾並納入到全局事務管理中;

通俗來說,Seata的TCC模式就是手工版本的AT模式,它允許你自定義兩階段的處理邏輯而不需要依賴AT模式的undo_log回滾表;


TCC事務模式應用實踐

基於SpringBoot單體應用的TCC事務

![img](E:\software\JAVA\springcloud-alibaba\document\動力節點spring cloud alibaba\wps7-1627612492997.jpg)

@LocalTCC
public interface AccountService {
  */****
\*   *** *扣除余額**
\*   *** *定義兩階段提交**
\*   ** name = reduceStock**為一階段**try**方法**
\*   ** commitMethod = commitTcc* *為二階段確認方法**
\*   ** rollbackMethod = cancel* *為二階段取消方法**
\*   ** BusinessActionContextParameter**注解 可傳遞參數到二階段方法**
\*   ****
\*   ** @param* *userId* *用戶**ID**
\*   ** @param* *money*  *扣減金額**
\*   ** @throws* *Exception* *失敗時拋出異常**
\*   **/**
 @TwoPhaseBusinessAction(name = "reduceBalance", commitMethod = "commitTcc", rollbackMethod = "cancelTcc")
  void reduceBalance(@BusinessActionContextParameter(paramName = "userId") Integer userId,
            @BusinessActionContextParameter(paramName = "money") BigDecimal money);

  */****
\*   *** *確認方法、可以另命名,但要保證與**commitMethod**一致**
\*   ** context**可以傳遞**try**方法的參數**
\*   ****
\*   ** @param* *context* *上下文**
\*   ** @return boolean**
\*   **/**
 boolean commitTcc(BusinessActionContext context);

  */****
\*   *** *二階段取消方法**
\*   ****
\*   ** @param* *context* *上下文**
\*   ** @return boolean**
\*   **/**
 boolean cancelTcc(BusinessActionContext context);
}

 

@LocalTCC注解標識此TCC為本地模式,即該事務是本地調用,非RPC調用,@LocalTCC一定需要注解在接口上,此接口可以是尋常的業務接口,只要實現了TCC的兩階段提交對應方法即可;

@TwoPhaseBusinessAction,該注解標識為TCC模式,注解try方法,其中name為當前tcc方法的bean名稱,寫方法名便可(全局唯一),commitMethod指提交方法,rollbackMethod指事務回滾方法,指定好三個方法之后,Seata會根據事務的成功或失敗,通過動態代理去幫我們自動調用提交或者回滾;

@BusinessActionContextParameter 注解可以將參數傳遞到二階段(commitMethod/rollbackMethod)的方法;

BusinessActionContext 是指TCC事務上下文,攜帶了業務方法的參數;

基於Spring Cloud Alibaba的TCC分布式事務

img

具體代碼實現和springboot單體應用的代碼實現幾乎沒有區別,具體參考Git上提交的代碼;

由於Seata出現時間並不長,也在不斷的改進中,在實際面試中應該不會問大家比較底層的實現,同學們如果感興趣的話,基於我們已有的源碼閱讀經驗,可以看一下Seata的源碼,它如何進行事務隔離保證數據一致性,官方提供的文檔並不詳細;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM