前置條件:安裝nacos和mysql數據庫
一丶下載seata服務端安裝包,將下載的安裝包上傳至Linux並解壓
1.2-1.4版本都可以
官網地址:https://github.com/seata/seata/releases
[root@iZuf6f6me43woqf6q431tqZ mysoft]# ls docker-compose jdk1.8.0_212 maven-3.6.3 mysql-8.0.23 nginx-1.20.1 node redis-6.2.1 seata seata-server-1.2.0.tar.gz tomcat-9.0.45 [root@iZuf6f6me43woqf6q431tqZ mysoft]#
二、在nacos中新建一個名稱空間專門用來管理seata服務

三丶進入解壓后的seata/conf目錄,編輯registry.conf文件內容如下,即將type設為nacos,並填寫自己的nacos配置信息,表示將seata服務端注冊到nacos中
備注:application,cluster,group的值建議和我填一樣,后面集成seata的時候要用,不然客戶端啟動時可能會找不到服務或者其他坑
type = "nacos" nacos { application = "serverAddr" serverAddr = "xxx.xxx.xxx.xxx:8848" # 填寫你自己的nacos服務地址 namespace = "113db2b2-3a00-4bab-af8d-5f823c8815e5" # 填寫你剛才在nacos中新建的名稱空間id cluster = "default" group = "DEFAULT_GROUP" }
四丶到seata/bin目錄下啟動seata
后台啟動:nohup ./seata-server.sh -h 127.0.0.1 -p 8091 >log.out 2>1 & 指定ip
備注:(1)這里最好將127.0.0.1換成你的Linux外網地址,不然客戶端啟動時可能會找不到服務,(2)如果啟動時報內存不足等錯誤,可以編輯seata-server.sh文件,將2048改成256

若啟動沒有異常,打開nacos,查看seta服務是否已經注冊到nacos中

五、准備數據庫
新建4張表,account(賬戶表)、order_info(訂單表)、storage(庫存表)、undo_log(事務回滾表)
/* Navicat Premium Data Transfer Source Server : aliyun Source Server Type : MySQL Source Server Version : 80023 Source Host : 47.100.205.138:3306 Source Schema : seata Target Server Type : MySQL Target Server Version : 80023 File Encoding : 65001 Date: 28/03/2022 14:04:13 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for account -- ---------------------------- DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', `user_id` bigint(0) NULL DEFAULT NULL COMMENT '用戶id', `total` decimal(10, 0) NULL DEFAULT NULL COMMENT '總額度', `used` decimal(10, 0) NULL DEFAULT NULL COMMENT '已用余額', `residue` decimal(10, 0) NULL DEFAULT 0 COMMENT '剩余可用額度', `is_deleted` int(0) NULL DEFAULT NULL COMMENT '刪除狀態:0-未刪除,1-已刪除', `update_user` bigint(0) NULL DEFAULT NULL COMMENT '更新人', `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新時間', `create_user` bigint(0) NULL DEFAULT NULL COMMENT '創建人', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '創建時間', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 0 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for order_info -- ---------------------------- DROP TABLE IF EXISTS `order_info`; CREATE TABLE `order_info` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `user_id` bigint(0) NULL DEFAULT NULL COMMENT '用戶id', `product_id` bigint(0) NULL DEFAULT NULL COMMENT '產品id', `count` int(0) NULL DEFAULT NULL COMMENT '數量', `money` decimal(11, 0) NULL DEFAULT NULL COMMENT '金額', `status` int(0) NULL DEFAULT NULL COMMENT '訂單狀態:0:創建中;1:已完結', `is_deleted` int(0) NULL DEFAULT NULL COMMENT '刪除狀態:0-未刪除,1-已刪除', `update_user` bigint(0) NULL DEFAULT NULL COMMENT '更新人', `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新時間', `create_user` bigint(0) NULL DEFAULT NULL COMMENT '創建人', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '創建時間', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 0 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for storage -- ---------------------------- DROP TABLE IF EXISTS `storage`; CREATE TABLE `storage` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `product_id` bigint(0) NULL DEFAULT NULL COMMENT '產品id', `total` int(0) NULL DEFAULT NULL COMMENT '總庫存', `used` int(0) NULL DEFAULT NULL COMMENT '已用庫存', `residue` int(0) NULL DEFAULT NULL COMMENT '剩余庫存', `is_deleted` int(0) NULL DEFAULT NULL COMMENT '刪除狀態:0-未刪除,1-已刪除', `update_user` bigint(0) NULL DEFAULT NULL COMMENT '更新人', `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新時間', `create_user` bigint(0) NULL DEFAULT NULL COMMENT '創建人', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '創建時間', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 0 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for undo_log -- ---------------------------- DROP TABLE IF EXISTS `undo_log`; CREATE TABLE `undo_log` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `branch_id` bigint(0) NOT NULL, `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(0) NOT NULL, `log_created` datetime(0) NOT NULL, `log_modified` datetime(0) NOT NULL, `ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 0 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1;
向account和storage表初始化一條數據(user_id和product_id寫死為1,后面下單時要用)
INSERT INTO account ( user_id, total, used, residue ) VALUES( 1, 100, 0, 100 ); INSERT INTO STORAGE ( product_id, total, used, residue ) VALUES(1,100,0,100)
六、搭建一個簡易的微服務工程。具體細節我就不展示了,我的項目大致結構如下。服務之間通過openfeign調用(gateway網關服務暫時是一個空模塊,沒有意義,請求直接訪問具體的服務,不走網關)
頂級模塊依賴如下

七、引入相關依賴,這里我在最頂級的pom定義依賴的版本號,然后在common模塊根據需求,引入具體的依賴
(ps:我這里同時引入了spring-cloud-alibaba-seata 和 seata-spring-boot-starter兩個依賴,一是主要是使用springBoot方式集成方式比較方便,直接編寫相關yml文件即可,二是cloud-alibaba-seata可以自動在服務調用鏈中傳遞xid)

<?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>
<packaging>pom</packaging>
<modules>
<module>wu-order</module>
<module>wu-common</module>
<module>wu-account</module>
<module>wu-storage</module>
<module>wu-gateway</module>
</modules>
<groupId>com.wu.demo</groupId>
<artifactId>springCloudAlibaba</artifactId>
<version>1.0-SNAPSHOT</version>
<!--繼承springboot父項目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR6</spring-cloud.version>
<spring.cloud.alibaba.version>2.2.1.RELEASE</spring.cloud.alibaba.version>
<mybatis.plus.version>3.2.0</mybatis.plus.version>
<knife4j.version>2.0.8</knife4j.version>
<seata.cloud.version>2.2.0.RELEASE</seata.cloud.version>
<seata.boot.version>1.3.0</seata.boot.version>
</properties>
<!--維護公共jar包版本-->
<dependencyManagement>
<dependencies>
<!--引入springcloud alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--引入springcloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--seata分布式事務spring-cloud集成-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>${seata.cloud.version}</version>
</dependency>
<!--seata分布式事務spring-boot集成-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.boot.version}</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<!--mybatis-plus代碼生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<!--knife4j-swagger增強版-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
common模塊依賴如下

<?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>wu-common</artifactId>
<groupId>com.wu.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common-web</artifactId>
<!--維護所有微服務可能用到的公共依賴-->
<dependencies>
<!--spring相關-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--nacos-服務發現-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入openfegin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--seata 分布式事務-->
<!-- <dependency>-->
<!-- <groupId>io.seata</groupId>-->
<!-- <artifactId>seata-spring-boot-starter</artifactId>-->
<!-- </dependency>-->
<!--seata 分布式事務(使用這種方式可以自動傳遞xid)-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<artifactId>druid</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
</exclusions>
</dependency>
<!--mysql驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--mybatis-plus代碼生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--knife4j-swagger增強版-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
八、編寫yml文件,將seata集成到項目中。這里我只寫和seata相關的配置
在每個服務的yml文件中填入以下代碼(appliaction.name根據所在服務自定義)
spring: application: name: order cloud: nacos: discovery: server-addr: xxx.xxx.xxx.xxx:8848 # nacos地址 # seata配置 seata: enabled: true enable-auto-data-source-proxy: false # 關閉自動代理數據源 application-id: ${spring.application.name} tx-service-group: my_tx_group service: vgroup-mapping: my_tx_group: default registry: type: nacos nacos: application: serverAddr server-addr: xxx.xxx.xxx.xxx:8848 # nacos地址 namespace: 113db2b2-3a00-4bab-af8d-5f823c8815e5 # seata服務所在的名稱空間 group: DEFAULT_GROUP cluster: default config: file: name: file.conf
九、為了避免我們引入的mybatis-plus失效,需要在每個服務下引入以下兩個配置類

@Data @Configuration public class DataSourceConfig { @Value("${spring.datasource.url}") private String url; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; @Value("${spring.datasource.driver-class-name}") private String driveClassName; }
@Configuration public class DataSourceProxyConfig { @Autowired private DataSourceConfig dataSourceConfig; @Bean("dataSource") public DataSource druidDataSource() { HikariDataSource hikariDataSource = new HikariDataSource (); hikariDataSource.setUsername(dataSourceConfig.getUsername()); hikariDataSource.setPassword(dataSourceConfig.getPassword()); hikariDataSource.setJdbcUrl(dataSourceConfig.getUrl()); hikariDataSource.setDriverClassName(dataSourceConfig.getDriveClassName()); return hikariDataSource; } @Bean @Primary public DataSourceProxy dataSourceProxy(DataSource dataSource) { return new DataSourceProxy(dataSource); } }
十、在每個服務的啟動類下排除掉默認的數據源
exclude = DataSourceAutoConfiguration.class

十一、seata集成到此基本完成,下面我們編寫具體的業務代碼來測試其是否能完成全局事務的回滾。
我這里的業務是,用戶下單,訂單服務創建訂單,然后調用賬戶服務扣減賬戶余額,然后調用庫存服務扣減庫存。其中賬戶服務和庫存服務在更新數據之前會校驗下余額或庫存是否充足,若不足,則拋出異常。若其中任何一個環節出現異常后,前面的事務能夠回滾,說明我們的seata生效了。
大致業務代碼如下(注意,在事務發起方要開啟全局事務注解@GlobalTransactional(rollbackFor = Exception.class))
訂單服務:

賬戶服務:

庫存服務:

十二、發起下單請求(user_id和product_id填寫我們前面在數據總中初始化的數據,這里count和money故意寫大一些,使其校驗不通過,拋出異常,然后觀察order_info表是否有數據插入,account和storage表是否被更新)

十二、發起請求后,如預期的一樣,拋出異常,並且order_info表是空的並沒有插入數據,account和storage表也沒有被更新,說明我們的全局事務生效了。觀察order服務的日志輸出,確實有回滾記錄。(ps:通過在異常之前打斷點可知,在拋出異常之前訂單數據其實已經插入到了訂單表中,並且undo_log表中也有數據,但是由於后序業務出現異常,seata根據undo_log中的數據將訂單表中的數據回滾,最后清除undo_log中的數據)



結語:seata集成當你最后完成時看似簡單,但整個過程確實很艱難,耗時費力,在集成過程中總是會出現各種錯誤,如seata server啟動報錯,客戶端啟動找不到服務,全局事務不回滾,mybatis-plus失效,代理數據源不生效等等,基本上網上哪些seata集成的各種錯誤我都遇到了,不得不說seata的整合的確很不友好,沒有極大的耐心很難堅持下來。上面的步驟,也只是我對最終效果的總結,參考一下可以,但可能並不適合每一個人。大家遇到報錯時還是具體問題具體分析。
