主頁 > 後端開發 > Seata分布式事務 (理論與部署相結合)

Seata分布式事務 (理論與部署相結合)

2023-02-15 07:11:27 後端開發

分布式事務——Seata

分布式事務

1. 本地事務與分布式事務

1.1 本地事務

本地事務,也就是傳統的單機事務,在傳統資料庫事務中,必須要滿足四個原則:

image

1.2 分布式事務問題

分布式事務,就是指不是在單個服務或單個資料庫架構下,產生的事務,例如:

  • 跨資料源的分布式事務
  • 跨服務的分布式事務
  • 綜合情況

在資料庫水平拆分、服務垂直拆分之后,一個業務操作通常要跨多個資料庫、服務才能完成,例如電商行業中比較常見的下單付款案例,包括下面幾個行為:

  • 創建新訂單
  • 扣減商品庫存
  • 從用戶賬戶余額扣除金額

完成上面的操作需要訪問三個不同的微服務和三個不同的資料庫,

image

訂單的創建、庫存的扣減、賬戶扣款在每一個服務和資料庫內是一個本地事務,可以保證ACID原則,

但是當我們把三件事情看做一個"業務",要滿足保證“業務”的原子性,要么所有操作全部成功,要么全部失敗,不允許出現部分成功部分失敗的現象,這就是分布式系統下的事務了,

此時ACID難以滿足,這是分布式事務要解決的問題

2. 分布式事務解決理論

解決分布式事務問題,需要一些分布式系統的基礎知識作為理論指導,

2.1.CAP定理

CAP定理是說在P一定會出現的情況下,A和C之間只能實作一個

1998年,加州大學的計算機科學家 Eric Brewer 提出,分布式系統有三個指標,

  • Consistency(一致性)
  • Availability(可用性)
  • Partition tolerance (磁區容錯性)

image

它們的第一個字母分別是 C、A、P,

Eric Brewer 說,這三個指標不可能同時做到,這個結論就叫做 CAP 定理,

2.1.1. C:一致性

Consistency(一致性):用戶訪問分布式系統中的任意節點,得到的資料必須一致,

比如現在包含兩個節點,其中的初始資料是一致的;當我們修改其中一個節點的資料時,兩者的資料產生了差異;要想保住一致性,就必須實作node01 到 node02的資料 同步:

image

2.1.2. A:可用性

Availability (可用性):用戶訪問集群中的任意健康節點,必須能得到回應,而不是超時或拒絕,

如圖,有三個節點的集群,訪問任何一個都可以及時得到回應:

image

當有部分節點因為網路故障或其它原因無法訪問時,代表節點不可用:

image

2.1.3. P:磁區容錯

Partition(磁區):因為網路故障或其它原因導致分布式系統中的部分節點與其它節點失去連接,形成獨立磁區,

image

Tolerance(容錯):在集群出現磁區時,整個系統也要持續對外提供服務

2.1.4. 矛盾

在分布式系統中,系統間的網路不能100%保證健康,一定會有故障的時候,而服務有必須對外保證服務,因此Partition Tolerance不可避免,

當節點接收到新的資料變更時,就會出現問題了:

image

如果此時要保證一致性,就必須等待網路恢復,完成資料同步后,整個集群才對外提供服務,服務處于阻塞狀態,不可用,

如果此時要保證可用性,就不能等待網路恢復,那node01、node02與node03之間就會出現資料不一致,

也就是說,在P一定會出現的情況下,A和C之間只能實作一個,

2.2.BASE理論

BASE理論是對CAP的一種解決思路,包含三個思想:

  • Basically Available (基本可用):分布式系統在出現故障時,允許損失部分可用性,即保證核心可用,
  • Soft State(軟狀態):在一定時間內,允許出現中間狀態,比如臨時的不一致狀態,
  • Eventually Consistent(最終一致性):雖然無法保證強一致性,但是在軟狀態結束后,最終達到資料一致,

2.3.解決分布式事務的思路

