1. 程式人生 > >Spring Cloud實戰 | 第十篇 :Spring Cloud + Seata 1.4.1 + Nacos1.4.0 整合實現微服務架構中逃不掉的話題分散式事務

Spring Cloud實戰 | 第十篇 :Spring Cloud + Seata 1.4.1 + Nacos1.4.0 整合實現微服務架構中逃不掉的話題分散式事務

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,持續更