1. 程式人生 > 其它 >Spring Cloud Alibaba分散式事務元件 seata 詳解(小白都能看懂)

Spring Cloud Alibaba分散式事務元件 seata 詳解(小白都能看懂)

一,什麼是事務(本地事務)?

      指作為單個邏輯工作單元執行的一系列操作,要麼完全地執行,要麼完全地不執行。

      簡單的說,事務就是併發控制的單位,是使用者定義的一個操作序列。

     而一個邏輯工作單元要成為事務,就必須滿足ACID屬性。
     A:原子性(Atomicity)
     事務中的操作要麼都不做,要麼就全做。
    C:一致性(Consistency)
    事務執行的結果必須是從資料庫從一個一致性狀態轉換到另一個一致性狀態。
   I:隔離性(Isolation) 
   一個事務的執行不能被其他事務干擾
   D:永續性(Durability)
   一個事務一旦提交,它對資料庫中資料的改變就應該是永久性的。



注:如果對事務不是很懂可以參考這篇文章:什麼是事務?事務的四個特性以及事務的隔離級別 - Kevin.ZhangCG - 部落格園 (cnblogs.com)  

二, 分散式事務

​ 隨著網際網路的快速發展,軟體系統由原來的單體應用轉變為分散式應用,下圖描述了單體應用向微服務的演變:

 

 

​ 分散式系統會把一個應用系統拆分為可獨立部署的多個服務,因此需要服務與服務之間遠端協作才能完成事務操作,這種分散式系統環境下由不同的服務之間通過網路遠端協作完成事務稱之為分散式事務。

三,分散式事務產生的情景

  1. 跨JVM程序產生分散式事務
    典型的場景就是微服務架構:微服務之間通過遠端呼叫完成事務操作。比如:訂單微服務和庫存微服務,下單的同時訂單微服務請求庫存微服務減少庫存。

 

           

 

  1. 跨資料庫例項產生分散式事務
    單體系統訪問多個數據庫例項當單體系統需要訪問多個數據庫(例項)時就會產生分散式事務。比如:使用者資訊和訂單資訊分別在兩個MySQL例項儲存,使用者管理系統刪除使用者資訊,需要分別刪除使用者資訊及使用者的訂單資訊,由於資料分佈在不同的資料例項,需要通過不同的資料庫連結去操作資料,此時產生分散式事務。

 

           

 

  1. 多服務訪問同一個資料庫例項
    訂單微服務和庫存微服務即使訪問同一個資料庫也會產生分散式事務,原因就是跨JVM程序,兩個微服務持有了不同的資料庫連結進行資料庫操作,此時產生分散式事務。

 

            

 

 

四, 分散式事務解決方案之 2PC

      常見分散式事務解決方案:

         1.seata 阿里分散式事務框架

         2. 訊息佇列

         3.sage

         4,XA

   但是他們都有一個共同點:都是兩個階段(2PC)。其實這四種解決方案對應的是分散式事務的四種模式:AT,TCC,Sage,XA 

4.1 什麼是 2PC

2PC 即兩階段提交協議,是將整個事務流程分為兩個階段,準備階段(Prepare phase)、提交階段(commit phase),2 是指兩個階段,P 是指準備階段,C 是指提交階段。

舉例:張三和李四好久不見,老友約起聚餐,飯店老闆要求先買單,才能出票。這時張三和李四分別抱怨近況不如意,囊中羞澀,都不願意請客,這時只能AA。只有張三和李四都付款,老闆才能出票安排就餐。但由於張三和李四都是鐵公雞,形成了尷尬的一幕:

準備階段:老闆要求張三付款,張三付款。老闆要求李四付款,李四付款。

提交階段:老闆出票,兩人拿票紛紛落座就餐。

例子中形成了一個事務,若張三或李四其中一人拒絕付款,或錢不夠,店老闆都不會給出票,並且會把已收款退回。

