この記事を読む前に、以下を参照してください。
https://blog.csdn.net/MinggeQingchun/article/details/126100300
https://blog.csdn.net/MinggeQingchun/article/details/126176893
ATモード
前提
1. ローカル ACID トランザクションをサポートするリレーショナル データベースに基づく
2. JDBC を介してデータベースにアクセスする Java アプリケーション
全体的なメカニズム
2 フェーズ コミット プロトコルの進化:
1. フェーズ 1: ビジネス データとロールバック ログ レコードが同じローカル トランザクションでコミットされ、ローカル ロックと接続リソースが解放されます。
2. 第二段階:
(1) 送信は非同期であり、非常に迅速に完了します
(2) ロールバックは、1 段階のロールバック ログによって逆に補償されます。
書き込み分離
1. 最初の段階でローカル トランザクションをコミットする前に、まずグローバル ロックを取得する必要があります 。
2. グローバル ロックを取得できない場合 、ローカル トランザクションを送信できません。
3.グローバルロックの取得を 一定の範囲内に制限し、範囲を超えると断念してローカルトランザクションをロールバックし、ローカルロックを解除します。
分離を読む
Read Committed (Read Committed) 以上の データベース ローカル トランザクション分離レベルに基づい て、Seata (AT モード) の既定のグローバル分離レベルはRead Uncommitted (Read Uncommitted)です 。
If the application is in a specific scenario, it is need to require global read-committed . 現在の Seata メソッドは、SELECT FOR UPDATE ステートメントのプロキシ経由です。
1. AT トランザクション モード: SpringBoot シングル アプリケーション マルチデータ ソース AT 分散トランザクション
Spring Boot モノマー プロジェクトでは、複数のデータ ソースを使用する場合、複数のデータ ソースのデータの一貫性を確保する必要がある、つまり分散トランザクションの問題が発生します. Seata の AT トランザクション モードは、分散トランザクションの問題を解決するために使用されます。
次の写真を例にして注文してください
1. データベース、テーブルの作成、データの挿入など
(1) accountdb アカウント ライブラリ、アカウント アカウント テーブル
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for account
-- ----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`user_id` int(20) NULL DEFAULT NULL,
`balance` decimal(20, 0) NULL DEFAULT NULL,
`update_time` datetime(6) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
(2) productdb 製品ライブラリ、製品製品テーブル
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for product
-- ----------------------------
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`price` decimal(10, 2) NULL DEFAULT NULL,
`stock` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`add_time` datetime(6) NULL DEFAULT NULL,
`update_time` datetime(6) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
(3) orderdb 注文ライブラリ、注文注文テーブル
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for orders
-- ----------------------------
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`user_id` int(20) NULL DEFAULT NULL,
`product_id` int(20) NULL DEFAULT NULL,
`pay_amount` decimal(20, 0) NULL DEFAULT NULL,
`add_time` datetime(6) NULL DEFAULT NULL,
`update_time` datetime(6) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
(4)undo_log表
-- 注意此处0.7.0+ 增加字段 context
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,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
ノート:
各ライブラリは undo_log テーブルを作成する必要があります。これは、主にブランチ トランザクションのロールバックに使用される、Seata AT モードで作成する必要があるテーブルです。
2.SpringBoot 単一アプリケーションを作成する
1. springcloud-alibaba-2-seata-distributed-transaction という名前の springboot アプリケーションを作成します。
2. 依存関係を追加する (Spring Cloud 以外のマイクロサービス プロジェクト、Spring Cloud 依存関係なし)
<groupId>com.company</groupId>
<artifactId>springcloud-alibaba-2-seata-distributed-transaction</artifactId>
<version>1.0.0</version>
<name>springcloud-alibaba-2-seata-distributed-transaction</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
<spring-cloud-alibaba.version>2.2.7.RELEASE</spring-cloud-alibaba.version>
</properties>
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- <version>8.0.28</version>-->
</dependency>
<!-- mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- seata-spring-boot-starter -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!-- dynamic-datasource-spring-boot-starter动态数据源 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies>
<!-- dependencyManagement标签 通常适用于多模块环境下定义一个top module来专门管理公共依赖的情况
在子项目中不写该依赖项,那么子项目仍然会从父项目depenManagement中继承该artifactId和groupId依赖项(全部继承)
若子项目 中dependencies中的dependency声明了version,则父项目中dependencyManagement中的声明无效
Spring Cloud、Spring Cloud Alibaba 以及 Spring Boot 之间版本依赖参考官网
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
-->
<dependencyManagement>
<dependencies>
<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>
<!-- spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--mybatis代码自动生成插件-->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.0</version>
<configuration>
<!--配置文件的位置-->
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
<!--生成代码过程中是否打印日志-->
<verbose>true</verbose>
<!--生成时是否覆盖java文件,xml文件总是合并-->
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
3. application.properties 構成ファイル
#内嵌服务器端口
server.port=8081
#应用服务名称
spring.application.name=springcloud-alibaba-2-seata-distributed-transaction
# 设置默认的数据源或者数据源组,默认值即为master
spring.datasource.dynamic.primary=order-ds
# 订单order数据源配置
spring.datasource.dynamic.datasource.order-ds.url=jdbc:mysql://localhost:3306/orderdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
spring.datasource.dynamic.datasource.order-ds.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.dynamic.datasource.order-ds.username=root
spring.datasource.dynamic.datasource.order-ds.password=admin123456
# 商品product数据源配置
spring.datasource.dynamic.datasource.product-ds.url=jdbc:mysql://localhost:3306/productdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
spring.datasource.dynamic.datasource.product-ds.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.dynamic.datasource.product-ds.username=root
spring.datasource.dynamic.datasource.product-ds.password=admin123456
# 账户account数据源配置
spring.datasource.dynamic.datasource.account-ds.url=jdbc:mysql://localhost:3306/accountdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
spring.datasource.dynamic.datasource.account-ds.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.dynamic.datasource.account-ds.username=root
spring.datasource.dynamic.datasource.account-ds.password=admin123456
# 是否启动对Seata的集成
spring.datasource.dynamic.seata=true
#-----------------------------------------------------------
#单机版 tc server 配置
# Seata应用编号,默认为 ${spring.application.name}
seata.application-id=springboot-seata
# Seata事务组编号,用于TC集群名,一般格式为:${spring.application.name}-group
seata.tx-service-group=springboot-seata-group
# 虚拟组和分组的映射 seata.service.vgroup-mapping.${seata.tx-service-group}=default
seata.service.vgroup-mapping.springboot-seata-group=default
# 分组和Seata服务的映射,此处default指上面 seata.service.vgroup-mapping.springboot-seata-group 的值 default
seata.service.grouplist.default=192.168.133.129:8091
# 存储模式 默认 file模式
seata.config.type=file
# 默认为 file
seata.registry.type=file
#------------------------------------------------------------
4. 対応するコントローラー、モデル、マッパー、およびサービス クラスを記述します。ここでは、呼び出しシーケンスに関連するクラスのみを示します。
コントローラーのテストクラス
@Slf4j //lombok
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/order")
public Integer createOrder(@RequestParam("userId") Integer userId,
@RequestParam("productId") Integer productId) throws Exception {
log.info("请求下单, 用户:{}, 商品:{}", userId, productId);
return orderService.createOrder(userId, productId);
}
}
注文論理クラス
ノート:
(1) @DSアノテーション、複数データソースの切り替え
(2) @GlobalTransactionalアノテーション; seaa グローバルトランザクションアノテーション
メイン サービスは @GlobalTransactional でアノテーションを付けることができ、呼び出されたサービスは @GlobalTransactional と @Transactional を追加する必要はありません
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrdersMapper ordersMapper;
@Autowired
private AccountService accountService;
@Autowired
private ProductService productService;
@Override
/**
* MyBatis-Plus 使用 @DS注解 做多数据源切换
* 语法:@DS(value = "数据源名称")
* 1、依赖:
* <dependency>
* <groupId>com.baomidou</groupId>
* <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
* <version>3.0.0</version>
* </dependency>
* 2、yml 或 properties 配置
* # 设置默认的数据源或者数据源组,默认值即为master
* spring.datasource.dynamic.primary=order-ds
*
* # 订单order数据源配置
* spring.datasource.dynamic.datasource.order-ds.url=jdbc:mysql://localhost:3306/orderdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
* spring.datasource.dynamic.datasource.order-ds.driver-class-name=com.mysql.cj.jdbc.Driver
* spring.datasource.dynamic.datasource.order-ds.username=root
* spring.datasource.dynamic.datasource.order-ds.password=admin123456
*
* # 商品product数据源配置
* spring.datasource.dynamic.datasource.product-ds.url=jdbc:mysql://localhost:3306/productdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
* spring.datasource.dynamic.datasource.product-ds.driver-class-name=com.mysql.cj.jdbc.Driver
* spring.datasource.dynamic.datasource.product-ds.username=root
* spring.datasource.dynamic.datasource.product-ds.password=admin123456
* 3、@DS注解到实现类或者实现类的方法上才可以
* 当注解添加到类上,意味着此类里的方法都使用此数据源;
* 当注解添加到方法上时,意味着此方法上使用的数据源优先级高于其他一切配置
* 注:
* (1)注解添加在dao.mapper上无效
* (2)注解添加到interface Service类上无效
* (3)注解添加到interface Service方法上无效
*/
@DS(value = "order-ds")
@GlobalTransactional //seata全局事务注解
public Integer createOrder(Integer userId, Integer productId) throws Exception {
Integer amount = 1; // 购买数量暂时设置为 1
log.info("当前 XID: {}", RootContext.getXID());
//1、减库存
Product product = productService.reduceStock(productId, amount);
//2、减余额
accountService.reduceBalance(userId, product.getPrice());
//3、下订单
Orders order = new Orders();
order.setUserId(userId);
order.setProductId(productId);
order.setPayAmount(product.getPrice().multiply(new BigDecimal(amount)));
order.setAddTime(new Date());
ordersMapper.insertSelective(order);
//造成异常,测试是否回滚
//int a = 10/0;
log.info("下订单: {}", order.getId());
// 返回订单编号
return order.getId();
}
}
製品ロジック クラス
@Slf4j
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
@Override
@DS(value = "product-ds")
public Product reduceStock(Integer productId, Integer amount) throws Exception {
log.info("当前 XID: {}", RootContext.getXID());
// 检查库存
Product product = productMapper.selectByPrimaryKey(productId);
if (product.getStock() < amount) {
throw new Exception("库存不足");
}
// 扣减库存
int updateCount = productMapper.reduceStock(productId, amount);
// 扣除成功
if (updateCount == 0) {
throw new Exception("库存不足");
}
//造成异常,测试是否回滚
//int a = 10/0;
// 扣除成功
log.info("扣除 {} 库存成功", productId);
return product;
}
}
アカウント ロジック クラス
@Slf4j
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
@DS(value = "account-ds")
public void reduceBalance(Integer userId, BigDecimal money) throws Exception {
log.info("当前 XID: {}", RootContext.getXID());
// 检查余额
Account account = accountMapper.selectAccountByUserId(userId);
if (account.getBalance().doubleValue() < money.doubleValue()) {
throw new Exception("余额不足");
}
// 扣除余额
int updateCount = accountMapper.reduceBalance(userId, money);
// 扣除成功
if (updateCount == 0) {
throw new Exception("余额不足");
}
//造成异常,测试是否回滚
//int a = 10/0;
log.info("扣除用户 {} 余额成功", userId);
}
}
5. seata-server を起動し、ブラウザで http://localhost:8081/order?userId=1&productId=1にアクセスします。
OrderServiceImpl、ProductServiceImpl、および AccountServiceImpl の実装クラスにそれぞれ次のコードを記述して、トランザクションのロールバック テストを実行できます。
//造成异常,测试是否回滚
int a = 10/0;
@DS アノテーション
MyBatis-Plus は @DS アノテーションを使用して複数のデータ ソースを切り替えます
文法:
@DS(value = "データソース名")
1. 頼りにする:
<!-- dynamic-datasource-spring-boot-starter动态数据源 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
2.yml またはプロパティの構成
# 设置默认的数据源或者数据源组,默认值即为master
spring.datasource.dynamic.primary=order-ds
# 订单order数据源配置
spring.datasource.dynamic.datasource.order-ds.url=jdbc:mysql://localhost:3306/orderdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
spring.datasource.dynamic.datasource.order-ds.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.dynamic.datasource.order-ds.username=root
spring.datasource.dynamic.datasource.order-ds.password=admin123456
# 商品product数据源配置
spring.datasource.dynamic.datasource.product-ds.url=jdbc:mysql://localhost:3306/productdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
spring.datasource.dynamic.datasource.product-ds.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.dynamic.datasource.product-ds.username=root
spring.datasource.dynamic.datasource.product-ds.password=admin123456
# 账户account数据源配置
spring.datasource.dynamic.datasource.account-ds.url=jdbc:mysql://localhost:3306/accountdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
spring.datasource.dynamic.datasource.account-ds.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.dynamic.datasource.account-ds.username=root
spring.datasource.dynamic.datasource.account-ds.password=admin123456
3. @DS は、実装クラスまたは実装クラスのメソッドにのみアノテーションを付けることができます
注釈がクラスに追加されると、このクラスのメソッドがこのデータ ソースを使用することを意味します。
アノテーションがメソッドに追加されると、このメソッドで使用されるデータ ソースが他のすべての構成よりも優先されることを意味します。
ノート:
(1) dao.mapper でのアノテーションの追加は無効です
(2) インターフェース Service クラスへのアノテーションの追加は無効です
(3) インターフェイス Service メソッドに追記 無効
ノート:
try catch が異常な場合、トランザクションはロールバックされません
2. AT トランザクション モード: Spring Cloud Alibaba マイクロサービス AT 分散トランザクション
1. Linux のファイアウォールを一時的に閉じるか、ポート アクセス許可を設定します。
systemctl stop firewalld
2. Nacos は mysql の永続性を使用するため、最初に mysql サービスを開始する必要があります (手動インストールまたは docker の起動、docker の起動では最初に docker を起動して mysql サービスをマウントする必要があります。そうしないと、docker または mysql を再起動すると mysql データが失われます)
3.まず nacos サービスを開始します
sh startup.sh -m standalone
スタンドアロン環境は -m スタンドアロン パラメータを使用して開始する必要があります。クラスタ環境はパラメータなしで開始します。
4. Seata サーバーが Nacos 登録センターを使用しているため、conf/registry.conf ファイルを構成する必要があります。nacos を選択します。
Seata Server の構成
Seata Server インストール ディレクトリの config/registry.conf で、構成モード (config.type) を Nacos に変更し、Nacos 構成センターの関連情報を構成します。
type="nacos" などの登録センターが使用されている場合は、nacos アプリケーション名アプリケーション、サービス登録アドレス serverAddr、グループ グループ、名前空間 namespace、クラスター クラスター、ユーザー名 username、およびパスワード password が正しいかどうかなどを確認します。 .
config {
# Seata 支持 file、nacos 、apollo、zk、consul、etcd3 等多种配置中心
#配置方式修改为 nacos
type = "nacos"
nacos {
#修改为使用的 nacos 服务器地址
serverAddr = "127.0.0.1:8848"
#配置中心的命名空间
namespace = ""
#配置中心所在的分组
group = "SEATA_GROUP"
#Nacos 配置中心的用户名
username = "nacos"
#Nacos 配置中心的密码
password = "nacos"
}
}
それ以外の場合、エラーが報告されます。
no available service found in cluster 'default', please make sure registry config correct and keep your seata server running
Seata クライアントの構成
Seata Client (つまり、マイクロサービス アーキテクチャのサービス) で、application.yml などの構成ファイルを使用して Nacos 構成センターを構成します。
#-----------------------------------------------------------
#单机版 tc server 配置
# Seata应用编号,默认为 ${spring.application.name}
seata.application-id=springcloud-order-seata
# Seata事务组编号,用于TC集群名,一般格式为:${spring.application.name}-group
seata.tx-service-group=springcloud-order-seata-group
# 注:虚拟组和分组的映射要写对,不然报错:
# no available service 'null' found, please make sure registry config correct
# 虚拟组和分组的映射 seata.service.vgroup-mapping.${seata.tx-service-group}=default
seata.service.vgroup-mapping.springcloud-order-seata-group=default
# 分组和Seata服务的映射,此处default指上面 seata.service.vgroup-mapping.springboot-seata-group 的值 default
#seata.service.grouplist.default=192.168.133.129:8091
# 存储模式 默认 file模式
seata.config.type=file
# 默认为 file
#seata.registry.type=file
#------------------------------------------------------------
#设置使用注册中心
#seata-spring-boot-starter 1.1版本少一些配置项
seata.enabled=true
seata.registry.type=nacos
# 集群
seata.registry.nacos.cluster=default
# 分组
seata.registry.nacos.group=SEATA_GROUP
# 应用名
seata.registry.nacos.application=seata-server
# 服务注册地址
seata.registry.nacos.server-addr=192.168.133.129:8848
ノート:!! !
Seata アプリケーション番号 seata.application-id、デフォルトは ${spring.application.name} です
TC クラスター名に使用される Seata トランザクション グループ番号 seata.tx-service-group。一般的な形式は次のとおりです。 ${spring.application.name}-group
Seata 仮想グループとグループ マッピング seata.service.vgroup-mapping.${seata.tx-service-group}=default
3 つの間の対応は正しく記述されている必要があります。そうしないと、エラーが報告されます。
no available service 'null' found, please make sure registry config correct
テスト アプリケーション
1. 4 つの SpringBoot モジュールを作成する
(1)springcloud-alibaba-2-seata-distributed-commons
<dependencies>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<!--spring-cloud-starter-openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
@FeignClient(name = "springcloud-alibaba-2-seata-distributed-account")
public interface FeignAccountService {
/**
* 扣除余额
*
* @param userId 用户ID
* @param money 扣减金额
* @throws Exception 失败时抛出异常
*/
@PostMapping("/account/reduceBalance")
void reduceBalance(@RequestParam("userId") Integer userId, @RequestParam("money") BigDecimal money);
}
@FeignClient(name = "springcloud-alibaba-2-seata-distributed-order")
public interface FeignOrderService {
/**
* 创建订单
*
* @param userId 用户ID
* @param productId 产品ID
* @return 订单编号
* @throws Exception 创建订单失败,抛出异常
*/
Integer createOrder(Integer userId, Integer productId) throws Exception;
}
@FeignClient(name = "springcloud-alibaba-2-seata-distributed-product")
public interface FeignProductService {
/**
* 减库存
*
* @param productId 商品ID
* @param amount 扣减数量
* @throws Exception 扣减失败时抛出异常
*/
@PostMapping("/product/reduceStock")
Product reduceStock(@RequestParam("productId") Integer productId, @RequestParam("amount") Integer amount);
}
(2)springcloud-alibaba-2-seata-distributed-order
知らせ:
1. 層ごとに例外をスローする必要がある サブサービス (GlobalExceptionHandler など) で例外を処理すると、seata は手動で例外を処理したと見なします。
2. トランザクションが失敗した場合は、まず RootContext.getXID() をチェックし、xid が渡され、一貫性があるかどうかを確認します。
3. メイン サービスには @GlobalTransactional のアノテーションを付けることができ、呼び出されたサービスに @GlobalTransactional と @Transactional を追加する必要はありません。
4. @GlobalTransactional(rollbackFor = Exception.class) は、rollbackFor = Exception.class を追加するのが最適です。これは、Exception がロールバックされることを意味します。そうしないと、一部の例外 (カスタム例外など) がロールバックされません。
@Slf4j //lombok
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/order")
public Integer createOrder(@RequestParam("userId") Integer userId,
@RequestParam("productId") Integer productId) throws Exception {
log.info("请求下单, 用户:{}, 商品:{}", userId, productId);
return orderService.createOrder(userId, productId);
}
}
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrdersMapper ordersMapper;
@Autowired
private FeignAccountService accountService;
@Autowired
private FeignProductService productService;
@Override
@GlobalTransactional //seata全局事务注解
public Integer createOrder(Integer userId, Integer productId) {
Integer amount = 1; // 购买数量暂时设置为 1
log.info("当前 XID: {}", RootContext.getXID());
//1、减库存
Product product = productService.reduceStock(productId, amount);
//2、减余额
accountService.reduceBalance(userId, product.getPrice());
//3、下订单
Orders order = new Orders();
order.setUserId(userId);
order.setProductId(productId);
order.setPayAmount(product.getPrice().multiply(new BigDecimal(amount)));
order.setAddTime(new Date());
ordersMapper.insertSelective(order);
//造成异常,测试是否回滚
int a = 10/0;
log.info("下订单: {}", order.getId());
// 返回订单编号
return order.getId();
}
}
<groupId>com.company</groupId>
<artifactId>springcloud-alibaba-2-seata-distributed-order</artifactId>
<version>1.0.0</version>
<name>springcloud-alibaba-2-seata-distributed-order</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
<spring-cloud-alibaba.version>2.2.7.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-cloud-starter-alibaba-sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- spring-cloud-starter-alibaba-seata
在 Spring Cloud 项目中,spring-cloud依赖 也会引入 seata-spring-boot-starter 依赖,在此排除
-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- seata-spring-boot-starter
注:服务端和客户端版本要一致,不然报错:
no available service 'default' found, please make sure registry config correct
-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
<!--统一通用项目,model类、openfeign接口-->
<dependency>
<groupId>com.company</groupId>
<artifactId>springcloud-alibaba-2-seata-distributed-commons</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<!-- dependencyManagement标签 通常适用于多模块环境下定义一个top module来专门管理公共依赖的情况
在子项目中不写该依赖项,那么子项目仍然会从父项目depenManagement中继承该artifactId和groupId依赖项(全部继承)
若子项目 中dependencies中的dependency声明了version,则父项目中dependencyManagement中的声明无效
Spring Cloud、Spring Cloud Alibaba 以及 Spring Boot 之间版本依赖参考官网
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
-->
<dependencyManagement>
<dependencies>
<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>
<!-- spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
ノート:!! !
seata-spring-boot-starter サーバーとクライアントのバージョンは一致している必要があります。そうでない場合、エラーが報告されます。
no available service 'default' found, please make sure registry config correct
server.port=8081
spring.application.name=springcloud-alibaba-2-seata-distributed-order
spring.datasource.url=jdbc:mysql://192.168.133.129:3306/orderdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
#nacos服务的注册与发现
spring.cloud.nacos.discovery.server-addr=192.168.133.129:8848
# 用户名、密码为默认时,测试发现不写 用户名、密码也可以
spring.cloud.nacos.username=nacos
spring.cloud.nacos.password=nacos
#-----------------------------------------------------------
#单机版 tc server 配置
# Seata应用编号,默认为 ${spring.application.name}
seata.application-id=springcloud-order-seata
# Seata事务组编号,用于TC集群名,一般格式为:${spring.application.name}-group
seata.tx-service-group=springcloud-order-seata-group
# 注:虚拟组和分组的映射要写对,不然报错:
# no available service 'null' found, please make sure registry config correct
# 虚拟组和分组的映射 seata.service.vgroup-mapping.${seata.tx-service-group}=default
seata.service.vgroup-mapping.springcloud-order-seata-group=default
# 分组和Seata服务的映射,此处default指上面 seata.service.vgroup-mapping.springboot-seata-group 的值 default
#seata.service.grouplist.default=192.168.133.129:8091
# 存储模式 默认 file模式
seata.config.type=file
# 默认为 file
#seata.registry.type=file
#------------------------------------------------------------
#设置使用注册中心
#seata-spring-boot-starter 1.1版本少一些配置项
seata.enabled=true
seata.registry.type=nacos
# 集群
seata.registry.nacos.cluster=default
# 分组
seata.registry.nacos.group=SEATA_GROUP
# 应用名
seata.registry.nacos.application=seata-server
# 服务注册地址
seata.registry.nacos.server-addr=192.168.133.129:8848
#feign超时时间设置
feign.client.config.default.connect-timeout=60000
feign.client.config.default.read-timeout=60000
(3)springcloud-alibaba-2-seata-distributed-product
@Slf4j
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
@Override
public Product reduceStock(Integer productId, Integer amount) throws Exception {
log.info("当前 XID: {}", RootContext.getXID());
// 检查库存
Product product = productMapper.selectByPrimaryKey(productId);
if (product.getStock() < amount) {
throw new Exception("库存不足");
}
// 扣减库存
int updateCount = productMapper.reduceStock(productId, amount);
// 扣除成功
if (updateCount == 0) {
throw new Exception("库存不足");
}
//造成异常,测试是否回滚
//int a = 10/0;
// 扣除成功
log.info("扣除 {} 库存成功", productId);
return product;
}
}
(4)springcloud-alibaba-2-seata-distributed-account
@Slf4j
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public void reduceBalance(Integer userId, BigDecimal money) throws Exception {
log.info("当前 XID: {}", RootContext.getXID());
// 检查余额
Account account = accountMapper.selectAccountByUserId(userId);
if (account.getBalance().doubleValue() < money.doubleValue()) {
throw new Exception("余额不足");
}
// 扣除余额
int updateCount = accountMapper.reduceBalance(userId, money);
// 扣除成功
if (updateCount == 0) {
throw new Exception("余额不足");
}
//造成异常,测试是否回滚
//int a = 10/0;
log.info("扣除用户 {} 余额成功", userId);
}
}
2. 最初に Nacos を起動し、次に Seata-Server を起動します
Nacos に登録されているサービスの一覧は次のとおりです。
ノート:
try catch が異常な場合、トランザクションはロールバックされません
三、遭遇する問題
1. seata サーバーの起動に問題があります
エラーは次のとおりです。
Failed to retry rollbacking [192.168.133.129:8091:702852926242021399] Unknown java.lang.RuntimeException: rm client is not connected.
dbkey:jdbc:mysql://localhost:3306/orderdb,clientId:springboot-seata:192.168.133.1:64279
以前のブロガーが AT トランザクション モード: 単一アプリケーション マルチデータ ソース分散トランザクションをテストしたため、データは /bin/sessionStore/root.data にロールバックされましたが、接続されたデータベースの URL が間違っていたので、それを変更するか直接削除してください
rm -rf root.data
再起動するだけ
seata サーバーの起動時にエラー レポートを参照することもできます。
https://blog.csdn.net/MinggeQingchun/article/details/126172351
2、利用可能なサービス「null」が見つかりません。レジストリ設定が正しいことを確認してください
利用可能なサービス「デフォルト」が見つかりません。レジストリ構成が正しいことを確認してください
クラスタ「デフォルト」で利用可能なサービスが見つかりません。レジストリ設定が正しいことを確認し、seata サーバーを実行し続けてください
プロジェクト開始時、SeataサービスをNacosに登録したため、以下の3つのエラーが発生しました。
(1)利用可能なサービス「null」が見つかりません。レジストリ設定が正しいことを確認してください
Seata アプリケーション番号 seata.application-id、デフォルトは ${spring.application.name} です
TC クラスター名に使用される Seata トランザクション グループ番号 seata.tx-service-group。一般的な形式は次のとおりです。 ${spring.application.name}-group
Seata 仮想グループとグループ マッピング seata.service.vgroup-mapping.${seata.tx-service-group}=default
3 つの間の対応は正しく記述されている必要があります。そうしないと、エラーが報告されます。
no available service 'null' found, please make sure registry config correct
(2)利用可能なサービス「デフォルト」が見つかりません。レジストリ設定が正しいことを確認してください
Blogger Seata Server はバージョン 1.4.2 を使用しています
<!-- spring-cloud-starter-alibaba-seata
在 Spring Cloud 项目中,spring-cloud依赖 也会引入 seata-spring-boot-starter 依赖,在此排除
-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- seata-spring-boot-starter
注:服务端和客户端版本要一致,不然报错:
no available service 'default' found, please make sure registry config correct
-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2/version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.2.0及以上版本</version>
</dependency>
seata-spring-boot-starter サーバーとクライアントのバージョンは一致している必要があります。そうでない場合、エラーが報告されます。
no available service 'default' found, please make sure registry config correct
(3)クラスター「デフォルト」で利用可能なサービスが見つかりません。レジストリ設定が正しいことを確認し、seata サーバーを実行し続けてください
type="nacos" などの登録センターを使用して、nacos アプリケーション名アプリケーション、サービス登録アドレス serverAddr、グループ グループ、名前空間 namespace、クラスター クラスター、ユーザー名 username、およびパスワード password が正しいかどうかなどを確認します。
config {
# Seata 支持 file、nacos 、apollo、zk、consul、etcd3 等多种配置中心
#配置方式修改为 nacos
type = "nacos"
nacos {
#修改为使用的 nacos 服务器地址
serverAddr = "127.0.0.1:8848"
#配置中心的命名空间
namespace = ""
#配置中心所在的分组
group = "SEATA_GROUP"
#Nacos 配置中心的用户名
username = "nacos"
#Nacos 配置中心的密码
password = "nacos"
}
}
それ以外の場合、エラーが報告されます。
no available service found in cluster 'default', please make sure registry config correct and keep your seata server running
3. @GlobalTransactional アノテーションが無効であり、トランザクションはロールされません
詳細については、を参照してください。
AOP を介して Seata 分散トランザクションを動的に作成/閉じる
spring-cloud-starter-alibaba-seata @GlobalTransactional の統合失敗の問題
openfeign+seata+zipkin リクエスト サービス スタック オーバーフロー例外問題、feign_Stranger のチャーム ブログ - CSDN ブログ
偽装がサービスを呼び出す、呼び出されたサービス seata トランザクションが開かれていない、または xid が空である - CSDN blog_seata ロールバックが有効にならない
Seata トランザクションはロールバックできません_slivloon のブログ - CSDN blog_seata トランザクションはロールバックされません