1. Seata Server 部署
Seata分TC、TM和RM三個角色,TC(Server端)為單獨伺服器端部署,TM和RM(Client端)由業務系統整合。
首先,下載最新的安裝包
也可以下載原始碼,然後本地編譯。最新的版本是1.5.2
下載後的啟動包(或者原始碼)中有個scripts目錄,裡面有各種我們所需的指令碼
Server端儲存模式(store.mode)現有file、db、redis三種:
修改組態檔
主要修改的點是:
在資源目錄還有一個application.example.yml檔案,application.example.yml中附帶額外設定,可以將其db|redis相關設定複製至application.yml,進行修改store.db或store.redis相關屬性。
如果不使用外部設定中心,直接使用本地檔案設定的話,那麼最簡單的設定可能是這樣的:
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
type: file
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace:
cluster: default
store:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true&useUnicode=true&serverTimezone=Asia/Shanghai
user: root
password: 123456
min-conn: 5
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 100
max-wait: 5000
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
更多設定請參見 https://seata.io/zh-cn/docs/user/configurations.html
這裡面哪些是Server端需要設定的,哪些是Client端需要設定的,以及每個引數的含義都解釋得非常詳細,強烈建議看一下,這裡就不在贅述。
此處註冊中心用nacos
https://nacos.io/zh-cn/index.html
建表(預設資料庫是seata)
USE `seata`;
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '全域性事務表';
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '分支事務表';
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid_and_branch_id` (`xid` , `branch_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '全域性鎖表';
CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
primary key (`lock_key`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
如果採用AT模式,那麼每個業務資料庫還需建一個undo_log表
-- 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 NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) 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 = utf8mb4 COMMENT ='AT transaction mode undo table';
啟動
2. 業務系統整合 Seata Client
步驟一:新增seata依賴(建議單選)
步驟二:undo_log建表、設定引數(僅AT模式)
https://seata.io/zh-cn/docs/user/configurations.html
步驟三:資料來源代理(不支援自動和手動設定並存)
1、如果使用seata-all
自動設定由註解@EnableAutoDataSourceProxy開啟,並可選擇jdk proxy或者cglib proxy。
如果採用XA模式,@EnableAutoDataSourceProxy(dataSourceProxyMode = "XA")
手動設定參考如下:
@Primary
@Bean("dataSource")
public DataSource dataSource(DataSource druidDataSource) {
//AT 代理 二選一
return new DataSourceProxy(druidDataSource);
//XA 代理
return new DataSourceProxyXA(druidDataSource)
}
2、如果使用seata-starter
預設就開啟了自動代理資料來源,無需額外設定
如果使用自動代理資料來源時,如果使用XA模式還需要調整組態檔application.yml
seata:
data-source-proxy-mode: XA
如果想要關閉seata-spring-boot-starter的資料來源自動代理,可調整組態檔application.yml
seata:
enable-auto-data-source-proxy: false
步驟四:初始化GlobalTransactionScanner
自動,引入seata-spring-boot-starter、spring-cloud-starter-alibaba-seata等jar即可
手動
@Bean
public GlobalTransactionScanner globalTransactionScanner() {
String applicationName = this.applicationContext.getEnvironment().getProperty("spring.application.name");
String txServiceGroup = this.seataProperties.getTxServiceGroup();
if (StringUtils.isEmpty(txServiceGroup)) {
txServiceGroup = applicationName + "-fescar-service-group";
this.seataProperties.setTxServiceGroup(txServiceGroup);
}
return new GlobalTransactionScanner(applicationName, txServiceGroup);
}
步驟五:業務使用
只需在業務方法上加上@GlobalTransactional 註解即可
3. 範例程式碼
此處用官網的那個案例,呼叫關係如圖:
首先,建庫建表,指令碼如下:
CREATE DATABASE `account`;
USE `account`;
DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`amount` double(14,2) DEFAULT '0.00',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `t_account` VALUES (1,'1',79.90);
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`branch_id` bigint NOT NULL COMMENT 'branch transaction id',
`xid` varchar(128) 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 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 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AT transaction mode undo table';
CREATE DATABASE `order`;
USE `order`;
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
`id` int NOT NULL AUTO_INCREMENT,
`order_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`user_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`commodity_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`count` int DEFAULT '0',
`amount` double(14,2) DEFAULT '0.00',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`branch_id` bigint NOT NULL COMMENT 'branch transaction id',
`xid` varchar(128) 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 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 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AT transaction mode undo table';
CREATE DATABASE `stock`;
USE `stock`;
DROP TABLE IF EXISTS `t_stock`;
CREATE TABLE `t_stock` (
`id` int NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`count` int DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `t_stock` VALUES (1,'A001','立白洗潔精',7);
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`branch_id` bigint NOT NULL COMMENT 'branch transaction id',
`xid` varchar(128) 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 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 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AT transaction mode undo table';
注意版本:範例中使用的框架為 Spring Boot + Mybatis-Plus + Dubbo + Seata
雖然Seata最新版本是1.5.2,Dubbo最新版本為3.1.1,但這這兩個版本中二者不相容,可以降低其中一個的版本,比如
Seata 1.4.2 + Dubbo 3.1.1 或者 Seata 1.5.2 + Dubbo 2.7.18
本範例中採用的是 seata-spring-boot-starter 1.5.2 + dubbo 2.7.18
工程結構如圖
父pom.xml如下
<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>com.cjs.example</groupId>
<artifactId>seata-spring-boot-starter-samples</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>seata-spring-boot-starter-samples</name>
<modules>
<module>samples-common-service</module>
<module>samples-stock-service</module>
<module>samples-order-service</module>
<module>samples-account-service</module>
<module>samples-business-service</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>2.7.5</spring-boot.version>
<dubbo.version>2.7.18</dubbo.version>
<seata.version>1.5.2</seata.version>
<mybatis-plus.version>3.5.2</mybatis-plus.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
子pom.xml如下,其它幾個類似不再重複:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.cjs.example</groupId>
<artifactId>seata-spring-boot-starter-samples</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.cjs.example</groupId>
<artifactId>samples-order-service</artifactId>
<version>${parent.version}</version>
<name>samples-order-service</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.cjs.example</groupId>
<artifactId>samples-common-service</artifactId>
<version>${parent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 8083
servlet:
context-path: /order
spring:
application:
name: samples-order-service
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/order?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
dubbo:
protocol:
name: dubbo
port: 20883
registry:
address: nacos://127.0.0.1:8848
config-center:
address: nacos://127.0.0.1:8848
metadata-report:
address: nacos://127.0.0.1:8848
seata:
enabled: true
tx-service-group: my_test_tx_group
service:
vgroup-mapping:
my_test_tx_group: default
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
cluster: default
group: SEATA_GROUP
核心程式碼如下
依次啟動這四個專案,Postman存取一下
通過斷點,觀察 global_table、lock_table、branch_table 等表資料的變化
剛開始,所有表中資料都是空的
執行到事務方法之後,global_table表中有了一條資料
當呼叫第一個遠端介面扣減庫存後,lock_table和branch_table表中都有了一條資料
同時,stock庫中undo_log表中也新增了一條資料
繼續往下執行,當呼叫第二個遠端介面建立訂單後,branch_table和lock_table中都新增了2條資料
account庫和order庫中的undo_log也有了資料
當事務方法執行完後,事務提交,上述表中此次事務相關資料清空
我們通過Seata Server的紀錄檔可以更清晰的看到事務從建立到提交的整個過程
正式正常事務成功的情況,有時候由於業務報錯了,或者事務超時了,或者其它的情況,事務會回滾
4. 設定中心
在此之前,範例中沒有使用設定中心,而是採用本地組態檔的方式。但實際開發過程中,建議還是採用設定中心,下面以nacos作為設定中心演示。
細心的同學會發現,微服務架構中有設定中心和註冊中心,Dubbo中也有設定中心和註冊中心,而本文講的Seata也有設定中心和註冊中心。那麼它們有什麼區別嗎?其實,並沒有區別,各是各的。微服務的設定中心和註冊中心你要設定,Dubbo的你也要設定,Seata的設定中心和註冊中心你還要設定,儘管它們可能是同一個範例,但那也得各配各的。
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>2.1.1</version>
</dependency>
有兩種設定方式
方式一:放在某個名稱空間下,每一個設定項作為一行,即每一行一個DataId
https://github.com/seata/seata/tree/master/script/config-center
在scripts/config-center目錄下有個config.txt,這個config.txt檔案中為我們準備好了各種設定項,我們按需修改裡面的內容即可,然後可以通過指令碼將config.txt中的內容匯入nacos,當然也可以一條一條手動在nacos中建立
例如:
sh ${SEATAPATH}/script/config-center/nacos/nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u username -w password
注意:-g 指定Group -t 指定名稱空間
範例中上傳到預設名稱空間下了
注意,config.txt中包含了Server端和Client端的設定,裡面註釋寫的也比較清楚哪些是Server端需要的設定,哪些是Client端的設定
匯入設定以後,現在修改Server端的設定,seata-->conf-->application.yml中seata.config部分
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group : "SEATA_GROUP"
namespace:
username:
password:
同時,每個業務系統裡面的設定也要修改一下
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group : "SEATA_GROUP"
namespace:
username:
password:
由於這裡匯入設定的時候沒有-t指定名稱空間,即匯入到預設名稱空間,所以設定裡面namespace為空,如果-t指定了特定的名稱空間,則server和client端的namespace也要與之對應
方式二:通過dataId設定
首先,需要在nacos新建設定,此處dataId為seataServer.properties
然後,將修改後的config.txt內容貼上進去,儲存修改並行布即可
修改Server和Client端seata.config設定
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: "SEATA_GROUP"
namespace:
dataId: "seataServer.properties"
username:
password:
重啟Server和Client即可