Spring Cloud Alibaba Seata + Nacos + Jooq + RestTemplate實現分佈式事務

Spring Cloud Alibaba Seata + Nacos + Jooq + RestTemplate實現分佈式事務

示例代碼

本文所有代碼均可在github克隆使用:clyoudu/spring-cloud-seata-demo

環境

  • macOS 10.14.6(unix/linux)
  • JDK 1.8
  • seata(client) 0.9.0
  • seata server 0.7.1
  • nacos server 1.3.0
  • jooq 3.11.12
  • Spring Cloud Alibaba 2.1.0.RELEASE
  • Spring Cloud Hoxton SR5
  • Spring Boot 2.1.15.RELEASE
  • MySQL 8.0.20

Seata

Seata(Simple Extensiable Autonomous Transaction Architecture,簡單的、可擴展的、自治的事務架構)是一款阿里開源的分佈式事務解決方案,前身叫fescar,致力於提供高性能和簡單易用的分佈式事務服務。Seata 將爲用戶提供了 AT、TCC、SAGA 和 XA 事務模式,爲用戶打造一站式的分佈式解決方案。

github地址:seata/seata
官方網站:seata.io

Nacos

Nacos(Dynamic Naming and Configuration Service)是一個更易於構建雲原生應用的動態服務發現、配置管理和服務管理平臺。Nacos是阿里雲中間件團隊開源的一個項目,簡單理解就是Spring Cloud Eureka+Config Server,但是功能比Spring Cloud Eureka+Config Server更加強大。

github地址:alibaba/nacos
官方網站:nacos.io

JOOQ

JOOQ(Java Object Oriented Querying)是基於Java訪問關係型數據庫的工具包,輕量,簡單,並且足夠靈活,可以輕鬆的使用Java面向對象語法來實現各種複雜的sql。對於寫Java的碼農來說ORMS再也熟悉不過了,不管是Hibernate或者Mybatis,都能簡單的使用實體映射來訪問數據庫。

號稱是The easiest/best way to write SQL in Java,

github地址:jOOQ/jOOQ
官方網站:jooq.org

Getting Started

seata理論上可以和很多註冊中心配合使用,但這裏直接使用和Spring Cloud Alibaba配套的nacos,相對來說不會特別容易出錯,畢竟自家產品。

seta的原理和模式可以到github或官網查看,講的還是比較詳細。

選用JOOQ而不是Mybatis或其他,一是官方示例用的是mybatis + dubbo rpc,我想試試jooq + TestTemplate,加深對整個分佈式事務的理解。

部署Nacos

Nacos和Eureka一樣,有兩種部署方式:源碼和jar包,這裏直接使用jar包部署:
下載nacos:/alibaba/nacos/releases
這裏選擇1.3.0版本:nacos-server-1.3.0.tar.gz
接下來啓動nacos server:

cd /path/to/nacos-server-1.3.0.tar.gz
tar -zvxf nacos-server-1.3.0.tar.gz
cd nacos/bin
sh startup.sh

啓動後稍等一下訪問:http://127.0.0.1:8848/nacos,輸入nacos/nacos即可看到nacos的管理界面。

部署seata server

下載seata server,關於seata server版本爲何要選0.7.1這麼低,現在最新版本已是1.2.0,因爲Spring Cloud Alibaba目前在spring官網上最新版本是2.1.0.RELEASE,其中依賴的seata版本就是0.7.1,因此也選擇了對應版本的server,不過後來發現0.7.1的seata有bug,替換成了0.9.0,這個後面會提到。
下載seata server:seata-server-0.7.1.zip
接下來準備nacos配置:

cd /path/to/seata-server-0.7.1.zip
unzip seata-server-0.7.1.zip
cd seata-server-0.7.1/bin
# 拷貝conf目錄下的nacos-config.sh和nacos-config.txt到bin目錄
cp ../conf/nacos-config.sh	.
cp ../conf/nacos-config.txt .
# 修改nacos-config.txt,添加下面兩行配置到對應位置
# service.default.grouplist=127.0.0.1:8091
# service.disableGlobalTransaction=false
vi nacos-config.txt
# 保存
esc wq
# 同步配置到nacos
sh nacos-config.sh 127.0.0.1
# 最後會輸出
# init nacos config finished, please start seata-server. 
# 如果輸出:init nacos config fail. 請檢查前面的輸出並排查

