Spring Cloud實戰 | 第十篇 :Spring Cloud + Nacos整合Seata 1.4.1最新版本實現微服務架構中的分佈式事務,進階之路必須要邁過的檻

Seata分佈式事務在線體驗地址: www.youlai.store

本篇完整源碼地址:https://github.com/hxrui/youlai-mall

有想加入開源項目開發的童鞋也可以聯繫我(微信號:haoxianrui),希望大家能夠一起交流學習。覺得項目對你有幫助希望能給一個star或者關注,持續更新中。。。

一. 前言

相信瞭解過開源項目 youlai-mall 的童鞋應該知道該項目主要是基於Spring Cloud + Vue等當前最新最主流技術落地實現的一套微服務架構 + 前後端分離的全棧商城系統(App、微信小程序等)。

往期文章鏈接:

微服務

  1. Spring Cloud實戰 | 第一篇:Windows搭建Nacos服務
  2. Spring Cloud實戰 | 第二篇:Spring Cloud整合Nacos實現註冊中心
  3. Spring Cloud實戰 | 第三篇:Spring Cloud整合Nacos實現配置中心
  4. Spring Cloud實戰 | 第四篇:Spring Cloud整合Gateway實現API網關
  5. Spring Cloud實戰 | 第五篇:Spring Cloud整合OpenFeign實現微服務之間的調用
  6. Spring Cloud實戰 | 第六篇:Spring Cloud Gateway+Spring Security OAuth2+JWT實現微服務統一認證授權
  7. Spring Cloud實戰 | 最七篇:Spring Cloud Gateway+Spring Security OAuth2集成統一認證授權平臺下實現註銷使JWT失效方案
  8. Spring Cloud實戰 | 最八篇:Spring Cloud +Spring Security OAuth2+ Vue前後端分離模式下無感知刷新實現JWT續期
  9. Spring Cloud實戰 | 最九篇:Spring Security OAuth2認證服務器統一認證自定義異常處理

管理前端

  1. vue-element-admin實戰 | 第一篇: 移除mock接入後臺,搭建有來商城youlai-mall前後端分離管理平臺
  2. vue-element-admin實戰 | 第二篇: 最小改動接入後臺實現根據權限動態加載菜單

微信小程序

  1. vue+uniapp商城實戰 | 第一篇:【有來小店】微信小程序快速開發接入Spring Cloud OAuth2認證中心完成授權登錄

部署篇

  1. Docker實戰 | 第二篇:IDEA集成Docker插件實現一鍵自動打包部署微服務項目,一勞永逸的技術手段值得一試

  2. Docker實戰 | 第三篇:Docker安裝Nginx,實現基於vue-element-admin框架構建的項目線上部署

說到微服務,自然就少不了保證服務之間數據一致性的分佈式事務,所以本篇就以Seata的AT模式如何在微服務的實際場景中應用進行實戰說明,希望大家都能有個看其形知其意的效果。

1. 需求描述

會員提交訂單,扣減商品庫存,增加會員積分,完成前面步驟,更改訂單狀態爲已完成。

根據需求可知這其中牽涉到訂單、商品、會員3個微服務,分別對應 youlai-mall 商城項目的 mall-oms、mall-pms、mall-ums微服務。

2. 技術版本

技術 版本 說明
Spring Cloud Hoxton.SR9 微服務架構
Nacos 1.4.0 註冊、配置中心
Seata 1.4.1 分佈式事務

3. 環境準備

3.1 Nacos安裝和配置

https://www.cnblogs.com/haoxianrui/p/14059009.html

進入Nacos控制檯,創建seata命名空間

記住命名空間ID自定義爲seata_namespace_id,後面需要

3.2 Seata數據庫創建

創建數據庫名爲seata,執行Seata的Github官方源碼中提供的的MySQL數據庫腳本

MySQL腳本地址:
https://github.com/seata/seata/blob/1.4.1/script/server/db/mysql.sql

二. seata-server安裝

點擊 Docker Hub鏈接 查看最新Seata版本

可以看到最新版本爲1.4.1版本,複製指令獲取最新版本鏡像

docker pull seataio/seata-server:1.4.1

啓動臨時容器

docker run -d --name seata -p 8091:8091 seataio/seata-server

從臨時容器獲取到 registry.conf 配置文件

mkdir /opt/seata
docker cp seata:/seata-server/resources/registry.conf  /opt/seata

修改registry.conf配置,類型選擇nacos,namesapce爲上文中在nacos新建的命名空間id即seata_namespace_id,精簡如下:

vim /opt/seata/registry.conf
registry {
  type = "nacos"
  loadBalance = "RandomLoadBalance"
  loadBalanceVirtualNodes = 10

  nacos {
    application = "seata-server"
    serverAddr = "localhost:8848"
    namespace = "seata_namespace_id"
    cluster = "default"
  }
}
config {
  type = "nacos"
  
  nacos {
    serverAddr = "localhost:8848"
    namespace = "seata_namespace_id"
    group = "SEATA_GROUP"
  }
}

安排好 registry.conf 之後,刪除臨時容器

docker rm -f seata

接下來着手開始推送Seata依賴配置至Nacos

從Seata的GitHub官方源碼獲取配置文件(config.txt)和推送腳本文件(nacos/nacos-config.sh)

地址:https://github.com/seata/seata/blob/develop/script/config-center

因爲腳本的關係,文件存放目錄如下

/opt/seata
├── config.txt
└── nacos
    └── nacos-config.sh

修改配置文件 config.txt

vim /opt/seata/config.txt

修改事務組和MySQL連接信息,修改信息如下:

service.vgroupMapping.mall_tx_group=default

store.mode=db
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://www.youlai.store:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456

執行推送命令

cd /opt/seata/nacos

bash nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t seata_namespace_id -u nacos -w nacos
  • -t seata_namespace_id 指定Nacos配置命名空間ID
  • -g SEATA_GROUP 指定Nacos配置組名稱

如果有 init nacos config fail. 報錯信息,請檢查修改信息,如果有屬性修改提示failure,請修改config.txt中屬性。

如果出現類似 cat: /tmp/tmp.rRGz1B7MUP: No such file or directory 的錯誤不用慌,重新執行推送命令直至成功。

推送執行完畢,到Nacos控制檯查看配置是否已添加成功

做完上述準備工作之後,接下來最後一步:啓動Seata容器

docker run -d --name seata --restart=always -p 8091:8091  \
-e SEATA_IP=localhost \
-e SEATA_CONFIG_NAME=file:/seata-server/resources/registry.conf \
-v /opt/seata/registry.conf:/seata-server/resources/registry.conf \
-v /opt/seata/logs:/root/logs \
seataio/seata-server

三. Seata客戶端

上文完成了Seata服務端應用安裝、添加Seata配置至Nacos配置中心以及註冊Seata到Nacos註冊中心。

接下來的工作就是客戶端的配置,通過相關配置把訂單(mall-oms)、商品(mall-pms)、會員(mall-ums)這3個微服務關聯seata-server。

1. 添加undo_log表

Seata的AT模式下之所以在第一階段直接提交事務,依賴的是需要在每個RM創建一張undo_log表,記錄業務執行前後的數據快照。

如果二階段需要回滾,直接根據undo_log表回滾,如果執行成功,則在第二階段刪除對應的快照數據。

Seata官方Github源碼庫undo_log表腳本地址:

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

注意第一行的註釋說明

-- 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(20)   NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(100) 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 = utf8 COMMENT ='AT transaction mode undo table';

分別在項目的 mall-oms、mall-pms、mall-ums 的三個數據庫執行腳本創建 undo_log

2. 添加依賴

分別爲 youlai-mall 的 mall-oms、mall-pms、mall-ums 微服務添加如下seata客戶端依賴

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

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <!-- 排除依賴 指定版本和服務器端一致 -->
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </exclusion>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>1.4.1</version>
</dependency>

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.4.1</version>
</dependency>
  • 使用Alibaba官方提供的Spring Cloud和Seata整合好的Spring Boot啓動器 spring-cloud-starter-alibaba-seata
  • 需要指定seata版本和服務版本一致,這裏也就是1.4.1

3. yml配置

Seata官方Github源碼庫Spring配置鏈接:

https://github.com/seata/seata/blob/1.4.1/script/client/spring/application.yml

配置精簡如下:

# 分佈式事務配置
seata:
  tx-service-group: mall_tx_group
  enable-auto-data-source-proxy: true
  registry:
    type: nacos
    nacos:
      server-addr: c.youlai.store:8848
      namespace: seata_namespace_id
      group: SEATA_GROUP
  config:
    type: nacos
    nacos:
      server-addr: c.youlai.store:8848
      namespace: seata_namespace_id
      group: SEATA_GROUP
  • tx-service-group: mall_tx_group 配置事務羣組,其中羣組名稱 mall_tx_group 需和服務端的配置 service.vgroupMapping.mall_tx_group=default 一致
  • enable-auto-data-source-proxy: true 自動爲Seata開啓了代理數據源,實現集成對undo_log表操作
  • namespace: seata_namespace_id seata-server一致
  • group: SEATA_GROUP seata-server一致

將精簡的配置分別放置到 mall-oms、mall-pms、mall-ums的配置文件中

4. 啓動類調整

