SpringCloud+Seata1.4+Nacos1.4+MySQL8實現分布式事務(客戶端)


1. Seata執行流程

TM開啟分布式事務(TM向TC注冊全局事務記錄)
按業務場景,編排數據庫、服務等事務內資源(RM向TC匯報資源准備狀態)
TM結束分布式事務,事務一階段結束(TM 通知TC提交/回滾分布式事務)
TC匯總事務信息,決定分布式事務是提交還是回滾
TC通知所有RM提交/回滾資源,事務二階段結束

TC (Transaction Coordinator) - 事務協調者
維護全局和分支事務的狀態,驅動全局事務提交或回滾
TM (Transaction Manager) - 事務管理器
定義全局事務的范圍:開始全局事務、提交或回滾全局事務
RM (Resource Manager) - 資源管理器
管理分支事務處理的資源,與TC交談以注冊分支事務和報告分支事務的狀態,並驅動分支事務提交或回滾

2. 搭建Seata1.4+Nacos1.4+MySQL8服務端,不會的可以參考這篇文章

Docker搭建Nacos1.4+Seata1.4+MySQL8分布式事務(服務端)

3. 創建三個微服務,分別為: 訂單服務、賬戶服務、庫存服務。源碼GitHub自取

https://github.com/KiritsEmiya/seata

這里以訂單服務為例

  • application.yml
server:
  port: 2001

spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
      seata:
        #自定義事務組名稱需要與seata-server中的對應
        tx-service-group: fsp_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
        group: "SEATA_GROUP"
        namespace: "seata"
        username: "nacos"
        password: "nacos"
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 這里替換自己的mysql地址、賬戶和密碼
    url: jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    username: root
    password: root

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml
service {
  # 這里fsp_tx_group需要與application.yml文件中tx-service-group: fsp_tx_group保持一致(與seata-server中的對應)
  vgroupMapping.fsp_tx_group = "default"
}
  • registry.conf修改
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa、custom
  type = "nacos"
  loadBalance = "RandomLoadBalance"
  loadBalanceVirtualNodes = 10

  nacos {
    application = "seata-server"
    # 這里Nacos配置替換自己的ip、group、namespace ID
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = "seata"
    username = "nacos"
    password = "nacos"
  }
}

4. 建立三個服務的數據庫

  • 訂單服務數據庫seata_order
CREATE DATABASE seata_order;

CREATE TABLE `t_order`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(0) NULL DEFAULT NULL COMMENT '用戶id',
  `product_id` bigint(0) NULL DEFAULT NULL COMMENT '產品id',
  `count` int(0) NULL DEFAULT NULL COMMENT '數量',
  `money` decimal(11, 0) NULL DEFAULT NULL COMMENT '金額',
  `status` int(0) NULL DEFAULT NULL COMMENT '訂單狀態:0:創建中; 1:已完結',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
id user_id product_id count money status
(N/A) (N/A) (N/A) (N/A) (N/A) (N/A)
  • 賬戶服務數據庫seata_account
CREATE DATABASE seata_account;

CREATE TABLE `t_account`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(11) DEFAULT NULL COMMENT '用戶id',
  `total` decimal(10, 0) DEFAULT NULL COMMENT '總額度',
  `used` decimal(10, 0) DEFAULT NULL COMMENT '已用余額',
  `residue` decimal(10, 0) DEFAULT NULL COMMENT '可用余額',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `t_account` VALUES (1, 1, 1000, 0, 1000);
id user_id total used residue
1 1 1000 0 1000
  • 庫存服務數據庫seata_storage
CREATE DATABASE seata_storage;

CREATE TABLE `t_storage`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `product_id` bigint(0) NULL DEFAULT NULL COMMENT '產品id',
  `total` int(0) NULL DEFAULT NULL COMMENT '總庫存',
  `used` int(0) NULL DEFAULT NULL COMMENT '已用庫存',
  `residue` int(0) NULL DEFAULT NULL COMMENT '剩余庫存',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `t_storage` VALUES (1, 1, 100, 0, 100);
id product_id total used residue
1 1 1000 0 1000

5. 分別在三個服務的數據庫下建立undo_log表,用於回滾,Seata官方GitHub源碼庫sql腳本鏈接

https://github.com/seata/seata/blob/develop/script/client/at/db/mysql.sql

branch_id xid context rollback_info log_status log_created log_modified
(N/A) (N/A) (N/A) (N/A) (N/A) (N/A) (N/A)

6. 運行三個微服務,注冊進Nacos

一個seata服務端,三個seata客戶端

7. 模擬分布式事務問題

  • 訂單服務先不開啟全局事務,注釋掉@GlobalTransactional
    /**
     * 創建訂單->調用庫存服務扣減庫存->調用賬戶服務扣減賬戶余額->修改訂單狀態
     * 簡單說:下訂單->扣庫存->減余額->改狀態
     */
    @Override
    //@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
    public void create(Order order)
    {
        log.info("----->開始新建訂單");
        //1 新建訂單
        orderDao.create(order);

        //2 扣減庫存
        log.info("----->訂單微服務開始調用庫存,做扣減Count");
        storageService.decrease(order.getProductId(),order.getCount());
        log.info("----->訂單微服務開始調用庫存,做扣減end");

        //3 扣減賬戶
        log.info("----->訂單微服務開始調用賬戶,做扣減Money");
        accountService.decrease(order.getUserId(),order.getMoney());
        log.info("----->訂單微服務開始調用賬戶,做扣減end");

        //4 修改訂單狀態,從零到1,1代表已經完成
        log.info("----->修改訂單狀態開始");
        orderDao.update(order.getUserId(),0);
        log.info("----->修改訂單狀態結束");

        log.info("----->下訂單結束了,O(∩_∩)O哈哈~");

    }
  • 賬戶服務模擬超時異常
    /**
     * 扣減賬戶余額
     */
    @Override
    public void decrease(Long userId, BigDecimal money) {
        LOGGER.info("------->account-service中扣減賬戶余額開始");
        //模擬超時異常,全局事務回滾
        //暫停幾秒鍾線程
        try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
        accountDao.decrease(userId,money);
        LOGGER.info("------->account-service中扣減賬戶余額結束");
    }
  • Postman測試
    由於賬戶服務超時,postman連接異常
    url參數的意思是1號用戶購買了10個1號類型的產品,花了100元

order表

id user_id product_id count money status
1 1 1 10 100 0

account表 (由於feign的重試機制,可能第一次查看和第二次查看的結果會有所不同)

id user_id total used residue
1 1 1000 100 900

storage表

id product_id total used residue
1 1 100 10 90

此時可以看到賬戶和庫存都已經減少,訂單已經生成,而訂單狀態卻是未完成,這在生產環境中是很嚴重的問題

8. Navicat手動還原數據,驗證分布式事務

  • 訂單服務開啟全局事務注解@GlobalTransactional
    /**
     * 創建訂單->調用庫存服務扣減庫存->調用賬戶服務扣減賬戶余額->修改訂單狀態
     * 簡單說:下訂單->扣庫存->減余額->改狀態
     */
    @Override
    @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
    public void create(Order order)
  • Postman測試
    還是由於賬戶服務超時,postman連接異常
    開啟全局事務注解時postman調試

order表

id user_id product_id count money status
(N/A) (N/A) (N/A) (N/A) (N/A) (N/A)

account表

id user_id total used residue
1 1 1000 0 1000

storage表

id product_id total used residue
1 1 100 0 100

由上述表數據可知分布式事務驗證通過,同時成功or同時失敗(所有分支事務全都回滾)


免責聲明!

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



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