SpringCloudAlibaba分散式事務解決方案Seata實戰與原始碼分析-中

itxiaoshen發表於2022-07-17

事務模式

概述

  • 在當前的技術發展階段,不存一個分散式事務處理機制可以完美滿足所有場景的需求。一致性、可靠性、易用性、效能等諸多方面的系統設計約束,需要用不同的事務處理機制去滿足。
  • 目前使用的流行度情況是:AT>TCC > Saga,Seata 專案最核心的價值在於:構建一個全面解決分散式事務問題的標準化平臺。基於 Seata,上層應用架構可以根據實際場景的需求,靈活選擇合適的分散式事務解決方案。
  • Seata針對不同的業務場景提供了四種不同的事務模式,對比如下:
    • AT模式:AT 模式的一階段、二階段提交和回滾(藉助undo_log表來實現)均由 Seata 框架自動生成,使用者只需編寫“業務SQL”,便能輕鬆接入分散式事務,AT 模式是一種對業務無任何侵入的分散式事務解決方案。
    • TCC模式:相對於 AT 模式,TCC 模式對業務程式碼有一定的侵入性,但是 TCC 模式無 AT 模式的全域性行鎖,TCC 效能會比 AT模式高很多。適用於核心系統等對效能有很高要求的場景。
    • SAGA模式:Sage 是長事務解決方案,事務驅動,使用那種存在流程稽核的業務場景。
    • XA模式:XA模式是分散式強一致性的解決方案,但效能低而使用較少。

AT模式

基礎理論

  • 使用AT模式的前提條件

    • 基於支援本地 ACID 事務的關係型資料庫。
    • Java 應用,通過 JDBC 訪問資料庫。
  • AT模式支援的資料庫有:MySQL、Oracle、PostgreSQL、 TiDB、MariaDB。

  • 整體機制為兩階段提交協議的演變

    • 一階段:業務資料和回滾日誌記錄在同一個本地事務中提交,釋放本地鎖和連線資源。
    • 二階段:
      • 提交非同步化,非常快速地完成。
      • 回滾通過一階段的回滾日誌進行反向補償。
  • 寫隔離

    • 一階段本地事務提交前,需要確保先拿到 全域性鎖
    • 拿不到 全域性鎖 ,不能提交本地事務。
    • 全域性鎖 的嘗試被限制在一定範圍內,超出範圍將放棄,並回滾本地事務,釋放本地鎖。
  • 讀隔離

    • 在資料庫本地事務隔離級別 讀已提交(Read Committed) 或以上的基礎上,Seata(AT 模式)的預設全域性隔離級別是 讀未提交(Read Uncommitted)
    • 如果應用在特定場景下,必需要求全域性的 讀已提交 ,目前 Seata 的方式是通過 SELECT FOR UPDATE 語句的代理。
  • 工作機制

    • 假如分支事務邏輯為:update product set name = 'GTS' where name = 'TXC'; 業務表product 表欄位如下

    image-20220712103504661

    • 一階段

      • 解析 SQL:得到 SQL 的型別(UPDATE),表(product),條件(where name = 'TXC')等相關的資訊。select id, name, since from product where name = 'TXC';

      • 查詢前映象:根據解析得到的條件資訊,生成查詢語句,定位資料。前映象為image-20220712103808963

      • 執行業務 SQL:更新這條記錄的 name 為 'GTS'。

      • 查詢後映象:根據前映象的結果,通過 主鍵 定位資料。select id, name, since from product where id = 1;後映象為image-20220712103903331

      • 插入回滾日誌:把前後映象資料以及業務 SQL 相關的資訊組成一條回滾日誌記錄,插入到 UNDO_LOG 表中。

      • 提交前,向 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 記錄。

實戰示例