分布式事務最大的問題是各個子事務的一致性問題,因此可以借鑒CAP定理和BASE理論,有兩種解決思路:

  • AP模式:各子事務分別執行和提交,允許出現結果不一致,然后采用彌補措施恢復資料即可,實作最終一致,

  • CP模式:各個子事務執行后互相等待,同時提交,同時回滾,達成強一致,但事務等待程序中,處于弱可用狀態,

小知識:ES就是使用了AP模式,當有節點宕機時就會斷開此節點,并將之前在此節點備份好的資料錄入其他節點

但不管是哪一種模式,都需要在子系統事務之間互相通訊,協調事務狀態,也就是需要一個事務協調者(TC)

image

這里的子系統事務,稱為分支事務;有關聯的各個分支事務在一起稱為全域事務

Seata

Seata是 2019 年 1 月份螞蟻金服和阿里巴巴共同開源的分布式事務解決方案,致力于提供高性能和簡單易用的分布式事務服務,為用戶打造一站式的分布式解決方案,

官網地址:http://seata.io/,其中的檔案、播客中提供了大量的使用說明、原始碼分析,

image

1. Seata的架構

Seata事務管理中有三個重要的角色:

  • TC (Transaction Coordinator) - 事務協調者:維護全域和分支事務的狀態,協調全域事務提交或回滾,

  • TM (Transaction Manager) - 事務管理器:定義全域事務的范圍、開始全域事務、提交或回滾全域事務,

  • RM (Resource Manager) - 資源管理器:管理分支事務處理的資源,與TC交談以注冊分支事務和報告分支事務的狀態,并驅動分支事務提交或回滾,

整體的架構如圖:

image

Seata基于上述架構提供了四種不同的分布式事務解決方案:

  • XA模式:強一致性分階段事務模式,犧牲了一定的可用性,無業務侵入
  • AT模式:最終一致的分階段事務模式,無業務侵入,也是Seata的默認模式
  • TCC模式:最終一致的分階段事務模式,有業務侵入
  • SAGA模式:長事務模式,有業務侵入

無論哪種方案,都離不開TC,也就是事務的協調者,

2. 部署TC服務

就是把Seata注冊到nacos中

2.1 下載

首先我們要下載seata-server包,地址在http??/seata.io/zh-cn/blog/download.html

當然,課前資料也準備好了:

image

2.2 解壓

在非中文目錄解壓縮這個zip包,其目錄結構如下:

image

2.3 修改配置

修改conf目錄下的registry.conf檔案:

image

內容如下:

registry {
  # tc服務的注冊中心類,這里選擇nacos,也可以是eureka、zookeeper等
  type = "nacos"

  nacos {
    # seata tc 服務注冊到 nacos的服務名稱,可以自定義
    application = "seata-tc-server"
    serverAddr = "127.0.0.1:8848"
    group = "DEFAULT_GROUP"
    namespace = ""
    cluster = "SH"
    username = "nacos"
    password = "nacos"
  }
}

config {
  # 讀取tc服務端的組態檔的方式,這里是從nacos配置中心讀取,這樣如果tc是集群,可以共享配置
  type = "nacos"
  # 配置nacos地址等資訊
  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
  }
}

2.4 在nacos添加配置

特別注意,為了讓tc服務的集群可以共享配置,我們選擇了nacos作為統一配置中心,因此服務端組態檔seataServer.properties檔案需要在nacos中配好,

格式如下:

image

配置內容如下:

# 資料存盤方式,db代表資料庫
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
# 事務、日志等配置
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000

# 客戶端與服務端傳輸方式
transport.serialization=seata
transport.compressor=none
# 關閉metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

其中的資料庫地址、用戶名、密碼都需要修改成你自己的資料庫資訊

2.5 創建資料庫表

特別注意:tc服務在管理分布式事務時,需要記錄事務相關資料到資料庫中,你需要提前創建好這些表,

新建一個名為seata的資料庫,運行課前資料提供的sql檔案:

image

