Spring Cloud實戰 | 第十篇 :Spring Cloud + Seata 1.4.1 + Nacos1.4.0 整合實現微服務架構中逃不掉的話題分散式事務
阿新 • • 發佈:2021-01-15
Seata分散式事務線上體驗地址: [www.youlai.store](http://www.youlai.store)
![](https://i.loli.net/2021/01/14/ACcKBaGte5s7Wy9.png)
本篇完整原始碼地址:https://github.com/hxrui/youlai-mall
有想加入開源專案開發的童鞋也可以聯絡我(微訊號:haoxianrui),希望大家能夠一起交流學習。覺得專案對你有幫助希望能給一個star或者關注,持續更新中。。。
## 一. 前言
相信瞭解過開源專案 [youlai-mall](https://github.com/hxrui/youlai-mall) 的童鞋應該知道該專案主要是基於Spring Cloud + Vue等**當前最新最主流技術**落地實現的一套微服務架構 + 前後端分離的全棧商城系統(App、微信小程式等)。
**往期文章連結:**
> 微服務
1. [Spring Cloud實戰 | 第一篇:Windows搭建Nacos服務 ](https://www.cnblogs.com/haoxianrui/p/13581881.html)
2. [Spring Cloud實戰 | 第二篇:Spring Cloud整合Nacos實現註冊中心](https://www.cnblogs.com/haoxianrui/p/13584204.html)
3. [Spring Cloud實戰 | 第三篇:Spring Cloud整合Nacos實現配置中心](https://www.cnblogs.com/haoxianrui/p/13585125.html)
4. [Spring Cloud實戰 | 第四篇:Spring Cloud整合Gateway實現API閘道器](https://www.cnblogs.com/haoxianrui/p/13608650.html)
5. [Spring Cloud實戰 | 第五篇:Spring Cloud整合OpenFeign實現微服務之間的呼叫](https://www.cnblogs.com/haoxianrui/p/13615592.html)
6. [Spring Cloud實戰 | 第六篇:Spring Cloud Gateway+Spring Security OAuth2+JWT實現微服務統一認證授權](https://www.cnblogs.com/haoxianrui/p/13719356.html)
7. [Spring Cloud實戰 | 最七篇:Spring Cloud Gateway+Spring Security OAuth2整合統一認證授權平臺下實現登出使JWT失效方案](https://www.cnblogs.com/haoxianrui/p/13740264.html)
8. [Spring Cloud實戰 | 最八篇:Spring Cloud +Spring Security OAuth2+ Vue前後端分離模式下無感知重新整理實現JWT續期](https://www.cnblogs.com/haoxianrui/p/14022632.html)
9. [Spring Cloud實戰 | 最九篇:Spring Security OAuth2認證伺服器統一認證自定義異常處理](https://www.cnblogs.com/haoxianrui/p/14022632.html)
> 管理前端
1. [vue-element-admin實戰 | 第一篇: 移除mock接入後臺,搭建有來商城youlai-mall前後端分離管理平臺](https://www.cnblogs.com/haoxianrui/p/13624548.html)
2. [vue-element-admin實戰 | 第二篇: 最小改動接入後臺實現根據許可權動態載入選單](https://www.cnblogs.com/haoxianrui/p/13676619.html)
> 微信小程式
1. [vue+uniapp商城實戰 | 第一篇:【有來小店】微信小程式快速開發接入Spring Cloud OAuth2認證中心完成授權登入](https://www.cnblogs.com/haoxianrui/p/13882310.html)
> 部署篇
[Docker實戰 | 第二篇:IDEA整合Docker外掛實現一鍵自動打包部署微服務專案,一勞永逸的技術手段值得一試](https://www.cnblogs.com/haoxianrui/p/14088400.html)
[Docker實戰 | 第三篇:Docker安裝Nginx,實現基於vue-element-admin框架構建的專案線上部署](https://www.cnblogs.com/haoxianrui/p/14091762.html)
說到微服務,自然就少不了保證服務之間資料一致性的分散式事務,所以本篇就以Seata的AT模式如何在微服務的實際場景中應用進行實戰說明,希望大家都能有個看其形知其意的效果。
### 1. 需求描述
會員提交訂單,扣減商品庫存,增加會員積分,完成前面步驟,更改訂單狀態為已完成。
![](https://i.loli.net/2021/01/08/pa8OziyTcYbDg1G.png)
根據需求可知這其中牽涉到訂單、商品、會員3個微服務,分別對應 [youlai-mall](https://github.com/hxrui/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名稱空間
![](https://i.loli.net/2021/01/08/db2eiAvSW7gG85s.png)
記住名稱空間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連結](https://registry.hub.docker.com/r/seataio/seata-server/tags?page=1&ordering=last_updated) 檢視最新Seata版本
![](https://i.loli.net/2021/01/08/PSiyeXL1ZpBKfEQ.png)
可以看到最新版本為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)
地址:https://github.com/seata/seata/blob/develop/script/config-center
因為指令碼的關係,檔案存放目錄如下
```lua
/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控制檯檢視配置是否已新增成功
![](https://i.loli.net/2021/01/09/iuK4jUw1cqeVP97.png)
做完上述準備工作之後,接下來最後一步:啟動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表指令碼地址:
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](https://github.com/hxrui/youlai-mall) 的 mall-oms、mall-pms、mall-ums 微服務新增如下seata客戶端依賴
```
io.seata
seata-spring-boot-starter
1.4.0
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
io.seata
seata-all
io.seata
seata-spring-boot-starter
io.seata
seata-all
1.4.1
io.seata
seata-spring-boot-starter
1.4.1
```
- 使用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](https://github.com/hxrui/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](https://github.com/hxrui/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().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](https://github.com/hxrui/youlai-mall) ,大家有條件的話可以搭建一個本地環境除錯一下,專案從無到有的搭建參考專案中的說明文件。
但如果你想快速驗證Seata分散式事務和看到效果,ok,滿足你,在專案中添加了一個 `實驗室` 的選單,計劃用於技術點測試,也方便給大家提供一個完整的測試環境。
話不多說,看介面效果圖:
![](https://i.loli.net/2021/01/10/hsPp7MtZiKluW6Y.png)
看完上圖標註的地方,接下來通過介面來進行分散式事務測試
首先確定一下前提訂單提交肯定會因為會員積分服務超時出現異常
- **關閉事務提交**
![](https://i.loli.net/2021/01/14/vQq9zrPOWwsmdpt.png)
可以看到在關閉事務提交訂單異常的情況下,庫存和積分更新成功了,然而訂單確更新失敗了
接下來再看下開啟事務提交的結果又會是如何呢?
- **開啟事務提交**
![](https://i.loli.net/2021/01/10/qtZ2FX5DWM8VA4O.png)
更新訂單狀態失敗,因開啟了全域性事務,導致已更新的商品庫存、積分被回滾至初始狀態。
## 六. 結語
以上就Seata分散式事務結合實際場景應用的案例進行整合和測試,最後可以看到通過Seata實現了微服務呼叫鏈的最終資料一致性。最後提供了線上體驗實驗室功能模組,大家可以拉取到本地然後斷點除錯以及監聽資料表的資料變化,相信應該會很快掌握Seata的執行流程和實現原理。
最後,覺得專案不錯的話或對你有幫助的話,希望能給個star,持續更