我們重新修改下上一小節seata,專門定義一個group: SEATA_GROUP,然後重新啟動seata

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:
    # support: nacos 、 consul 、 apollo 、 zk  、 etcd3
    type: nacos
    nacos:
      server-addr: 192.168.50.95:8848
      namespace: a2b1a5b7-d0bc-48e8-ab65-04695e61db01
      group: SEATA_GROUP
      username: nacos
      password: nacos
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key: ""
      #secret-key: ""
      data-id: seataServer.properties
  registry:
    # support: nacos 、 eureka 、 redis 、 zk  、 consul 、 etcd3 、 sofa
    type: nacos
    preferred-networks: 30.240.*
    nacos:
      application: seata-server
      server-addr: 192.168.50.95:8848
      group: SEATA_GROUP
      namespace: a2b1a5b7-d0bc-48e8-ab65-04695e61db01
      #cluster: default
      username: nacos
      password: nacos
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key: ""
      #secret-key: ""

  server:
    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
    max-commit-retry-timeout: -1
    max-rollback-retry-timeout: -1
    rollback-retry-timeout-unlock-enable: false
    enableCheckAuth: true
    retryDeadThreshold: 130000
    xaerNotaRetryTimeout: 60000
    recovery:
      handle-all-session-period: 1000
    undo:
      log-save-days: 7
      log-delete-period: 86400000
    session:
      branch-async-queue-size: 5000 #branch async remove queue size
      enable-branch-async-remove: false #enable to asynchronous remove branchSession
  metrics:
    enabled: false
    registry-type: compact
    exporter-list: prometheus
    exporter-prometheus-port: 9898
  transport:
    rpc-tc-request-timeout: 30000
    enable-tc-server-batch-send-response: false
    shutdown:
      wait: 3
    thread-factory:
      boss-thread-prefix: NettyBoss
      worker-thread-prefix: NettyServerNIOWorker
      boss-thread-size: 1
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login

我們建立訂單和庫存兩個微服務來測試AT事務模式

  • 訂單服務:根據採購需求建立訂單。
  • 庫存服務:對給定的商品扣除倉儲數量。

SEATA AT 模式需要 UNDO_LOG 表,UNDO_LOG 表語句在原始碼目錄下script\client\at\db\mysql.sql裡

建立訂單資料庫storage,建立庫存業務表storage_tbl和undo_log

DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- for AT mode you must to init this sql for you business database. the seata server not need it.
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';

建立庫存資料庫order,建立訂單業務表order_tbl

DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT 0,
  `money` int(11) DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- for AT mode you must to init this sql for you business database. the seata server not need it.
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';

image-20220713150525635

引入seata依賴,由於庫存微服務和訂單微服務都引用ecom-commons,所以在ecom-commons的pom中新增如下seata依賴,注意這裡web容器建議使用tomcat,原來我使用的是undertow,但是執行分散式事務報錯所以最後註釋使用undertow
image-20220717005155846

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.5.2</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <version>2021.1</version>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

由於使用最新版本seata-all依賴需要class位元組碼版本為55也即是JDK11,因此演示專案使用的JDK11,庫存微服務和訂單微服務微服務的啟動配置檔案中加入如下配置

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.50.95:8848
        group: ecom-group
        namespace: a2b1a5b7-d0bc-48e8-ab65-04695e61db01      
        username: nacos
        password: nacos
# seata 配置
seata:
  # 使用哪個事務組
  tx-service-group: default_tx_group
  service:
    vgroup-mapping:
      default_tx_group: default # TC 叢集(必須與seata-server保持一致)
  enabled: true
  config:
    type: nacos
    nacos:
      username: nacos
      password: nacos
      # 讀取的配置分組
      group: SEATA_GROUP
      server-addr: 192.168.50.95:8848
      namespace: a2b1a5b7-d0bc-48e8-ab65-04695e61db01
      dataId: seataServer.properties
  # 註冊中心設定
  registry:
    type: nacos
    nacos:
      # SEATA服務中心的微服務名,此處與服務端保持一致
      application: seata-server
      server-addr: 192.168.50.95:8848
      username: nacos
      password: nacos
      group: SEATA_GROUP
      namespace: a2b1a5b7-d0bc-48e8-ab65-04695e61db01

訂單控制器提供訂單建立介面

image-20220713171908476

庫存控制器提供扣減庫存介面

image-20220713172002569

通過在訂單的實現類上新增一個@GlobalTransactional註解實現分散式事務
image-20220713172312967

目前是一個正常場景,訪問http://localhost:4070/order/create/1000/1001/2 返回成功

image-20220713172357497

庫存成功扣減2個

image-20220713172437200

訂單表也增加一條記錄

image-20220713172505444

庫存實現類增加異常程式碼

image-20220713173714822

訪問http://localhost:4070/order/create/1000/1001/2 返回失敗