整個事務過程由事務管理器和參與者組成,店老闆就是事務管理器,張三、李四就是事務參與者,事務管理器負責決策整個分散式事務的提交和回滾,事務參與者負責自己本地事務的提交和回滾。

在計算機中部分關係資料庫如 Oracle、MySQL 支援兩階段提交協議,如下圖:

  1. 準備階段(Prepare phase):事務管理器給每個參與者傳送 Prepare 訊息,每個資料庫參與者在本地執行事務,並寫本地的 Undo/Redo 日誌,此時事務沒有提交。(Undo 日誌是記錄修改前的資料,用於資料庫回滾,Redo 日誌是記錄修改後的資料,用於提交事務後寫入資料檔案)
  2. 提交階段(commit phase):如果事務管理器收到了參與者的執行失敗或者超時訊息時,直接給每個參與者傳送回滾(Rollback)訊息;否則,傳送提交(Commit)訊息;參與者根據事務管理器的指令執行提交或者回滾操作,並釋放事務處理過程中使用的鎖資源。注意:必須在最後階段釋放鎖資源。

下圖展示了2PC的兩個階段,分成功和失敗兩個情況說明:

成功情況

 

 

失敗情況

 

 

 

 

五,Seata 方案(AT模式)

Seata 是由阿里中介軟體團隊發起的開源專案 Fescar,後更名為 Seata,它是一個是開源的分散式事務框架。

傳統 2PC 的問題在 Seata 中得到了解決,它通過對本地關係資料庫的分支事務的協調來驅動完成全域性事務,是工作在應用層的中介軟體。主要優點是效能較好,且不長時間佔用連線資源,它以高效並且對業務 0 侵入的方式解決微服務場景下面臨的分散式事務問題,它目前提供 AT 模式(即 2PC)及 TCC 模式的分散式事務解決方案。

Seata 的設計思想如下:

Seata 的設計目標其一是對業務無侵入,因此從業務無侵入的 2PC 方案著手,在傳統 2PC的基礎上演進,並解決 2PC 方案面臨的問題。

Seata 把一個分散式事務理解成一個包含了若干分支事務的全域性事務。全域性事務的職責是協調其下管轄的分支事務達成一致,要麼一起成功提交,要麼一起失敗回滾。此外,通常分支事務本身就是一個關係資料庫的本地事務,下圖是全域性事務與分支事務的關係圖:

 

 

與傳統 2PC 的模型類似,Seata 定義了 3 個元件來協議分散式事務的處理過程:

 

 

  • Transaction Coordinator(TC):事務協調器,它是獨立的中介軟體,需要獨立部署執行,它維護全域性事務的執行狀態,接收 TM 指令發起全域性事務的提交與回滾,負責與 RM 通訊協調各各分支事務的提交或回滾。
  • Transaction Manager(TM): 事務管理器,TM 需要嵌入應用程式中工作,它負責開啟一個全域性事務,並最終向 TC 發起全域性提交或全域性回滾的指令。
  • Resource Manager(RM):控制分支事務,負責分支註冊、狀態彙報,並接收事務協調器 TC 的指令,驅動分支(本地)事務的提交和回滾。

舉例Seata的分散式事務過程:

    簡單業務邏輯圖:

 

 

第一階段:

 

 

 

第二階段:提交,回滾

 

 

 提交:

 

 

 回滾:

 

 

 

 

 

Seata實現2PC與傳統2PC的差別

架構層次方面:傳統 2PC 方案的 RM 實際上是在資料庫層,RM 本質上就是資料庫自身,通過 XA 協議實現,而 Seata 的 RM 是以 jar 包的形式作為中介軟體層部署在應用程式這一側的。

兩階段提交方面:傳統 2PC無論第二階段的決議是 commit 還是 rollback ,事務性資源的鎖都要保持到 Phase2 完成才釋放。而 Seata 的做法是在 Phase1 就將本地事務提交,這樣就可以省去 Phase2 持鎖的時間,整體提高效率。

六,TCC模式

 

缺點:程式碼侵入性較強,需要自己手寫處理程式碼

