Spring Cloud實戰 | 第十篇 :Spring Cloud + Seata 1.4.1 + Nacos1.4.0 整合實現微服務架構中的分散式事務
- 2021 年 1 月 15 日
- 筆記
- Spring Cloud
Seata分散式事務在線體驗地址: www.youlai.store
本篇完整源碼地址://github.com/hxrui/youlai-mall
有想加入開源項目開發的童鞋也可以聯繫我(微訊號:haoxianrui),希望大家能夠一起交流學習。覺得項目對你有幫助希望能給一個star或者關注,持續更新中。。。
一. 前言
相信了解過開源項目 youlai-mall 的童鞋應該知道該項目主要是基於Spring Cloud + Vue等當前最新最主流技術落地實現的一套微服務架構 + 前後端分離的全棧商城系統(App、微信小程式等)。
往期文章鏈接:
微服務
- Spring Cloud實戰 | 第一篇:Windows搭建Nacos服務
- Spring Cloud實戰 | 第二篇:Spring Cloud整合Nacos實現註冊中心
- Spring Cloud實戰 | 第三篇:Spring Cloud整合Nacos實現配置中心
- Spring Cloud實戰 | 第四篇:Spring Cloud整合Gateway實現API網關
- Spring Cloud實戰 | 第五篇:Spring Cloud整合OpenFeign實現微服務之間的調用
- Spring Cloud實戰 | 第六篇:Spring Cloud Gateway+Spring Security OAuth2+JWT實現微服務統一認證授權
- Spring Cloud實戰 | 最七篇:Spring Cloud Gateway+Spring Security OAuth2集成統一認證授權平台下實現註銷使JWT失效方案
- Spring Cloud實戰 | 最八篇:Spring Cloud +Spring Security OAuth2+ Vue前後端分離模式下無感知刷新實現JWT續期
- Spring Cloud實戰 | 最九篇:Spring Security OAuth2認證伺服器統一認證自定義異常處理
管理前端
- vue-element-admin實戰 | 第一篇: 移除mock接入後台,搭建有來商城youlai-mall前後端分離管理平台
- vue-element-admin實戰 | 第二篇: 最小改動接入後台實現根據許可權動態載入菜單
微信小程式
部署篇
Docker實戰 | 第二篇:IDEA集成Docker插件實現一鍵自動打包部署微服務項目,一勞永逸的技術手段值得一試
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安裝和配置
//www.cnblogs.com/haoxianrui/p/14059009.html
進入Nacos控制台,創建seata命名空間
記住命名空間ID自定義為seata_namespace_id
,後面需要
3.2 Seata資料庫創建
創建資料庫名為seata
,執行Seata的Github官方源碼中提供的的MySQL資料庫腳本
MySQL腳本地址:
//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 /etc/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 = "c.youlai.store:8848"
namespace = "seata_namespace_id"
cluster = "default"
}
}
config {
type = "nacos"
nacos {
serverAddr = "c.youlai.store:8848"
namespace = "seata_namespace_id"
group = "SEATA_GROUP"
}
}
安排好 registry.conf
之後,刪除臨時容器
docker rm -f seata
接下來著手開始推送Seata依賴配置至Nacos
從Seata的GitHub官方源碼獲取配置文件(config.txt)和推送腳本文件(nacos/nacos-config.sh)
地址://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 c.youlai.store -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=c.youlai.store \
-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表腳本地址:
//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配置鏈接:
//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,持續更新中…