I. Introduction
Through the following series of chapters:
docker-compose implements high-availability deployment of Seata Server | Spring Cloud 51
Seata AT mode theory study, transaction isolation and partial source code analysis | Spring Cloud 52
Spring Boot integrates Seata with AT mode distributed transaction example | Spring Cloud 53
Seata XA mode theory learning, use and precautions | Spring Cloud54
Solve the problems of idempotence, suspension and empty rollback in Seata TCC mode | Spring Cloud56
Comparison summary of Seata's four modes | Spring Cloud 59
We have completed the theoretical and multi-dimensional comparison and summary of Seata
its AT
, XA
, TCC
, and transaction modes, and gained a deep understanding of their use through business examples.Saga
Sharding of databases and tables is the most commonly used processing method in database expansion. ShardingSphere
As the most widely used data sharding middleware, ShardingSphere
it supports distributed transactions Seata
and ensures data consistency. In this article, we use ShardingSphere
integration Seata
to make (remote) distributed transaction calls in data sharding scenarios, and explain in detail the matters that need to be paid attention to during the integration process.
In order to cut down unnecessary space, this chapter will apply the content of some previous chapters, which are organized as follows, please refer to it for yourself:
ShardingSphere
Realize data sharding and realize related content:
Spring Boot integrates ShardingSphere to implement data fragmentation (1) | Spring Cloud 40
Spring Boot integrates ShardingSphere to realize data fragmentation (2) | Spring Cloud 41
Spring Boot integrates ShardingSphere to realize data fragmentation (3) | Spring Cloud 42
ShardingSphere 5.3
Series configuration upgrade guide:
ShardingSphere 5.3 Series Spring Configuration Upgrade Guide | Spring Cloud 47
Seata
Utilize AT
pattern distributed transaction example related content:
docker-compose implements high-availability deployment of Seata Server | Spring Cloud 51
Seata AT mode theory study, transaction isolation and partial source code analysis | Spring Cloud 52
Spring Boot integrates Seata with AT mode distributed transaction example | Spring Cloud 53
2. Overall structure of the project
Directory structure analysis:
-
common-at
: Public module, including: entity class, openfeign interface, unified exception handling, etc. -
account-at
: Account service, which can query/modify the user's account information. for the -resourcemanagerRM (Resource Manager)
role .Resources that manage branch transactions,
TC
interact with fork transactions to register and report the status of branch transactions, and drive branch transaction commits or rollbacks. -
storage-at
: Warehousing service, you can query/modify the inventory quantity of the product. for the -resourcemanagerRM (Resource Manager)
role .
For the above content, please refer to: Spring Boot Integration Seata Using AT Mode Distributed Transaction Example | Spring Cloud 53 , based on this, there is no change.
-
order-sharding-database-at
: order service, you can place an order. ForTM (Transaction Manager)
the -Transaction Manager role.The focus of this chapter is to use
ShardingSphere
to define data sharding rules, integrate toSeata
start distributed transactions, and make remote microservice calls.
3. Order-sharding-database-at construction
3.1 Complete dependencies
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>
Please check the comments above each dependent package by yourself, and each situation has been described in detail.
3.2 Complete configuration file
Project configuration files are divided into the following four:
- bootstrap.yml
- seta.conf
- registry.conf
- registry.conf
3.2.1 bootstrap.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
Precautions:
seata.enabled=true
This configuration is to realize the use ofopenfeign
propagating global transactionsxid
.
3.2.2 seta.conf
client {
application.id = order-sharding-database-at
transaction.service.group = mygroup
}
Refer to the following source code for Seata
related configuration org.apache.shardingsphere.transaction.base.seata.at.SeataATShardingSphereTransactionManager
:
SeataATShardingSphereTransactionManager
When the transaction manager is initialized, it will read the configuration file seata.conf
and load the above configuration information.
3.2.3 registry.conf
Seata
Registration center and configuration center configuration:
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 sharding.yaml
shardingsphere
Define data sharding rules and distributed transaction configuration:
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
For data sharding, please refer to the official website:
https://shardingsphere.apache.org/document/5.3.2/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/sharding/
shardingsphere
For distributed transaction support, please refer to the official website: https://shardingsphere.apache.org/document/5.3.2/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/transaction/
3.3 Data source configuration
Due to sharding.yaml
the data sharding rules configured in and Seata AT
the transaction mode used, the data sources are as follows:
3.3.1 ds1 table creation statement
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 table creation statement
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 Function Construction
3.4.1 Startup class
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 Entity class
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;
}
Precautions:
- Here corresponds to the logical table name
@TableName("t_share_order")
insharding.yaml
userId
The annotation on the field@TableId(type = IdType.ASSIGN_ID)
indicates that the primary key generation strategy usesmybatis-plus
the built-in snowflake algorithm, and the required type isLong
, but notlong
(the strategy does not take effect,id=0
).
3.4.3 Mapper class
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 Service class
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;
}
}
Precautions:
- In order for distributed transactions to take effect, annotations must be added to the calling method
@Transactional
. At this time, due to the integrated support for patterns,shardingsphere
no annotations can be added to the calling method of the ( ) -transaction manager roleSeata AT
TM
Transaction Manager
@GlobalTransactional
Reference source code org.apache.shardingsphere.transaction.base.seata.at.SeataATShardingSphereTransactionManager
:
When opening a transaction through the transaction manager SeataATShardingSphereTransactionManager
, the existing global transaction will be read or a new global transaction will be created, so there is no need to explicitly add @GlobalTransactional
annotations.
3.4.5 Controller class
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. Example test
Request address: http://127.0.0.1:3012/buy?userId=user1&count=2&commodityCode=iphone
For each request, 200 yuan will be deducted from the balance and 2 inventory will be deducted
4.1 The global transaction is submitted successfully
4.2 Global transaction rollback succeeds
At this time, check t_share_order
the table of the corresponding data source according to the log, and find that no new data has been added.