這些表主要記錄全域事務、分支事務、全域鎖資訊:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- 分支事務表
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table`  (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `resource_group_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `branch_type` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` tinyint(4) NULL DEFAULT NULL,
  `client_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime(6) NULL DEFAULT NULL,
  `gmt_modified` datetime(6) NULL DEFAULT NULL,
  PRIMARY KEY (`branch_id`) USING BTREE,
  INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- 全域事務表
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table`  (
  `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_service_group` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `timeout` int(11) NULL DEFAULT NULL,
  `begin_time` bigint(20) NULL DEFAULT NULL,
  `application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime NULL DEFAULT NULL,
  `gmt_modified` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`xid`) USING BTREE,
  INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
  INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

2.6 啟動TC服務

進入bin目錄,運行其中的seata-server.bat即可:

image

啟動成功后,seata-server應該已經注冊到nacos注冊中心了,

打開瀏覽器,訪問nacos地址:http://localhost:8848,然后進入服務串列頁面,可以看到seata-tc-server的資訊:

image

3. 微服務集成Seata

以order-service為例 【每個微服務要集成seata都需要如下操作】

3.1. 引入依賴

首先,在order-service中引入依賴:

<!--seata-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <!--版本較低,1.3.0,因此排除--> 
        <exclusion>
            <artifactId>seata-spring-boot-starter</artifactId>
            <groupId>io.seata</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <!--seata starter 采用1.4.2版本  下面是有父模板指定為1.4.2了,所以才寫成${seata.version}-->
    <version>${seata.version}</version>
</dependency>

3.2 配置TC地址

在order-service中的application.yml中,配置TC服務資訊,通過注冊中心nacos,結合服務名稱獲取TC地址:

seata:
  registry: # TC服務注冊中心的配置,微服務根據這些資訊去注冊中心獲取tc服務地址
    type: nacos # 注冊中心型別 nacos
    nacos:
      server-addr: 127.0.0.1:8848 # nacos地址
      namespace: "" # namespace,默認為空
      group: DEFAULT_GROUP # 分組,默認是DEFAULT_GROUP
      application: seata-tc-server # seata服務名稱
      username: nacos
      password: nacos
  tx-service-group: seata-demo # 事務組名稱
  service:
    vgroup-mapping: # 事務組與cluster的映射關系
      seata-demo: SH

微服務如何根據這些配置尋找TC的地址呢?

我們知道注冊到Nacos中的微服務,確定一個具體實體需要四個資訊:

  • namespace:命名空間
  • group:分組
  • application:服務名
  • cluster:集群名

以上四個資訊,在剛才的yaml檔案中都能找到:

image

namespace為空,就是默認的public

結合起來,TC服務的資訊就是:public@DEFAULT_GROUP@seata-tc-server@SH,這樣就能確定TC服務集群了,然后就可以去Nacos拉取對應的實體資訊了,

4. 四大事務模式

4.1.XA模式

少用,因為性能太差了

XA 規范 是 X/Open 組織定義的分布式事務處理(DTP,Distributed Transaction Processing)標準,XA 規范描述了全域的TM與區域的RM之間的介面,幾乎所有主流的資料庫都對 XA 規范提供了支持,

4.1.1.兩階段提交

XA是規范,目前主流資料庫都實作了這種規范,實作的原理都是基于兩階段提交,

正常情況:

image

例外情況:

image

一階段:

  • 事務協調者通知每個事物參與者執行本地事務
  • 本地事務執行完成后報告事務執行狀態給事務協調者,此時事務不提交,繼續持有資料庫鎖

二階段:

  • 事務協調者基于一階段的報告來判斷下一步操作
    • 如果一階段都成功,則通知所有事務參與者,提交事務
    • 如果一階段任意一個參與者失敗,則通知所有事務參與者回滾事務
4.1.2.Seata的XA模型

Seata對原始的XA模式做了簡單的封裝和改造,以適應自己的事務模型,基本架構如圖:

image

RM一階段的作業:

? ① 注冊分支事務到TC

? ② 執行分支業務sql但不提交

? ③ 報告執行狀態到TC

TC二階段的作業:

  • TC檢測各分支事務執行狀態

    a.如果都成功,通知所有RM提交事務

    b.如果有失敗,通知所有RM回滾事務

RM二階段的作業:

  • 接收TC指令,提交或回滾事務
