分散式事務 —— SpringCloud Alibaba Seata

低吟不作語發表於2023-10-06

Seata 簡介

傳統的單體應用中,業務操作使用同一條連線操作不同的資料表,一旦出現異常就可以整體回滾。隨著公司的快速發展、業務需求的變化,單體應用被拆分成微服務應用,原來的單體應用被拆分成多個獨立的微服務,分別使用獨立的資料來源,業務操作需要呼叫三個服務來完成。此時每個服務內部的資料一致性由本地事務來保證,但是全域性的資料一致性問題無法保證。在微服務架構中,一次業務請求需要操作多個資料來源或需要進行遠端呼叫,就會產生分散式事務問題。

Seata 是一款開源的分散式事務解決方案,致力於提供高效能和簡單易用的分散式事務服務,Seata 為使用者提供 AT、TCC、SAGA 和 XA 事務模式,為使用者打造一站式的分散式解決方案。AT 模式是 Seata 預設的工作模式,該模式是 XA 協議的演變。

關於分散式事務的知識可以參考:https://zhuanlan.zhihu.com/p/263555694


Seata 服務端

透過 https://github.com/seata/seata/releases 地址下載 Seata 安裝包,本文使用的 Seata 版本是 seata-server-1.7.1

解壓 seata-server-1.7.1 安裝包,修改 conf/application.conf 檔案

server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: D:\seata-server-1.7.1\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:
    # 使用nacos作為配置中心,seata將從nacos獲取配置
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: 0178e474-2cfb-47c3-bded-da7cfa260f99
      group: springcloud-project
      data-id: seata-server.properties
  registry:
    # 使用nacos作為註冊中心,seata將自身服務註冊到nacos
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: 0178e474-2cfb-47c3-bded-da7cfa260f99
      group: springcloud-project
      application: seata-server
      cluster: default
  store:
    # 儲存模式:
    # file模式為單機模式,全域性事務會話資訊記憶體中讀寫並持久化本地檔案 rootdata,效能較高
    # db模式為高可用模式,全域性事務會話資訊透過db共享,相應效能會差
    # redis模式效能較高,存在事務資訊丟失風險,需要提前配置適合當前場景的redis持久化配置
    mode: file
#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login

在 Nacos 控制檯建立配置檔案 seata-server.properties,注意分組和名稱空間要與上述配置保持一致

具體配置項是從 script/config-center/config.txt 貼上修改而來,這裡只使用對我們有用的配置,主要是資料庫配置資訊

#Transaction storage configuration, only for the server.
store.mode=db
store.lock.mode=db
store.session.mode=db

#These configurations are required if the `store mode` is `db`.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/test_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useAffectedRows=true
store.db.user=root
store.db.password=123
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

在上面配置的資料庫內,執行 script/server/db 目錄下的 sql 指令碼,建立服務端所需的表

完成以後,即可進入 bin 目錄使用指令碼啟動 Seata


Seata 客戶端

為客戶端微服務新增依賴

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

配置檔案新增如下配置

seata:
  enabled: true
  tx-service-group: test-seata-group # 自定義事務組名稱,需要與下面service.vgroup-mapping中的一個對映保持一致
  service:
    vgroup-mapping:
      test-seata-group: default
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8847/nacos
      namespace: 0178e474-2cfb-47c3-bded-da7cfa260f99
      group: springcloud-project
      application: seata-server

Seata 預設使用 AT 模式,該模式需求每個客戶端庫內都存在一張 undo_log 表,用於回滾事務時臨時記錄資料

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,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

在需要使用分散式事務的方法上新增註解 @GlobalTransactional,當方法內發生異常時,就可以帶動所呼叫微服務進行回滾

@GlobalTransactional
public void create(Order order) {

    log.info("下單開始");
    orderDao.create(order);
    log.info("下單結束");

    log.info("開始扣減餘額");
    server02FeignClient.accountIncrUsed(order.getUserId(), order.getMoney());
    log.info("扣減餘額結束");

    log.info("開始扣減庫存");
    server03FeignClient.storageIncrUsed(order.getProductId(), order.getCount());
    log.info("扣減庫存結束");

    log.info("開始修改訂單狀態");
    orderDao.updateStatus(1, order.getId());
    log.info("修改訂單狀態結束");
}

配置完成,啟動服務,即可開始測試


相關文章