SpringCloud - Spring Cloud Alibaba の Seata 分散トランザクション サービス; AT トランザクション モード (20)

この記事を読む前に、以下を参照してください。

https://blog.csdn.net/MinggeQingchun/article/details/126100300

https://blog.csdn.net/MinggeQingchun/article/details/126176893

ATモード

シート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表 

シートATモード

-- 注意此处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 モードで作成する必要があるテーブルです。

シート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 トランザクションはロールバックされません

おすすめ

転載: blog.csdn.net/MinggeQingchun/article/details/126153368