I.はじめに
次の一連の章を通して説明します。
docker-compose は Seata Server の高可用性デプロイメントを実装します | Spring Cloud 51
Seata AT モード理論の研究、トランザクション分離と部分的なソースコード分析 | Spring Cloud 52
Spring Boot が Seata を AT モード分散トランザクションと統合する例 | Spring Cloud 53
Seata XA モード理論の学習、使用方法、注意事項 | Spring Cloud54
Seata TCC モード理論の学習、本番レベルの使用例の構築と注意点 | Spring Cloud55
Seata TCC モードでの冪等性、一時停止、空のロールバックの問題を解決する | Spring Cloud56
Seata Saga モード理論の学習、本番レベルの使用例構築と注意点 (1) | Spring Cloud57
Seata Saga モード理論の学習、本番レベルの使用例構築と注意点 (2) | Spring Cloud58
Seataの4つのモード比較まとめ|Spring Cloud 59
私たちは、、 、、 およびトランザクション モードSeata
の理論的かつ多次元の比較と概要を完了し、ビジネス例を通じてそれらの使用法を深く理解しました。AT
XA
TCC
Saga
データベースとテーブルのシャーディングは、データベース拡張で最も一般的に使用される処理方法であり、最もShardingSphere
広く使用されているデータ シャーディング ミドルウェアとして、ShardingSphere
分散トランザクションをサポートしSeata
、データの一貫性を保証します。この記事では、データ シャーディング シナリオでShardingSphere
統合をSeata
使用して (リモート) 分散トランザクション呼び出しを行い、統合プロセス中に注意する必要がある事項について詳しく説明します。
不要なスペースを削減するために、この章は、次のように構成されている以前のいくつかの章の内容を適用します。ご自身で参照してください。
ShardingSphere
データシャーディングを実現し、関連コンテンツを実現します。
Spring Boot は ShardingSphere を統合してデータ断片化を実装します (1) | Spring Cloud 40
Spring Boot は ShardingSphere を統合してデータの断片化を実現します (2) | Spring Cloud 41
Spring Boot は ShardingSphere を統合してデータの断片化を実現します (3) | Spring Cloud 42
ShardingSphere 5.3
シリーズ構成アップグレードガイド:
ShardingSphere 5.3 シリーズ Spring 構成アップグレードガイド | Spring Cloud 47
Seata
AT
パターン分散トランザクションの例に関連するコンテンツを利用します。
docker-compose は Seata Server の高可用性デプロイメントを実装します | Spring Cloud 51
Seata AT モード理論の研究、トランザクション分離と部分的なソースコード分析 | Spring Cloud 52
Spring Boot が Seata を AT モード分散トランザクションと統合する例 | Spring Cloud 53
2. プロジェクトの全体構成
ディレクトリ構造の分析:
-
common-at
: エンティティ クラス、openfeign インターフェイス、統合例外処理などを含むパブリック モジュール。 -
account-at
: ユーザーのアカウント情報を照会/変更できるアカウント サービス。-resourcemanagerRM (Resource Manager)
ロールの場合。ブランチ トランザクションを管理し、
TC
フォーク トランザクションと対話してブランチ トランザクションのステータスを登録および報告し、ブランチ トランザクションのコミットまたはロールバックを駆動するリソース。 -
storage-at
: 倉庫サービス。商品の在庫数量を照会・変更できます。-resourcemanagerRM (Resource Manager)
ロールの場合。
上記の内容については、Spring Boot Integration Seata using AT Mode Distributed Transaction Example | Spring Cloud 53 を参照していただき、これを踏まえて変更はありません。
-
order-sharding-database-at
:オーダーサービス、ご注文いただけます。TM (Transaction Manager)
-トランザクションマネージャーロールの場合。この章の焦点は、
ShardingSphere
データ シャーディング ルールの定義、分散トランザクションの開始のための統合、およびリモート マイクロサービス呼び出しの作成に使用することですSeata
。
3. 構築時のシャーディングデータベースの注文
3.1 完全な依存関係
seata/openfeign-at/order-sharding-database-at/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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>openfeign-at</artifactId>
<groupId>com.gm</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-sharding-database-at</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.gm</groupId>
<artifactId>common-at</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<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>
</dependency>
<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>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>com.gm</groupId>
<artifactId>common-tcc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!-- 解决shardingsphere集成依赖版本冲突和算法依赖包 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.33</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-transaction-base-seata-at</artifactId>
<version>5.3.2</version>
<!-- 排除掉shardingsphere-transaction-base-seata-at默认的seata版本,以免版本不一致出现问题-->
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 注意一定要引入对版本,要引入spring-cloud版本seata,而不是springboot版本的seata-->
<!-- 引入spring-cloud-starter-alibaba-seata解决openfeign远程调用传递xid问题 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<!-- 排除掉springcloud默认的seata版本,以免版本不一致出现问题-->
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.6.1</version>
<!-- Could not deserialize ATN with version 4 (expected 3). -->
<exclusions>
<exclusion>
<groupId>org.antlr</groupId>
<artifactId>antlr4</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
各依存パッケージの上のコメントをご自身で確認してください。それぞれの状況が詳細に説明されています。
3.2 完全な設定ファイル
プロジェクト設定ファイルは以下の 4 つに分かれています。
- ブートストラップ.yml
- seta.conf
- レジストリ.conf
- レジストリ.conf
3.2.1 ブートストラップ.yml
server:
port: 3012
spring:
application:
name: @artifactId@
cloud:
nacos:
username: @nacos.username@
password: @nacos.password@
discovery:
server-addr: ${
NACOS_HOST:nacos1.kc}:${
NACOS_PORT:8848}
datasource:
driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
url: jdbc:shardingsphere:classpath:sharding.yaml
# 此处配置是为了实现利用openfeign传播xid
seata:
enabled: true
management:
endpoints:
web:
exposure:
include: '*'
logging:
level:
io.seata: info
# mybatis-plus配置控制台打印完整带参数SQL语句
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
予防:
seata.enabled=true
openfeign
この構成は、伝播するグローバルトランザクションの使用を実現するためのものですxid
。
3.2.2 seta.conf
client {
application.id = order-sharding-database-at
transaction.service.group = mygroup
}
Seata
関連する設定については、次のソース コードを参照してくださいorg.apache.shardingsphere.transaction.base.seata.at.SeataATShardingSphereTransactionManager
。
トランザクション マネージャーが初期化されるSeataATShardingSphereTransactionManager
と、構成ファイルが読み取られseata.conf
、上記の構成情報がロードされます。
3.2.3 レジストリ.conf
Seata
登録センターと構成センターの構成:
registry {
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "192.168.0.31:8848"
group = "DEFAULT_GROUP"
namespace = "a4c150aa-fd09-4595-9afe-c87084b22105"
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
type = "nacos"
nacos {
serverAddr = "192.168.0.31:8848"
namespace = "a4c150aa-fd09-4595-9afe-c87084b22105"
group = "DEFAULT_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}
3.2.4 シャーディング.yaml
shardingsphere
データ シャーディング ルールと分散トランザクション構成を定義します。
dataSources:
ds1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.0.35:3306/db1?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: '1qaz@WSX'
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
ds2:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.0.46:3306/db2?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: '1qaz@WSX'
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
rules:
- !TRANSACTION
defaultType: BASE
providerType: Seata
- !SHARDING
# 数据分片规则配置
tables:
# 指定某个表的分片配置,逻辑表名
t_share_order:
# 这个配置是告诉sharding有多少个库和多少个表及所在实际的数据库节点,由数据源名 + 表名组成(参考 Inline 语法规则)
actualDataNodes: ds$->{
1..2}.t_share_order
# 配置库分片策略
databaseStrategy:
# 用于单分片键的标准分片场景
standard:
# 分片列名称
shardingColumn: id
# 分片算法名称
shardingAlgorithmName: t_share_order_database_inline
# 分布式序列策略
keyGenerateStrategy:
# 自增列名称,缺省表示不使用自增主键生成器
column: id
# 分布式序列算法名称
keyGeneratorName: snowflake
# 分片算法配置
shardingAlgorithms:
# 分片算法名称
t_share_order_database_inline:
# 分片算法类型
type: INLINE
# 分片算法属性配置
props:
algorithm-expression: ds$->{
id % 2 + 1}
# 分布式序列算法配置(如果是自动生成的,在插入数据的sql中就不要传id,null也不行,直接插入字段中就不要有主键的字段)
keyGenerators:
# 分布式序列算法名称
snowflake:
# 分布式序列算法类型
type: SNOWFLAKE
props:
sql-show: true #显示sql
shardingsphere
データシャーディングについては、公式 Web サイトを参照してください:
https://shardingsphere.apache.org/document/5.3.2/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/sharding/
shardingsphere
分散トランザクションのサポートについては、公式 Web サイトを参照してください: https://shardingsphere.apache.org/document/5.3.2/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/transaction/
3.3 データソースの構成
sharding.yaml
で構成されたデータ シャーディング ルールと使用されるトランザクション モードによりSeata AT
、データ ソースは次のようになります。
3.3.1 ds1 テーブル作成ステートメント
create DATABASE db1;
user db1;
-- ----------------------------
-- Table structure for t_share_order
-- ----------------------------
DROP TABLE IF EXISTS `t_share_order`;
CREATE TABLE `t_share_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`commodity_code` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`count` int NULL DEFAULT 0,
`money` decimal(10, 2) NULL DEFAULT 0.00,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
-- 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';
3.3.2 ds2 テーブル作成ステートメント
create DATABASE db2;
user db2;
-- ----------------------------
-- Table structure for t_share_order
-- ----------------------------
DROP TABLE IF EXISTS `t_share_order`;
CREATE TABLE `t_share_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`commodity_code` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`count` int NULL DEFAULT 0,
`money` decimal(10, 2) NULL DEFAULT 0.00,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
-- 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';
3.4 関数の構築
3.4.1 スタートアップクラス
com/gm/seata/openfeign/OrderShardingDatabaseATApplication.java
:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients("com.gm.seata.openfeign.feign")
public class OrderShardingDatabaseATApplication {
public static void main(String[] args) {
SpringApplication.run(OrderShardingDatabaseATApplication.class, args);
}
}
3.4.2 エンティティクラス
com/gm/seata/openfeign/entity/Order.java
:
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
@Data
@TableName("t_share_order")
public class Order {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String userId;
private String commodityCode;
private int count;
private BigDecimal money;
}
予防:
- ここは、次の論理テーブル名に対応します
@TableName("t_share_order")
。sharding.yaml
- フィールド
userId
の注釈は@TableId(type = IdType.ASSIGN_ID)
、主キー生成戦略がmybatis-plus
組み込みのスノーフレーク アルゴリズムを使用し、必要な型が であるがLong
、そうではないことを示していますlong
(戦略は有効になりませんid=0
)。
3.4.3 マッパークラス
com/gm/seata/openfeign/mapper/OrderMapper.java
:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gm.seata.openfeign.entity.Order;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}
3.4.4 サービスクラス
com/gm/seata/openfeign/service/OrderService.java
:
public interface OrderService {
/**
* 创建订单
*
* @param userId
* @param commodityCode
* @param count
* @return
*/
boolean createOrder(String userId, String commodityCode, Integer count);
}
com/gm/seata/openfeign/service/impl/OrderServiceImpl.java
:
import com.gm.seata.openfeign.entity.Order;
import com.gm.seata.openfeign.feign.AccountServiceApi;
import com.gm.seata.openfeign.feign.StorageServiceApi;
import com.gm.seata.openfeign.mapper.OrderMapper;
import com.gm.seata.openfeign.service.OrderService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
OrderMapper orderMapper;
@Autowired
StorageServiceApi storageServiceApi;
@Autowired
AccountServiceApi accountServiceApi;
@Transactional
/*@GlobalTransactional*/
@Override
public boolean createOrder(String userId, String commodityCode, Integer count) {
String xid = RootContext.getXID();
log.info("全局事务 xid:{}", xid);
Order order = new Order();
order.setCount(count);
order.setCommodityCode(commodityCode);
order.setUserId(userId);
order.setMoney(new BigDecimal(count * 100.0));
int i = orderMapper.insert(order);
try {
storageServiceApi.deduct(commodityCode, count);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
try {
accountServiceApi.deduct(userId, new BigDecimal(count * 100.0));
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
return i == 1;
}
}
予防:
- 分散トランザクションを有効にするには、呼び出しメソッドに注釈を追加する必要があります。
@Transactional
現時点では、パターンのサポートが統合されているため、( ) -トランザクションマネージャーshardingsphere
ロールの呼び出しメソッドに注釈を追加することはできません。Seata AT
TM
Transaction Manager
@GlobalTransactional
参考ソースコードorg.apache.shardingsphere.transaction.base.seata.at.SeataATShardingSphereTransactionManager
:
トランザクション マネージャーを介してトランザクションを開くとSeataATShardingSphereTransactionManager
、既存のグローバル トランザクションが読み取られるか、新しいグローバル トランザクションが作成されるため、明示的に@GlobalTransactional
アノテーションを追加する必要はありません。
3.4.5 コントローラークラス
com/gm/seata/openfeign/controller/OrderController.java
:
import com.gm.seata.openfeign.service.OrderService;
import com.gm.seata.openfeign.util.ErrorEnum;
import com.gm.seata.openfeign.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class OrderController {
@Autowired
OrderService orderService;
/**
* 商品下单购买
*
* @param userId
* @param commodityCode
* @param count
* @return
*/
@RequestMapping(value = "buy", method = RequestMethod.GET)
public R<String> buy(@RequestParam("userId") String userId, @RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count) {
try {
orderService.createOrder(userId, commodityCode, count);
return R.ok("下单成功", "");
} catch (Exception e) {
e.printStackTrace();
int code = Integer.parseInt(e.getMessage());
return R.restResult("下单失败", code, ErrorEnum.getEnumByCode(code).getTitle());
}
}
}
4. テスト例
リクエストアドレス: http://127.0.0.1:3012/buy?userId=user1&count=2&commodityCode=iphone
リクエストごとに、残高から 200 元が差し引かれ、在庫が 2 つ差し引かれます。
4.1 グローバルトランザクションが正常に送信される
4.2 グローバルトランザクションのロールバックが成功する
このとき、t_share_order
ログから該当するデータソースのテーブルを確認すると、新たなデータが追加されていないことがわかります。