ShardingSphere 5.3 integrates Seata distributed transactions | Spring Cloud 61

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

Seata TCC mode theory learning, production-level use example construction and precautions | Spring Cloud55

Solve the problems of idempotence, suspension and empty rollback in Seata TCC mode | Spring Cloud56

Seata Saga mode theory learning, production-level use example construction and precautions (1) | Spring Cloud57

Seata Saga mode theory learning, production-level use example construction and precautions (2) | Spring Cloud58

Comparison summary of Seata's four modes | Spring Cloud 59

We have completed the theoretical and multi-dimensional comparison and summary of Seataits 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, ShardingSphereit supports distributed transactions Seataand ensures data consistency. In this article, we use ShardingSphereintegration Seatato 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:

ShardingSphereRealize 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.3Series configuration upgrade guide:

ShardingSphere 5.3 Series Spring Configuration Upgrade Guide | Spring Cloud 47

SeataUtilize ATpattern 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

insert image description hereinsert image description here
insert image description here
insert image description here

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, TCinteract 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. For TM (Transaction Manager)the -Transaction Manager role.

    The focus of this chapter is to use ShardingSphereto define data sharding rules, integrate to Seatastart 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=trueThis configuration is to realize the use of openfeignpropagating global transactions xid.

3.2.2 seta.conf

client {
    
    
    application.id = order-sharding-database-at
    transaction.service.group = mygroup
}

Refer to the following source code for Seatarelated configuration org.apache.shardingsphere.transaction.base.seata.at.SeataATShardingSphereTransactionManager:

insert image description here

SeataATShardingSphereTransactionManagerWhen the transaction manager is initialized, it will read the configuration file seata.confand load the above configuration information.

3.2.3 registry.conf

SeataRegistration 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

shardingsphereDefine 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

shardingsphereFor 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/

shardingsphereFor 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.yamlthe data sharding rules configured in and Seata ATthe 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
  • userIdThe annotation on the field @TableId(type = IdType.ASSIGN_ID)indicates that the primary key generation strategy uses mybatis-plusthe built-in snowflake algorithm, and the required type is Long, but not long(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, shardingsphereno annotations can be added to the calling method of the ( ) -transaction manager roleSeata ATTMTransaction Manager@GlobalTransactional

Reference source code org.apache.shardingsphere.transaction.base.seata.at.SeataATShardingSphereTransactionManager:

insert image description here
insert image description here

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 @GlobalTransactionalannotations.

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

insert image description here

4.2 Global transaction rollback succeeds

insert image description here

At this time, check t_share_orderthe table of the corresponding data source according to the log, and find that no new data has been added.

Guess you like

Origin blog.csdn.net/ctwy291314/article/details/131487332