4.1.3.優缺點

XA模式的優點是什么?

  • 事務的強一致性,滿足ACID原則,
  • 常用資料庫都支持,實作簡單,并且沒有代碼侵入

XA模式的缺點是什么?

  • 因為一階段需要鎖定資料庫資源,等待二階段結束才釋放,性能較差
  • 依賴關系型資料庫實作事務
4.1.4.實作XA模式

Seata的starter已經完成了XA模式的自動裝配,實作非常簡單,步驟如下:

1)修改application.yml檔案(每個參與事務的微服務),開啟XA模式:

seata:
  data-source-proxy-mode: XA

2)給發起全域事務的入口方法添加@GlobalTransactional注解:

本例中是OrderServiceImpl中的create方法.

image

4.2.AT模式

最常用的一種,因為無代碼只需簡單配置,且性能還行

AT模式同樣是分階段提交的事務模型,不過缺彌補了XA模型中資源鎖定周期過長的缺陷,

4.2.1.Seata的AT模型

基本流程圖:

image

階段一RM的作業:

  • 注冊分支事務
  • 記錄undo-log(資料快照)
  • 執行業務sql并提交
  • 報告事務狀態

階段二提交時RM的作業:

  • 洗掉undo-log即可

階段二回滾時RM的作業:

  • 根據undo-log恢復資料到更新前
4.2.2.流程梳理

我們用一個真實的業務來梳理下AT模式的原理,

比如,現在又一個資料庫表,記錄用戶余額:

id money
1 100

其中一個分支業務要執行的SQL為:

update tb_account set money = money - 10 where id = 1

AT模式下,當前分支事務執行流程如下:

一階段:

1)TM發起并注冊全域事務到TC

2)TM呼叫分支事務

3)分支事務準備執行業務SQL

4)RM攔截業務SQL,根據where條件查詢原始資料,形成快照,

{
    "id": 1, "money": 100
}

5)RM執行業務SQL,提交本地事務,釋放資料庫鎖,此時 money = 90

6)RM報告本地事務狀態給TC

二階段:

1)TM通知TC事務結束

2)TC檢查分支事務狀態

? a)如果都成功,則立即洗掉快照

? b)如果有分支事務失敗,需要回滾,讀取快照資料({"id": 1, "money": 100})將快斬訓復到資料庫,此時資料庫恢復為100

流程圖:

image

4.2.3.AT與XA的區別

簡述AT模式與XA模式最大的區別是什么?

XA是將資料庫進行鎖定占用整個資料庫dp鎖資源直到兩階段結束,而AT是在兩階段間用全域鎖來鎖單張表(甚至可以鎖單行資料)

  • XA模式一階段不提交事務,鎖定資源;AT模式一階段直接提交,不鎖定資源,
  • XA模式依賴資料庫機制實作回滾;AT模式利用資料快照實作資料回滾,
  • XA模式強一致;AT模式最終一致
4.2.4.臟寫問題

在多執行緒并發訪問AT模式的分布式事務時,有可能出現臟寫問題,如圖:

image

解決思路就是引入了全域鎖的概念,在釋放DB鎖之前,先拿到全域鎖,避免同一時刻有另外一個事務來操作當前資料,

image

4.2.5.優缺點

AT模式的優點:

  • 一階段完成直接提交事務,釋放資料庫資源,性能比較好
  • 利用全域鎖實作讀寫隔離
  • 沒有代碼侵入,框架自動完成回滾和提交

AT模式的缺點:

  • 兩階段之間屬于軟狀態,屬于最終一致
  • 框架的快照功能會影響性能,但比XA模式要好很多
4.2.6.實作AT模式

AT模式中的快照生成、回滾等動作都是由框架自動完成,沒有任何代碼侵入,因此實作非常簡單,

只不過,AT模式需要一個表來記錄全域鎖、另一張表來記錄資料快照undo_log,

1)匯入資料庫表,記錄全域鎖

匯入課前資料提供的Sql檔案:seata-at.sql,其中lock_table匯入到TC服務關聯的資料庫,undo_log表匯入到微服務關聯的資料庫:

image