因爲要使用Seata提供的代理數據源,所以在啓動類移除SpringBoot自動默認裝配的數據源

同樣也是需要在3個微服務啓動類分別調整,不然分佈式事務不會生效

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

四. 測試環境模擬

根據上訴步驟完成Seata服務安裝以及客戶端的配置之後

接下來就開始着手 透過現象看本質 的工作,根據業務需求創建業務表和編寫業務代碼

1. 業務表

提供業務表關鍵字段,完整表結構請點擊 youlai-mall

訂單表(oms_order):

CREATE TABLE `oms_order` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
  `status` int NOT NULL DEFAULT '101' COMMENT '訂單狀態【101->待付款;102->用戶取消;103->系統取消;201->已付款;202->申請退款;203->已退款;301->待發貨;401->已發貨;501->用戶收貨;502->系統收貨;901->已完成】',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='訂單表';

庫存表(pms_sku):

CREATE TABLE `pms_sku` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品id',
  `stock` int NOT NULL DEFAULT '0' COMMENT '庫存',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='商品庫存表';

會員表(ums_user):

CREATE TABLE `ums_user` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `point` int DEFAULT '0' COMMENT '會員積分',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='會員信息表';

2. 業務代碼

提供核心業務代碼,完整代碼請點擊 youlai-mall

訂單微服務(mall-oms):

代碼定位:OmsOrderServiceImpl#submit

@Override
@GlobalTransactional(rollbackFor = Exception.class)
public boolean submit() {
    log.info("扣減庫存----begin");
    productFeignService.updateStock(1l, -1);
    log.info("扣減庫存----end");

    log.info("增加積分----begin");
    memberFeignService.updatePoint(1l, 10);
    log.info("增加積分----end");

    log.info("修改訂單狀態----begin");
    boolean result = this.update(new LambdaUpdateWrapper<OmsOrder>().eq(OmsOrder::getId, 1l).set(OmsOrder::getStatus, 901));
    log.info("修改訂單狀態----end");
    return result;
}
  • @GlobalTransactional註解,標識TM(事務管理器)開啓全局事務

商品微服務(mall-pms):

代碼定位:AppSkuController#updateStock

@PutMapping("/{id}/stock")
public Result updateStock(@PathVariable Long id, @RequestParam Integer num) {
    PmsSku sku = iPmsSkuService.getById(id);
    sku.setStock(sku.getStock() + num);
    boolean result = iPmsSkuService.updateById(sku);
    return Result.status(result);
}

會員微服務(mall-ums):

 @PutMapping("/{id}/point")
public Result updatePoint(@PathVariable Long id, @RequestParam Integer num) {
    UmsUser user = iUmsUserService.getById(id);
    user.setPoint(user.getPoint() + num);
    boolean result = iUmsUserService.updateById(user);
    try {
        Thread.sleep(15 * 1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return Result.status(result);
}

Thread.sleep(15 * 1000); 模擬超時異常驗證事務是否能正常回滾

注意15s的設定有講究的

先看下訂單微服務的feign調用配置,

ribbon:
  ReadTimeout: 10000

feign底層使用ribbon做負載均衡和遠程調用,上面設置ribbon的超時時間爲10s

然而在訂單調用會員服務的時候需要至少15s才能獲得結果,顯然會造成接口請求超時的異常,接下來就看事務能不能進行正常回滾。

五. 驗證測試

本篇源碼包括測試用例均已整合到 youlai-mall ,大家有條件的話可以搭建一個本地環境調試一下,項目從無到有的搭建參考項目中的說明文檔。

但如果你想快速驗證Seata分佈式事務和看到效果,ok,滿足你,在項目中添加了一個 實驗室 的菜單,計劃用於技術點測試,也方便給大家提供一個完整的測試環境。

話不多說,看界面效果圖:

看完上圖標註的地方,接下來通過界面來進行分佈式事務測試

首先確定一下前提訂單提交肯定會因爲會員積分服務超時出現異常

  • 關閉事務提交

可以看到在關閉事務提交訂單異常的情況下,庫存和積分更新成功了,然而訂單確更新失敗了

接下來再看下開啓事務提交的結果又會是如何呢?

  • 開啓事務提交

更新訂單狀態失敗,因開啓了全局事務,導致已更新的商品庫存、積分被回滾至初始狀態。

六. 結語

以上就Seata分佈式事務結合實際場景應用的案例進行整合和測試,最後可以看到通過Seata實現了微服務調用鏈的最終數據一致性。最後提供了在線體驗實驗室功能模塊,大家可以拉取到本地然後斷點調試以及監聽數據表的數據變化,相信應該會很快掌握Seata的執行流程和實現原理。

最後,覺得項目不錯的話或對你有幫助的話,希望能給個star,持續更新中...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章