在這裏插入圖片描述
在這裏插入圖片描述
打開nacos管理界面,會有如下內容:
在這裏插入圖片描述
接着修改配置文件:

vi ../conf/registry.conf
# 修改註冊方式和配置獲取方式,全部修改爲nacos
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
}
config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
}

最後啓動

sh seata-server.sh

稍等一下,nacos註冊中心可以看到seata server已註冊成功:
在這裏插入圖片描述

準備數據庫

創建3個schema:balance_db、order_db、stock_db。
balance_db庫結構和數據如下:

DROP TABLE IF EXISTS `balance_tb`;
CREATE TABLE `balance_tb` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) DEFAULT NULL,
  `balance` decimal(16,2) DEFAULT '0.00',
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_name` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

INSERT INTO `balance_tb` VALUES (1, 'cd083092-b3c4-445d-8e77-76fb927a02fc', 1000000.00);

DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `branch_id` bigint NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

order_db庫結構和數據如下:

DROP TABLE IF EXISTS `order_tb`;
CREATE TABLE `order_tb` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) DEFAULT NULL,
  `product_code` varchar(255) DEFAULT NULL,
  `count` int DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `branch_id` bigint NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

stock_db庫結構和數據如下:

DROP TABLE IF EXISTS `stock_tb`;
CREATE TABLE `stock_tb` (
  `id` int NOT NULL AUTO_INCREMENT,
  `product_code` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `description` text,
  `price` decimal(10,2) DEFAULT '0.00',
  `count` int DEFAULT '0',
  `user_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `id` (`id`,`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;

INSERT INTO `stock_tb` VALUES (21, '00001', '未來架構: 從服務化到雲原生', '{\n  \"description\": \"<div><p>互聯網架構不斷演化,經歷了從集中式架構到分佈式架構,再到雲原生架構的過程。雲原生因能解決傳統應用升級緩慢、架構臃腫、無法快速迭代等問題而成了未來雲端應用的目標。《未來架構:從服務化到雲原生》首先介紹架構演化過程及雲原生的概念,讓讀者對基礎概念有一個準確的瞭解,接着闡述分佈式、服務化、可觀察性、容器調度、Service Mesh、雲數據庫等技術體系及原理,並介紹相關的SkyWalking、Dubbo、Spring Cloud、Kubernetes、Istio等開源解決方案,最後深度揭祕開源分佈式數據庫生態圈ShardingSphere的設計、實現,以及進入Apache基金會的歷程,非常適合架構師、雲計算從業人員閱讀、學習。</p><div><b>張亮</b><p>京東數科數據研發負責人,Apache Sharding-Sphere發起人兼PPMC成員。熱愛分享,擁抱開源,主張代碼優雅化,擅長以Java爲主的分佈式架構及以Kubernetes和Mesos爲主的雲平臺的構建。ShardingSphere已進入Apache軟件基金會,是京東集團首個進入Apache的開源項目,也是Apache首個分佈式數據庫中間件。</p><b>吳晟</b><p>Apache SkyWalking創始人及PPMC成員,Apache ShardingSphere原型作者及PPMC成員,Apache Zipkin貢獻者,Apache孵化器導師,CNCF基金會OpenTracing標準化委員會成員,W3C Trace Context規範貢獻者。擅長分佈式架構、性能監控與診斷、分佈式追蹤、雲原生監控等領域。</p><b>敖小劍</b><p>具有十七年軟件開發經驗,資深碼農,微服務專家,Cloud Native擁護者,敏捷實踐者,ServiceMesh佈道師,ServicelMesher中文社區聯合創始人。專注於基礎架構建設,對微服務、雲計算等相關技術有着深入研究和獨到見解。</p><b>宋淨超</b><p>螞蟻金服雲原生布道師,ServiceMesher中文社區聯合創始人,Kubemetes社區成員,Istio社區成員,《Cloud Native Go》《Python雲原生》《雲原生Java》等圖書譯者。</p></div></div>\",\n  \"author\": \"張亮, 吳晟, 敖小劍, 宋淨超\",\n  \"image_url\": \"http://reserved-antcloud-cnshnfpub-opsware-v2.oss-cn-shanghai.aliyuncs.com/fas/books/1.png?OSSAccessKeyId=RZU9wKztYEqaBQGB&Expires=1647166160&Signature=xCS%2FpJtY8%2FVcdbLqfjHUp6z%2FoOw%3D\"\n}\n', 99.00, 10000, 'cd083092-b3c4-445d-8e77-76fb927a02fc');
INSERT INTO `stock_tb` VALUES (22, '00002', 'Cloud Native Go: 構建基於Go和React的雲原生Web應用與微服務', '{\n  \"description\": \"<div><p>本書旨在向開發人員展示如何構建適用於大流量、高併發場景下的雲原生Web應用。本書從搭建開發測試環境開始,逐步介紹使用Go語言構建微服務的方法,通過引入CI/CD流程和Wercker、Docker等工具將應用推送到雲中。結合微服務構建中的後端服務、數據服務、事件溯源和CQRS模式、基於React和Flux的UI設計等,本書最後構建了一個基於Web的RPG遊戲World of FluxCraft,可以作爲使用Go構建雲原生Web應用的參考,適合於雲計算與Go語言編程從業者們閱讀。</p><div><b>1.雲原生是雲計算時代的發展趨勢和必然結果</b><p>《Cloud Native Go:構建基於Go和React的雲原生Web應用與微服務》通過一個雲原生應用項目的構建,爲大家介紹了雲原生的道與術,引導讀者瞭解雲原生理念的產生、應用場景、優勢。</p><b>2.集現今諸多熱點技術之大成</b><p>《Cloud Native Go:構建基於Go和React的雲原生Web應用與微服務》在構建雲原生項目時,涉及Docker、持續集成、微服務、DevOps、事件溯源與CQRS等衆多備受關注的技術熱點,無疑會讓讀者受益匪淺。</p><b>3.Go語言助理雲開發完美實現</b><p>Go語言以其簡單優雅、快速安全、支持高併發等特性,成爲雲計算時代的最優語言。《Cloud Native Go:構建基於Go和React的雲原生Web應用與微服務》將帶領讀者正確認識Go語言,掌握用Go構建應用程序的方法。</p><b>4.流程完整,示例具體詳細</b><p>《Cloud Native Go:構建基於Go和React的雲原生Web應用與微服務》從搭建平臺開始,逐步帶領讀者開發一個完整的雲上項目。其中的每一環節都有詳細講解。示例具有代表性,代碼詳細,幫助讀者輕鬆掌握雲原生開發的關鍵。</p></div></div>\",\n  \"author\": \"Kevin Hoffman, 宋淨超\",\n  \"image_url\": \"http://reserved-antcloud-cnshnfpub-opsware-v2.oss-cn-shanghai.aliyuncs.com/fas/books/2.png?OSSAccessKeyId=RZU9wKztYEqaBQGB&Expires=1647166182&Signature=U4ep6Dsh4w8TqW6tLlDLoEopIm4%3D\"\n}\n', 69.00, 10000, 'cd083092-b3c4-445d-8e77-76fb927a02fc');
INSERT INTO `stock_tb` VALUES (23, '00003', '雲原生Java: Spring Boot、Spring Cloud與Cloud Foundry彈性系統設計', '{\n  \"description\": \"<div><p>無論是傳統IT行業,還是互聯網行業,都正處於行業歷史上最劇烈的變革中 :大量的系統正在從傳統的IT架構轉向基於雲的架構, 開發模式也正在從開發和運維分工的傳統模式,逐漸轉向統一的“DevOps”模式。Java技術已經進入了新的生命週期,大量被用於構建現代的、基於雲的應用程序。 本書詳細闡述了開發雲原生應用程序的機遇和挑戰,明確指出了成功實現的方向,並且重點介紹了微服務框架Spring Boot。Spring Boot可以輕鬆創建任何粒度的 Spring服務,並部署到現代的容器環境中。本書主要面向正在使用 Spring Boot、SpringCloud和Cloud Foundry, 以便更快、更好地構建軟件的Java/JVM 開發人員。本書一共分爲4個部分共15章。第1章和第2章介紹了雲原生思想產生的背景,然後介紹了Spring Foundry。第3章介紹瞭如何配置Spring Boot應用程序。第4章介紹瞭如何測試Spring應用程序,從如何測試最簡單的組件到測試分佈式系統。第5章介紹了可以將應用程序遷移到Cloud Foundry等雲平臺的輕量級重構方式。第6章介紹瞭如何使用Spring構建HTTP和RESTful服務。第7章介紹了在分佈式系統中控制請求進出的常用方法。第8章介紹瞭如何構建一個響應外部請求的服務。第9章介紹瞭如何使用Spring Data在Spring中管理數據。這爲領域驅動的思想奠定了基礎。第10章介紹瞭如何使用Spring中事件驅動、消息中心化的能力,來集成分佈式服務和數據。第11章介紹瞭如何利用雲平臺(如Cloud Foundry)的能力來處理長期運行的工作。第12章介紹了在分佈式系統中管理狀態的一些方法。第13章介紹瞭如何構建具備可觀測性和可操作性的系統。第14章介紹瞭如何構建類似於Cloud Foundry平臺的服務代理。第15章介紹了持續交付背後的思想。</p><div><b>1. 基礎知識</b><p>瞭解雲原生思維背後的動機;配置和測試Spring Boot應用程序;將您的傳統應用程序遷移至雲端</p><b>2. 微服務</b><p>使用Spring構建HTTP和RESTful服務;在分佈式系統中路由請求;建立更接近數據的邊緣服務</p><b>3. 數據整合</b><p>使用Spring Data管理數據,並將分佈式服務與——Spring對事件驅動、以消息傳遞爲中心架構的支持——集成起來</p><b>4. 生產</b><p>讓您的系統可觀察;使用服務代理來連接有狀態的服務;瞭解持續交付背後的重要思想</p></div></div>\",\n  \"author\": \"Josh Long, 張若飛, 宋淨超\",\n  \"image_url\": \"http://reserved-antcloud-cnshnfpub-opsware-v2.oss-cn-shanghai.aliyuncs.com/fas/books/3.png?OSSAccessKeyId=RZU9wKztYEqaBQGB&Expires=1647166193&Signature=WfYCQ1S8XLuqj7oCGxHYTH3VNdc%3D\"\n}\n', 128.00, 10000, 'cd083092-b3c4-445d-8e77-76fb927a02fc');
INSERT INTO `stock_tb` VALUES (24, '00004', 'Python雲原生: 構建應對海量用戶數據的高可擴展Web應用', '{\n  \"description\": \"<div><p>《Python雲原生:構建應對海量用戶數據的高可擴展Web應用》以一個應用開發貫穿始終,從雲原生和微服務的概念原理講起,使用Python構建雲原生應用,並使用React構建Web視圖。爲了應對大規模的互聯網流量,使用了Flux構建UI和事件溯源及CQRS模式。考慮到Web應用的安全性,《Python雲原生:構建應對海量用戶數據的高可擴展Web應用》對此也給出瞭解決方案。書中對於關鍵步驟進行了詳細講解並給出運行結果。讀者可以利用Docker容器、CI/CD工具,敏捷構建和發佈本書示例中的應用到AWS、Azure這樣的公有云平臺上,再利用平臺工具對基礎設施和應用的運行進行持續監控。</p><div><b>雲原生是雲計算時代的發展趨勢和必然結果</b><p>雲原生將持續領航雲時代架構理念</p><b>用Python語言進行開發</b><p>易如門,易掌握,集現今諸多熱點技術之大成</p><b>流程完整,示例具體詳細</b><p>一個實際開發案例貫穿始終,全面開放代碼</p></div></div>\",\n  \"author\": \"Manish Sethi, 宋淨超\",\n  \"image_url\": \"http://reserved-antcloud-cnshnfpub-opsware-v2.oss-cn-shanghai.aliyuncs.com/fas/books/4.png?OSSAccessKeyId=RZU9wKztYEqaBQGB&Expires=1647178695&Signature=9l4JHy7eMQVj3T3mXgRpAN9wdPI%3D\"\n}\n', 89.00, 10000, 'cd083092-b3c4-445d-8e77-76fb927a02fc');
INSERT INTO `stock_tb` VALUES (25, '00005', '深入淺出Istio: Service Mesh快速入門與實踐', '{\n  \"description\": \"<div><p>Google聯合IBM、Lyft推出的Istio,一經問世就受到了人們的普遍關注,其熱度迅速攀升,成爲Service Mesh(服務網格)方案的代表項目。本書整理了Istio中的部分概念和案例,以快速入門的形式,對Istio的基礎用法一一進行講解,並在書末給出一些試用方面的建議。<br/>在本書中,前3章從微服務和服務網格的簡短歷史開始,講述了服務網格的誕生過程、基本特性及Istio的核心功能,若對這些內容已經有所瞭解,則可以直接從第4章開始閱讀;第4、5章分別講解了Istio的配置和部署過程;第6章至第9章,通過多個場景來講解Istio的常用功能;第10章結合了筆者的實踐經驗,爲讀者提供了Istio的一系列試用建議。本書沒有采用官方複雜的Book Info應用案例,而是採用客戶端+簡單HTTP服務端的案例,讀者隨時都能在短時間內啓動一個小的測試。<br/>本書面向對服務網格技術感興趣,並希望進一步瞭解和學習Istio的中高級技術人員,假設讀者已經瞭解Kubernetes的相關概念並能夠在Kubernetes上熟練部署和管理微服務。若希望全面、深入地學習Kubernetes,可參考《Kubernetes 權威指南:從Docker到Kubernetes實踐全接觸》和《Kubernetes 權威指南:企業級容器雲實戰》。</p><div><b>快速入門Service Mesh和實踐</b><p>手把手快速入門Service Mesh和實踐,並根據Istio 1.1版本的升級,將源碼及內容同步更新至GitHub</p><b>作者爲Kubernetes 權威指南作者之一</b><p>作者爲Kubernetes 權威指南作者之一,Istio、Kubernetes項目成員,Istio.io主要貢獻者之一</p><b>知名大咖熱評</b><p>知名大咖敖小劍、馬全一、張琦及《Kubernetes 權威指南》作者龔正等熱評!</p></div></div>\",\n  \"author\": \"崔秀龍\",\n  \"image_url\": \"http://reserved-antcloud-cnshnfpub-opsware-v2.oss-cn-shanghai.aliyuncs.com/fas/books/5.png?OSSAccessKeyId=RZU9wKztYEqaBQGB&Expires=1647179151&Signature=LIY%2F56jF8Out5eHsxAU1hkUOp7o%3D\"\n}\n', 79.00, 9998, 'cd083092-b3c4-445d-8e77-76fb927a02fc');

DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `branch_id` bigint NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

注意每個庫裏都有undo_log表,這個是記錄每個分佈式事務參與者的本地事務undo_log,用於記錄本地事務日誌信息,用於處理回滾、懸掛、空回滾等等。模擬場景下,表裏基本不會有數據,單個事務的數據將會在對應的整個分佈式事務提交或回滾後刪除。

構建服務

場景:
簡單的商品付款場景

  • 頁面點擊購買
  • 賬戶服務執行用戶扣款操作
  • 庫存服務扣除庫存
  • 訂單服務生成訂單

所有操作必須都成功纔算成功,任何一個服務失敗,其他已經執行的操作都必須全部回滾。

服務結構如下:
spring-cloud-seata-demo
├── account
├── business
├── order
└── storage

  • account:賬戶服務
  • business:業務入口,作爲全局事務的發起方
  • order:訂單服務
  • storage:庫存服務

這裏只以businees和account作爲例子記錄,order和storage服務與account幾乎一樣。
首先修改父項目spring-cloud-seata-demo的pom文件,統一管理依賴的版本:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>github.clyoudu</groupId>
    <artifactId>spring-cloud-seata-demo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>business</module>
        <module>storage</module>
        <module>order</module>
        <module>account</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.15.RELEASE</version>
    </parent>

    <properties>
        <seata.version>0.9.0</seata.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-all</artifactId>
                <version>${seata.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

主要統一管理了spring cloud、spring cloud alibaba和seata的版本,並且統一引入了lombok、spring-boot-maven-plugin等相關依賴和插件。

然後新建businrss子模塊,pom如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-seata-demo</artifactId>
        <groupId>github.clyoudu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>business</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seta-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

注意這裏替換了spring-cloud-alibaba-seata中seata-all的版本。
business模塊的結構如下:
business
├── pom.xml
└── src
└── main
├── resources
│ ├── registry.conf
│ ├── bootstrap.properties
│ └── application.properties
└── java.github.clyoudu.business
├── dto
│ └── ResultDto.java
├── config
│ └── RestTemplateConfig.java
├── controller
│ └── PurchaseController.java
├── service
│ ├── impl
│ │ └── PurchaseServiceImpl.java
│ └── PurchaseService.java
└── BusinessApplication.java

有兩個關鍵的類,一個是RestTemplateConfig,注意加@Loadbalanced註解,否則會報找不到服務:

@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate () {
        return new RestTemplate();
    }

}

另一個是業務實現類PurchaseServiceImpl

@Service
public class PurchaseServiceImpl implements PurchaseService {

    @Autowired
    private RestTemplate restTemplate;

    @Override
    @GlobalTransactional(timeoutMills = 300000, name = "business")
    public ResultDto purchase(Integer userId, String username, String productCode, Integer count, BigDecimal amount) {
        restTemplate.getForEntity("http://account/debit?userId=" + userId + "&amount=" + amount, ResultDto.class).getBody();
        restTemplate.getForEntity("http://storage/deduct?productCode=" + productCode + "&count=" + count, ResultDto.class).getBody();
        restTemplate.getForEntity("http://order/create?username=" + username + "&productCode=" + productCode + "&count=" + count + "&amount=" + amount, ResultDto.class).getBody();
        return new ResultDto(200, "支付成功", null);
    }
}

作爲分佈式事務的發起方,需要在對應的方法上添加GlobalTransactional註解,可以指定超時時間。

接着新建account子模塊,pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-seata-demo</artifactId>
        <groupId>github.clyoudu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>account</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seta-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jooq</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>org.jooq</groupId>
                <artifactId>jooq-codegen-maven</artifactId>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>

                <configuration>
                    <jdbc>
                        <driver>com.mysql.cj.jdbc.Driver</driver>
                        <url>jdbc:mysql://localhost:3306/balance_db?useUnicode=true&amp;allowMultiQueries=true&amp;useSSL=false&amp;characterEncoding=utf8</url>
                        <username>root</username>
                        <password>root@leichen</password>
                    </jdbc>
                    <generator>
                        <database>
                            <name>org.jooq.meta.mysql.MySQLDatabase</name>
                            <includes>.*</includes>
                            <inputSchema>balance_db</inputSchema>
                        </database>

                        <target>
                            <packageName>github.clyoudu.account.jooq</packageName>
                            <directory>src/main/java</directory>
                        </target>
                    </generator>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

注意這裏替換了spring-cloud-alibaba-seata中seata-all的版本。
account項目的結構如下:

account
├── pom.xml
└── src
    └── main
        ├── resources
        │   ├── registry.conf
        │   ├── bootstrap.properties
        │   └── application.properties
        └── java.github.clyoudu.account
            ├── dto
            │   └── ResultDto.java
            ├── jooq
            │   ├── tables
            │   │   ├── records
            │   │   │   └── BalanceTbRecord.java
            │   │   └── BalanceTb.java
            │   ├── Indexes.java
            │   ├── DefaultCatalog.java
            │   ├── BalanceDb.java
            │   ├── Keys.java
            │   └── Tables.java
            ├── AccountApplication.java
            ├── config
            │   └── DataSourceConfig.java
            ├── dao
            │   ├── BaseDao.java
            │   └── BalanceTbDao.java
            ├── sercice
            │   ├── impl
            │   │   └── BalanceServiceImpl.java
            │   └── BalanceService.java
            └── controller
                └── BalanceController.java

配置文件有仨,registry.conf用於seata-client的註冊,application.properties/bootstrap.properties用於SpringBoot Application註冊。
registry.conf,主要定義了seata配置來源,這裏選擇剛剛搭建和完成配置初始化的nacos

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"
  nacos {
    serverAddr = "localhost"
    namespace = ""
  }
}
config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"
  nacos {
    serverAddr = "localhost"
    namespace = ""
  }
}

application.properties,其中的關鍵配置爲:spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group ,這個配置的值要和啓動seata-server配置的service.vgroup_mapping.xxx中的xxx一樣,比如我這裏在nacos中查詢出的配置爲:
在這裏插入圖片描述
所以這裏spring.cloud.alibaba.seata.tx-service-group的值爲my_test_tx_group

server.port=8001
spring.application.name=account

spring.cloud.nacos.discovery.server-addr=localhost:8848
management.endpoints.web.exposure.include=*

spring.cloud.sentinel.transport.port=8719
spring.cloud.sentinel.transport.dashboard=localhost:8080

spring.datasource.url=jdbc:mysql://localhost:3306/balance_db
spring.datasource.username=root
spring.datasource.password=root@leichen
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group

bootstrap.properties,指定了nacos config相關的配置:

spring.application.name=account
spring.cloud.nacos.config.server-addr=localhost:8848

關鍵邏輯爲扣款操作,BalanceTbDao和BalanceServiceImpl:

@Repository
public class BalanceTbDao extends BaseDao {

    public int debit(Integer userId, BigDecimal amount) {
        return dslContext.execute("update balance_tb set balance = balance - " + amount + " where id = " + userId);
    }

}
@Service
@Slf4j
public class BalanceServiceImpl implements BalanceService {

    @Autowired
    private BalanceTbDao balanceTbDao;

    @Override
    public ResultDto debit(int userId, BigDecimal amount) {
        log.info("事務ID:" + RootContext.getXID());
        if(balanceTbDao.debit(userId, amount) > 0) {
            return new ResultDto(200, "操作成功", null);
        }
        throw new RuntimeException("賬戶扣款失敗");
    }
}

有一個配置類DataSourceConfig,用於代理增強Spring默認的DataSource,讓其支持分佈式事務:

@Configuration
public class DataSourceConfig {

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    public DataSource dataSource(DataSourceProperties properties) {
        HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
        if (properties.getName()!=null && properties.getName().length() > 0) {
            dataSource.setPoolName(properties.getName());
        }
        return new DataSourceProxy(dataSource);
    }

    @SuppressWarnings("unchecked")
    protected static <T> T createDataSource(DataSourceProperties properties,
                                            Class<? extends DataSource> type) {
        return (T) properties.initializeDataSourceBuilder().type(type).build();
    }

}

其他幾個項目和account類似,具體代碼參考:clyoudu/spring-cloud-seata-demo

開始驗證

分別啓動各個服務,先後順序無關。

  • 驗證事務提交:調用businees服務的purchase接口,返回
    {
    	"code": 200,
    	"msg": "支付成功",
    	"data": null
    }
    
    並且account、order、storage三個服務的日誌均輸出:Branch commit result: PhaseTwo_Committed,檢查各個庫表,數據正常邏輯無誤,證明分佈式事務提交成功。
  • 驗證事務回滾:業務的流程是扣款、減庫存、創建訂單,在任何一個流程製造異常,比如修改對應的接口直接拋出異常,或者修改一下表名等等,調用businees服務的purchase接口,返回錯誤,並且account、order、storage三個服務的日誌均輸出:Branch commit result: PhaseTwo_RollBacked,檢查各個庫表,數據正常邏輯無誤,證明分佈式事務回滾成功。
  • undo_log相關的驗證:可以將分佈式事務的超時時間調長一些,並且人爲地讓某些業務流程處理時間變長,查看undo_log表的記錄,或者在業務處理過程中關掉已經完成的業務服務或未完成的業務服務,觀察undo_log表記錄,觀察分佈式事務是否能正常回滾,甚至在業務處理過程中停掉seata server,結合seata 相關文檔,觀察業務系統表現,驗證seata相關原理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章