2)修改application.yml檔案,將事務模式修改為AT模式即可:

seata:
  data-source-proxy-mode: AT # 默認就是AT

3)給發起全域事務的入口方法添加@GlobalTransactional注解:

這個注解一般是加在最外層的微服務方法(該方法里在遠程呼叫其他服務的方法)

本例中是OrderServiceImpl中的create方法.

image

4.3.TCC模式

在大廠中常用,因為性能最高,但代碼復雜度高

TCC模式與AT模式非常相似,每階段都是獨立事務,不同的是TCC通過人工編碼來實作資料恢復,需要實作三個方法:

  • Try:資源的檢測和預留;

  • Confirm:完成資源操作業務;要求 Try 成功 Confirm 一定要能成功,

  • Cancel:預留資源釋放,可以理解為try的反向操作,

4.3.1.流程分析

階段一Try經歷后,階段二只會經過一個Confirm 或者 Canncel

舉例,一個扣減用戶余額的業務,假設賬戶A原來余額是100,需要余額扣減30元,

  • 階段一( Try ):檢查余額是否充足,如果充足則凍結金額增加30元,可用余額扣除30

初識余額:

image

余額充足,可以凍結:

image

此時,總金額 = 凍結金額 + 可用金額,數量依然是100不變,事務直接提交無需等待其它事務,

  • 階段二(Confirm):假如要提交(Confirm),則凍結金額扣減30

確認可以提交,不過之前可用金額已經扣減過了,這里只要清除凍結金額就好了:

image

此時,總金額 = 凍結金額 + 可用金額 = 0 + 70 = 70元

  • 階段二(Canncel):如果要回滾(Cancel),則凍結金額扣減30,可用余額增加30

需要回滾,那么就要釋放凍結金額,恢復可用金額:

image

4.3.2.Seata的TCC模型

Seata中的TCC模型依然延續之前的事務架構,如圖:

image

4.3.3.優缺點

TCC模式的每個階段是做什么的?

  • Try:資源檢查和預留
  • Confirm:業務執行和提交
  • Cancel:預留資源的釋放

TCC的優點是什么?

  • 一階段完成直接提交事務,釋放資料庫資源,性能好
  • 相比AT模型,無需生成快照,無需使用全域鎖,性能最強
  • 不依賴資料庫事務,而是依賴補償操作,可以用于非事務型資料庫

TCC的缺點是什么?

  • 有代碼侵入,需要人為撰寫try、Confirm和Cancel介面,太麻煩
  • 軟狀態,事務是最終一致
  • 需要考慮Confirm和Cancel的失敗情況,做好冪等處理
4.3.4.事務懸掛和慷訓滾
1)慷訓滾

當某分支事務的try階段阻塞時,可能導致全域事務超時而觸發二階段的cancel操作,在未執行try操作時先執行了cancel操作,這時cancel不能做回滾,就是慷訓滾

如圖:

image

執行cancel操作時,應當判斷try是否已經執行,如果尚未執行,則應該慷訓滾,

2)業務懸掛

對于已經慷訓滾的業務,之前被阻塞的try操作恢復,繼續執行try,就永遠不可能confirm或cancel ,事務一直處于中間狀態,這就是業務懸掛

執行try操作時,應當判斷cancel是否已經執行過了,如果已經執行,應當阻止慷訓滾后的try操作,避免懸掛

4.3.5.實作TCC模式

解決慷訓滾和業務懸掛問題,必須要記錄當前事務狀態,是在try、還是cancel?

1)思路分析

這里我們定義一張表:

CREATE TABLE `account_freeze_tbl` (
  `xid` varchar(128) NOT NULL,
  `user_id` varchar(255) DEFAULT NULL COMMENT '用戶id',
  `freeze_money` int(11) unsigned DEFAULT '0' COMMENT '凍結金額',
  `state` int(1) DEFAULT NULL COMMENT '事務狀態,0:try,1:confirm,2:cancel',
  PRIMARY KEY (`xid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

其中:

  • xid:是全域事務id
  • freeze_money:用來記錄用戶凍結金額
  • state:用來記錄事務狀態

那此時,我們的業務開怎么做呢?

  • Try業務:
    • 記錄凍結金額和事務狀態到account_freeze表
    • 扣減account表可用金額
  • Confirm業務
    • 根據xid洗掉account_freeze表的凍結記錄
  • Cancel業務
    • 修改account_freeze表,凍結金額為0,state為2
    • 修改account表,恢復可用金額
  • 如何判斷是否慷訓滾?
    • cancel業務中,根據xid查詢account_freeze,如果為null則說明try還沒做,需要慷訓滾
  • 如何避免業務懸掛?
    • try業務中,根據xid查詢account_freeze ,如果已經存在則證明Cancel已經執行,拒絕執行try業務

接下來,我們改造account-service,利用TCC實作余額扣減功能,

2)宣告TCC介面

TCC的Try、Confirm、Cancel方法都需要在介面中基于注解來宣告,

我們在account-service專案中的cn.itcast.account.service包中新建一個介面,宣告TCC三個介面:

package cn.itcast.account.service;

import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

@LocalTCC
public interface AccountTCCService {

    @TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
    void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
                @BusinessActionContextParameter(paramName = "money")int money);

    boolean confirm(BusinessActionContext ctx);

    boolean cancel(BusinessActionContext ctx);
}
3)撰寫實作類

在account-service服務中的cn.itcast.account.service.impl包下新建一個類,實作TCC業務:

記得要在Try資源預留的方法前加 事務@Transactional

package cn.itcast.account.service.impl;

import cn.itcast.account.entity.AccountFreeze;
import cn.itcast.account.mapper.AccountFreezeMapper;
import cn.itcast.account.mapper.AccountMapper;
import cn.itcast.account.service.AccountTCCService;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Slf4j
public class AccountTCCServiceImpl implements AccountTCCService {

    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private AccountFreezeMapper freezeMapper;

    @Override
    @Transactional
    public void deduct(String userId, int money) {
        // 0.獲取事務id
        String xid = RootContext.getXID();
        // 1.扣減可用余額
        accountMapper.deduct(userId, money);
        // 2.記錄凍結金額,事務狀態
        AccountFreeze freeze = new AccountFreeze();
        freeze.setUserId(userId);
        freeze.setFreezeMoney(money);
        freeze.setState(AccountFreeze.State.TRY);
        freeze.setXid(xid);
        freezeMapper.insert(freeze);
    }

    @Override
    public boolean confirm(BusinessActionContext ctx) {
        // 1.獲取事務id
        String xid = ctx.getXid();
        // 2.根據id洗掉凍結記錄
        int count = freezeMapper.deleteById(xid);
        return count == 1;
    }

    @Override
    public boolean cancel(BusinessActionContext ctx) {
        // 0.查詢凍結記錄
        String xid = ctx.getXid();
        AccountFreeze freeze = freezeMapper.selectById(xid);

        // 1.恢復可用余額
        accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());
        // 2.將凍結金額清零,狀態改為CANCEL
        freeze.setFreezeMoney(0);
        freeze.setState(AccountFreeze.State.CANCEL);
        int count = freezeMapper.updateById(freeze);
        return count == 1;
    }
}

4.4 SAGA模式

平時中也很少用,一般是在處理之前遺留的業務代碼時使用

Saga 模式是 Seata 即將開源的長事務解決方案,將由螞蟻金服主要貢獻,

其理論基礎是Hector & Kenneth 在1987年發表的論文Sagas,

Seata官網對于Saga的指南:https://seata.io/zh-cn/docs/user/saga.html

4.4.1 原理

在 Saga 模式下,分布式事務內有多個參與者,每一個參與者都是一個沖正補償服務,需要用戶根據業務場景實作其正向操作和逆向回滾操作,

分布式事務執行程序中,依次執行各參與者的正向操作,如果所有正向操作均執行成功,那么分布式事務提交,如果任何一個正向操作執行失敗,那么分布式事務會去退回去執行前面各參與者的逆向回滾操作,回滾已提交的參與者,使分布式事務回到初始狀態,

image

Saga也分為兩個階段:

  • 一階段:直接提交本地事務
  • 二階段:成功則什么都不做;失敗則通過撰寫補償業務來回滾
4.4.2.優缺點

優點:

  • 事務參與者可以基于事件驅動實作異步呼叫,吞吐高
  • 一階段直接提交事務,無鎖,性能好
  • 不用撰寫TCC中的三個階段,實作簡單

缺點:

  • 軟狀態持續時間不確定,時效性差
  • 沒有鎖,沒有事務隔離,會有臟寫

4.5.四種模式對比

我們從以下幾個方面來對比四種實作:

  • 一致性:能否保證事務的一致性?強一致還是最終一致?
  • 隔離性:事務之間的隔離性如何?
  • 代碼侵入:是否需要對業務代碼改造?
  • 性能:有無性能損耗?
  • 場景:常見的業務場景

image

5. TC服務集群

Seata的TC服務作為分布式事務核心,一定要保證集群的高可用性,

5.1.高可用架構模型

搭建TC服務集群非常簡單,啟動多個TC服務,注冊到nacos即可,

但集群并不能確保100%安全,萬一集群所在機房故障怎么辦?所以如果要求較高,一般都會做異地多機房容災,

比如一個TC集群在上海,另一個TC集群在杭州:

image

微服務基于事務組(tx-service-group)與TC集群的映射關系,來查找當前應該使用哪個TC集群,當SH集群故障時,只需要將vgroup-mapping中的映射關系改成HZ,則所有微服務就會切換到HZ的TC集群了,

5.2.實作高可用

TC服務的高可用和異地容災

5.2.1 模擬異地容災的TC集群

計劃啟動兩臺seata的tc服務節點:

節點名稱 ip地址 埠號 集群名稱
seata 127.0.0.1 8091 SH
seata2 127.0.0.1 8092 HZ

之前我們已經啟動了一臺seata服務,埠是8091,集群名為SH,

現在,將seata目錄復制一份,起名為seata2

修改組態檔seata2/conf/registry.conf內容如下:

registry {
  # tc服務的注冊中心類,這里選擇nacos,也可以是eureka、zookeeper等
  type = "nacos"

  nacos {
    # seata tc 服務注冊到 nacos的服務名稱,可以自定義
    application = "seata-tc-server"
    serverAddr = "127.0.0.1:8848"
    group = "DEFAULT_GROUP"
    namespace = ""
    cluster = "HZ"
    username = "nacos"
    password = "nacos"
  }
}

config {
  # 讀取tc服務端的組態檔的方式,這里是從nacos配置中心讀取,這樣如果tc是集群,可以共享配置
  type = "nacos"
  # 配置nacos地址等資訊
  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
  }
}

進入seata2/bin目錄,然后運行命令:

seata-server.bat -p 8092

打開nacos控制臺,查看服務串列:

image

點進詳情查看:

image

5.2.2 將事務組映射配置到nacos

接下來,我們需要將tx-service-group與cluster的映射關系都配置到nacos配置中心,

新建一個配置:

image

配置的內容如下:

# 事務組映射關系
service.vgroupMapping.seata-demo=SH

service.enableDegrade=false
service.disableGlobalTransaction=false
# 與TC服務的通信配置
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
# RM配置
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
# TM配置
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000

# undo日志配置
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
client.log.exceptionRate=100
5.2.3 微服務讀取nacos配置

接下來,需要修改每一個微服務的application.yml檔案,讓微服務讀取nacos中的client.properties檔案:

seata:
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      username: nacos
      password: nacos
      group: SEATA_GROUP
      data-id: client.properties

重啟微服務,現在微服務到底是連接tc的SH集群,還是tc的HZ集群,都統一由nacos的client.properties來決定了,

本文來自博客園,作者:不吃紫菜,遵循CC 4.0 BY-SA著作權協議,轉載請附上原文出處鏈接:https://www.cnblogs.com/buchizicai/p/17093759.html及本宣告,

本文著作權歸作者所有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/543868.html

標籤:其他

上一篇:Python pydot與graphviz庫在Anaconda環境的配置

下一篇:大數處理方案

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more