image-20220713173128755

庫存表沒有減庫存,訂單表也沒有新的訂單記錄

image-20220713173334737

在訂單的實現類實現類建立訂單方法中去掉@GlobalTransactional後再次訪問,這時候就沒有分散式事務支援,再次訪問後訂單表有新增新的訂單記錄,但是庫存表沒有減少,至此AT事務模式的例項完整結束。

除錯看過程

在訂單實現類create方法和庫存實現類的deduct方法入口加上埠,重新啟動兩個微服務

image-20220717010555501

檢視seata server的執行日誌,出現庫存和訂單微服務已經註冊到seata中

image-20220717010501868

檢視nacos服務列表,由於我啟動兩個訂單微服務

image-20220717010853932

訪問http://localhost:4070/order/create/1000/1001/2 ,進入第一個斷點,注意整個分散式事務不要超過超時時間預設為60秒,可以配置,我是快速截圖儲存

image-20220717011156158

當斷點走完庫存返回檢視幾張表如下,鎖表記錄

image-20220717011745582

分支表記錄

image-20220717012407573

庫存表undo_log表資訊如下

image-20220717012427417

rollback_info資訊,核心是beforeImage和afterImage

{"@class":"io.seata.rm.datasource.undo.BranchUndoLog","xid":"192.168.5.52:8091:9043512942211305791","branchId":9043512942211305793,"sqlUndoLogs":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.undo.SQLUndoLog","sqlType":"UPDATE","tableName":"storage_tbl","beforeImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"storage_tbl","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"id","keyType":"PRIMARY_KEY","type":4,"value":1},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"count","keyType":"NULL","type":4,"value":760}]]}]]},"afterImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"storage_tbl","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"id","keyType":"PRIMARY_KEY","type":4,"value":1},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"count","keyType":"NULL","type":4,"value":758}]]}]]}}]]}

訂單表的undo_log表資訊如下

image-20220717012835064

訂單表的undolog的rollback_info資訊,核心是beforeImage和afterImage

{"@class":"io.seata.rm.datasource.undo.BranchUndoLog","xid":"192.168.5.52:8091:9043512942211305857","branchId":9043512942211305887,"sqlUndoLogs":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.undo.SQLUndoLog","sqlType":"INSERT","tableName":"order_tbl","beforeImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords$EmptyTableRecords","tableName":"order_tbl","rows":["java.util.ArrayList",[]]},"afterImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"order_tbl","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"commodity_code","keyType":"NULL","type":12,"value":"1001"},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"count","keyType":"NULL","type":4,"value":2},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"id","keyType":"PRIMARY_KEY","type":4,"value":9},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"money","keyType":"NULL","type":4,"value":20},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"user_id","keyType":"NULL","type":12,"value":"1000"}]]}]]}}]]}

XA模式

前置理論

剛性事務指的是分散式事務要像本地式事務⼀樣,具備資料強⼀致性。從 CAP 來看就是要達到 CP 狀態。常見的剛性事務方案有:XA 協議(2PC、JTA、JTS)、3PC。由於剛性事務同步阻塞,處理效率低,不適合⼤型⽹站分散式場景。

XA 規範是 X/Open 組織定義的分散式事務處理 (DTP,Distributed Transaction Processing) 標準。規範描述了全域性的事務管理器與區域性的資源管理器之間的介面。對於 XA 模型,包含三個角色:

  • AP:Applicaiton,應用程式業務層,哪些操作屬於⼀個事務,就是 AP 定義的。
  • TM:Transaction Manager:接收 AP 的事務請求,對全域性事務進⾏管理,管理事務分⽀狀態,協調 RM 的處理,通知 RM 哪些操作屬於哪些全域性事務以及事務分⽀等等。這個也是整個事務排程模型的核⼼部分。
  • RM:Resource Manager,資源管理器:⼀般是資料庫,也可以是其他的資源管理器,如訊息佇列 (如 JMS 資料來源),⽂件系統等。

XA 規範的目的是允許多個資源 (如資料庫,應用伺服器,訊息佇列等) 在同一事務中訪問,這樣可以使 ACID 屬性跨越應用程式而保持有效。XA 規範使用兩階段提交 (2PC,Two-Phase Commit) 協議來保證所有資源同時提交或回滾任何特定的事務。目前知名的資料庫,如 Oracle, DB2, mysql 等,都是實現了 XA 介面的,都可以作為 RM。

XA 規範定義了 (全域性) 事務管理器 (Transaction Manager) 和 (區域性) 資源管理器 (Resource Manager) 之間的介面。XA 介面是雙向的系統介面,在事務管理器 (Transaction Manager) 以及一個或多個資源管理器 (Resource Manager) 之間形成通訊橋樑。

XA 之所以需要引入事務管理器是因為,在分散式系統中,從理論上講,兩臺機器理論上無法達到一致的狀態,需要引入一個單點進行協調。事務管理器控制著全域性事務,管理事務生命週期,並協調資源。資源管理器負責控制和管理實際資源 (如資料庫或 JMS 佇列)

XA 是資料庫的分散式事務,強一致性,在整個過程中,資料一張鎖住狀態,即從 prepare 到 commit、rollback 的整個過程中,TM 一直把持折資料庫的鎖,如果有其他人要修改資料庫的該條資料,就必須等待鎖的釋放,存在⻓事務⻛險。

為什麼要在Seata中支援XA

Seata 已經支援了三大事務模式:AT、TCC、SAGA,這三個都是補償型事務,補償型事務處理你機制構建在 事務資源之上(要麼中介軟體層面,要麼應用層),事務資源本身對於分散式的事務是無感知的,這種對於分散式事務的無感知存在有一個根本性的問題,無法做到真正的全域性一致性。

例如一個庫存記錄,在補償型事務處理過程中,用80扣減為60,這個時候倉庫管理員查詢資料結果,看到的是60,之後因為異常回滾,庫存回滾到原來的80,那麼這個時候庫存管理員看到的60,其實就是髒資料,而這個中間狀態就是補償型事務存在的髒資料。

和補償型事務不同,XA協議要求事務資源 本身提供對規範和協議的支援,因為事務資源感知並參與分散式事務處理過程中,所以事務資源可以保證從任意視角對資料的訪問有效隔離性,滿足全域性資料的一致性。

XA的價值

與 補償型 不同,XA 協議 要求 事務資源 本身提供對規範和協議的支援。因為 事務資源 感知並參與分散式事務處理過程,所以 事務資源(如資料庫)可以保障從任意視角對資料的訪問有效隔離,滿足全域性資料一致性。比如,剛才提到的庫存更新場景,XA 事務處理過程中,中間狀態資料庫存 50 由資料庫本身保證,是不會被倉庫管理員的查詢統計看到的。除了 全域性一致性 這個根本性的價值外,支援 XA 還有如下幾個方面的好處:

  • 業務無侵入:和 AT 一樣,XA 模式將是業務無侵入的,不給應用設計和開發帶來額外負擔。
  • 資料庫的支援廣泛:XA 協議被主流關係型資料庫廣泛支援,不需要額外的適配即可使用。
  • 多語言支援容易:因為不涉及 SQL 解析,XA 模式對 Seata 的 RM 的要求比較少。
  • 傳統基於 XA 應用的遷移:傳統的,基於 XA 協議的應用,遷移到 Seata 平臺,使用 XA 模式將更平滑。

seata XA基礎理論

  • 前提

    • 支援XA 事務的資料庫。
    • Java 應用,通過 JDBC 訪問資料庫。
  • 整體機制

    • 在 Seata 定義的分散式事務框架內,利用事務資源(資料庫、訊息服務等)對 XA 協議的支援,以 XA 協議的機制來管理分支事務的一種 事務模式。

image-20220717154316317

  • 執行階段

    • 可回滾:業務 SQL 操作放在 XA 分支中進行,由資源對 XA 協議的支援來保證 可回滾
    • 持久化:XA 分支完成後,執行 XA prepare,同樣,由資源對 XA 協議的支援來保證 持久化(即,之後任何意外都不會造成無法回滾的情況)
  • 完成階段

    • 分支提交:執行 XA 分支的 commit
    • 分支回滾:執行 XA 分支的 rollback
  • XA模式只支援實現了XA協議的資料庫。Seata支援MySQL、Oracle、PostgreSQL和MariaDB。

實戰示例

XA事務模式使用和AT事務模式使用基本沒有區別,我們還是用前面的例子,只需要在庫存和訂單微服務的配置檔案中新增data-source-proxy-mode為XA,其他不變

seata:
  data-source-proxy-mode: XA

XA模式不需要undo_log表,演示時我們可以先把訂單和庫存資料庫的undo_log表刪掉,為了看下xid,增加一個xid列印日誌,啟動訂單和庫存微服務,訪問http://localhost:4070/order/create/2000/1001/5 ,訂單微服務日誌列印如下

image-20220717125832841

庫存微服務資訊日誌資訊也使用XA模式,全面xid事務deduct xid:192.168.5.52:8091:9043512942211306753和訂單相同

image-20220717130228498

檢視訂單資料庫已經新增訂單,庫存已經建庫存,這個是正常場景。
image-20220717130352578

在訂單中新增一個異常,http://localhost:4070/order/create/2000/1001/5 顯示異常,後臺日誌列印異常的提示

image-20220717130830230

庫存微服務中日誌已經進行回滾,檢視庫存表沒有減庫存,訂單也沒有建立

image-20220717131001619

TCC模式

  • 一個分散式的全域性事務,整體是 兩階段提交 的模型。全域性事務是由若干分支事務組成的,分支事務要滿足 兩階段提交 的模型要求,即需要每個分支事務都具備自己的:

    • 一階段 prepare 行為
    • 二階段 commit 或 rollback 行為

    image-20220712141421195

  • 根據兩階段行為模式的不同,我們將分支事務劃分為 Automatic (Branch) Transaction ModeManual (Branch) Transaction Mode.

  • TCC模式不依賴資料來源(1.4.2版本及之前),1.4.2版本之後增加了TCC防懸掛措施,需要資料來源支援。

  • AT模式是基於 支援本地 ACID 事務關係型資料庫

    • 一階段 prepare 行為:在本地事務中,一併提交業務資料更新和相應回滾日誌記錄。
    • 二階段 commit 行為:馬上成功結束,自動 非同步批量清理回滾日誌。
    • 二階段 rollback 行為:通過回滾日誌,自動 生成補償操作,完成資料回滾。
  • TCC 模式則不依賴於底層資料資源的事務支援,支援把 自定義 的分支事務納入到全域性事務的管理中。

    • 一階段 prepare 行為:呼叫 自定義 的 prepare 邏輯。
    • 二階段 commit 行為:呼叫 自定義 的 commit 邏輯。
    • 二階段 rollback 行為:呼叫 自定義 的 rollback 邏輯。

案例可以參考https://github.com/seata/seata-samples ,後續再補充實戰案例

Saga模式

Saga模式是SEATA提供的長事務解決方案,在Saga模式中,業務流程中每個參與者都提交本地事務,當出現某一個參與者失敗則補償前面已經成功的參與者,一階段正向服務和二階段補償服務都由業務開發實現。

image-20220712141902003

  • 使用場景
    • 業務流程長、業務流程多
    • 參與者包含其它公司或遺留系統服務,無法提供 TCC 模式要求的三個介面
  • 優勢
    • 一階段提交本地事務,無鎖,高效能
    • 事件驅動架構,參與者可非同步執行,高吞吐
    • 補償服務易於實現
  • 缺點
    • 不保證隔離性
  • Saga模式不依賴資料來源。

Seata基於狀態機引擎的 Saga 實現:

目前SEATA提供的Saga模式是基於狀態機引擎來實現的,機制是:

  1. 通過狀態圖來定義服務呼叫的流程並生成 json 狀態語言定義檔案
  2. 狀態圖中一個節點可以是呼叫一個服務,節點可以配置它的補償節點
  3. 狀態圖 json 由狀態機引擎驅動執行,當出現異常時狀態引擎反向執行已成功節點對應的補償節點將事務回滾

注意: 異常發生時是否進行補償也可由使用者自定義決定

  1. 可以實現服務編排需求,支援單項選擇、併發、子流程、引數轉換、引數對映、服務執行狀態判斷、異常捕獲等功能

Seata Saga 提供了一個視覺化的狀態機設計器方便使用者使用,程式碼和執行指南請參考: https://github.com/seata/seata/tree/develop/saga/seata-saga-statemachine-designer,官方案例可以參考https://github.com/seata/seata-samples ,後續再充實戰案例

**本人部落格網站 **IT小神 www.itxiaoshen.com

相關文章