
一、概述
在微服務架構下,雖然我們會盡量避免分布式事務,但是只要業務復雜的情況下這是一個繞不開的問題,如何保證業務數據一致性呢?本文主要介紹同步場景下使用Seata的AT模式來解決一致性問題。
Seata是 阿里巴巴 開源的 一站式分布式事務解決方案 中間件,以 高效 並且對業務 0 侵入 的方式,解決 微服務 場景下面臨的分布式事務問題
二、Seata介紹
整體事務邏輯是基於 兩階段提交 的模型,核心概念包括以下3個角色:
-
TM:事務的發起者。用來告訴 TC,全局事務的開始,提交,回滾。
-
RM:具體的事務資源,每一個 RM 都會作為一個分支事務注冊在 TC。
-
TC:事務的協調者seata-server,用於接收我們的事務的注冊,提交和回滾。
目前的Seata有兩種模式可使用分別對應不同業務場景
2.1. AT模式
該模式適合的場景:
-
基於支持本地
ACID事務的關系型數據庫。 -
Java 應用,通過
JDBC訪問數據庫。

一個典型的分布式事務過程:
-
TM向TC申請開啟一個全局事務,全局事務創建成功並生成一個全局唯一的XID。 -
XID在微服務調用鏈路的上下文中傳播。 -
RM向TC注冊分支事務,將其納入 XID 對應全局事務的管轄。 -
TM向TC發起針對XID的全局提交或回滾決議。 -
TC調度XID下管轄的全部分支事務完成提交或回滾請求。
2.2. MT模式
該模式邏輯類似TCC,需要 自定義實現 prepare、commit和rollback的邏輯,適合 非關系型數據庫 的場景

三、Seata場景樣例
模擬一個簡單的用戶下單場景,4個子工程分別是 Bussiness(事務發起者)、Order(創建訂單)、Storage(扣減庫存) 和 Account(扣減賬戶余額)

3.1. 部署Seata的Server端

Discover注冊、Config配置和Store存儲模塊默認都是使用file只能適用於單機,我們安裝的時候分別改成使用nacos和Mysql以支持server端集群
3.1.1. 下載最新版本並解壓
https://github.com/seata/seata/releases
3.1.2. 修改 conf/registry.conf 配置
注冊中心和配置中心默認是file這里改為nacos;設置 registry 和 config 節點中的type為nacos,修改serverAddr為你的nacos節點地址。
registry {
type = "nacos"
nacos {
serverAddr = "192.168.28.130"
namespace = "public"
cluster = "default"
}
}
config {
type = "nacos"
nacos {
serverAddr = "192.168.28.130"
namespace = "public"
cluster = "default"
}
}
3.1.3. 修改 conf/nacos-config.txt配置

-
修改 service.vgroup_mapping 為自己應用對應的名稱;如果有多個服務,添加相應的配置
默認組名為
${spring.application.name}-fescar-service-group,可通過spring.cloud.alibaba.seata.tx-service-group配置修改 -
修改 store.mode 為
db,並修改數據庫相關配置
3.1.4. 初始化seata的nacos配置
cd conf
sh nacos-config.sh 192.168.28.130
成功后在nacos的配置列表中能看到seata的相關配置

3.1.5. 初始化數據庫
執行conf/db_store.sql中的腳本
3.1.6. 啟動seata-server
sh bin/seata-server.sh -p 8091 -h 192.168.28.130
3.2. 應用配置
3.2.1. 初始化數據庫
執行腳本 seata-demo.sql
CREATE DATABASE `seata-demo` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `seata-demo`;
--==========================回滾日志表==========================
-- 此腳本必須初始化在你當前的業務數據庫中,用於AT 模式XID記錄。與server端無關(注:業務數據庫)
drop table `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) 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 AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
--==========================業務模擬表==========================
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 初始化庫存模擬數據
INSERT INTO storage_tbl (id, commodity_code, count) VALUES (1, 'P001', 9999999);
INSERT INTO account_tbl (id, user_id, money) VALUES ('1', 'U001', 10000);
需在業務相關的數據庫中添加 undo_log 表,用於保存需要回滾的數據
3.2.2. 添加registry.conf配置
直接把 seata-server 中的registry.conf復制到每個服務中去即可,不需要修改
3.2.3. 修改配置
demo中的每個服務各自修改配置文件
-
bootstrap.yml 修改nacos地址
-
application.yml 修改數據庫配置
3.2.4. 配置數據源代理
Seata是通過代理數據源實現分布式事務,所以需要配置io.seata.rm.datasource.DataSourceProxy的Bean,且是@Primary默認的數據源,否則事務不會回滾,無法實現分布式事務
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Primary
@Bean
public DataSourceProxy dataSourceProxy(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}
因為使用了mybatis的starter所以需要排除DataSourceAutoConfiguration,不然會產生循環依賴
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
3.2.5. 事務發起者添加全局事務注解
事務發起者 business-service 添加 @GlobalTransactional 注解
@GlobalTransactional
public void placeOrder(String userId) {
......
}
3.3. 測試
提供兩個接口測試
-
事務成功:扣除庫存成功 > 創建訂單成功 > 扣減賬戶余額成功
http://localhost:9090/placeOrder -
事務失敗:扣除庫存成功 > 創建訂單成功 > 扣減賬戶余額失敗,事務回滾
http://localhost:9090/placeOrderFallBack
3.4. demo地址
https://gitee.com/zlt2000/microservices-platform/tree/master/zlt-demo/seata-demo