優點:沒有鎖的概念,效率高一點,由於是自己手寫,定製化程度比較高。

 Tcc 框架:

概念圖:

      

 

 

 

簡單業務圖:

 

 

 

七,Seata 服務端的自帶表說明

UNDO_LOG(回滾日誌)表:記錄參與者的xid和相關的操作,用於對事務的回滾

表結構:

`CREATE TABLE `undo_log` (

`id` bigint(20) NOT NULL AUTO_INCREMENT,

`branch_id` bigint(20) NOT NULL,

`xid` varchar(100) NOT NULL,

`context` varchar(128) NOT NULL,

`rollback_info` longblob NOT NULL,

`log_status` int(11) 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=1 DEFAULT CHARSET=utf8;

  

在 db 模式下,我們需要針對全域性事務的會話資訊建立以下 3 張資料庫表。

全域性事務表,對應的表為:global_table

分支事務表,對應的表為:branch_table

全域性鎖表,對應的表為:lock_table

 

---資料庫server 要的表

-- -------------------------------- The script used when storeMode is 'db' --------------------------------

-- the table to store GlobalSession data

CREATE TABLE IF NOT EXISTS ‎"‎‎global_table‎‎"‎

(

  `xid`            VARCHAR(128) NOT NULL,

  `transaction_id`      BIGINT,

  `status`          TINYINT   NOT NULL,

  `application_id`      VARCHAR(32),

  `transaction_service_group` VARCHAR(32),

  `transaction_name`     VARCHAR(128),

  `timeout`          INT,

  `begin_time`        BIGINT,

  `application_data`     VARCHAR(2000),

  `gmt_create`        DATETIME,

  `gmt_modified`       DATETIME,

  PRIMARY KEY (`xid`),

  KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),

  KEY `idx_transaction_id` (`transaction_id`)

) ENGINE = InnoDB

 DEFAULT CHARSET = utf8mb4;

 

-- the table to store BranchSession data

CREATE TABLE IF NOT EXISTS `branch_table`

(

  `branch_id`     BIGINT    NOT NULL,

  `xid`        VARCHAR(128) NOT NULL,

  `transaction_id`  BIGINT,

  `resource_group_id` VARCHAR(32),

  `resource_id`    VARCHAR(256),

  `branch_type`    VARCHAR(8),

  `status`      TINYINT,

  `client_id`     VARCHAR(64),

  `application_data` VARCHAR(2000),

  `gmt_create`    DATETIME(6),

  `gmt_modified`   DATETIME(6),

  PRIMARY KEY (`branch_id`),

  KEY `idx_xid` (`xid`)

) ENGINE = InnoDB

 DEFAULT CHARSET = utf8mb4;

 

-- the table to store lock data

CREATE TABLE IF NOT EXISTS `lock_table`

(

  `row_key`    VARCHAR(128) NOT NULL,

  `xid`      VARCHAR(128),

  `transaction_id` BIGINT,

  `branch_id`   BIGINT    NOT NULL,

  `resource_id`  VARCHAR(256),

  `table_name`   VARCHAR(32),

  `pk`       VARCHAR(36),

  `status`     TINYINT   NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',

  `gmt_create`   DATETIME,

  `gmt_modified`  DATETIME,

  PRIMARY KEY (`row_key`),

  KEY `idx_status` (`status`),

  KEY `idx_branch_id` (`branch_id`)

) ENGINE = InnoDB

 DEFAULT CHARSET = utf8mb4;

 

CREATE TABLE IF NOT EXISTS `distributed_lock`

(

  `lock_key`    CHAR(20) NOT NULL,

  `lock_value`   VARCHAR(20) NOT NULL,

  `expire`     BIGINT,

  primary key (`lock_key`)

) ENGINE = InnoDB

 DEFAULT CHARSET = utf8mb4;

 

注:看到這裡了,感覺有用,就關注一下吧,或者贊助一下吧!你們的贊助就是我創作的最大動力。