Spring Cloud Eureka integrates Seata to realize distributed transactions

Posted by DeepakJ on Mon, 07 Mar 2022 11:20:27 +0100

1, Introduction to Seata

Seata is an open source distributed transaction solution, which is committed to providing high-performance and easy-to-use distributed transaction services. Seata will provide users with AT, TCC, SAGA and XA transaction modes to create a one-stop distributed solution for users.

2, Software version

Software

edition

address

JDK

1.8.0_271

Java Downloads | Oracle

Spring Boot

2.5.6

Spring Boot

Spring Cloud

2020.0.4

Spring Cloud

Seata

1.4.2

https://github.com/seata/seata/releases

3, Environment construction

3.1 installation of Seata

3.1.1. Download Seata server

Download seata-server-1.4.2 Zip, address: https://github.com/seata/seata/releases

3.1.2 installation of Seata server

Unzip seata-server-1.4.2 zip

3.1.3. seata database configuration

Create the database spring cloud seata Eureka and create the tables required by the seata service:

        

Database script required by Seata server:

        https://github.com/seata/seata/tree/v1.4.2/script/server/db

Database script required by Seata client:

        https://github.com/seata/seata/tree/v1.4.2/script/client/at/db

In Seata AT mode, the client only needs undo_log table. Next, create a new business table t_account,t_order,t_storage three business tables

If three tables are put into one database, you only need to create an undo_log table

If you split three tables into three databases, you need to create undo for each database_ Log table

 

3.1.4 configure Seata server

We need to modify the file in the seata-server-1.4.2/conf directory Conf and registry Conf, here we use the eureka registry to store the MySQL database used. The configuration is as follows

        file.conf configuration is as follows:

## transaction log store, only used in seata-server
store {
  ## store mode: file,db,redis
  mode = "db"
  ## rsa decryption public key
  publicKey = ""

  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.cj.jdbc.Driver"
    ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
    url = "jdbc:mysql://127.0.0.1:3306/spring-cloud-seata-eureka?rewriteBatchedStatements=true&characterEncoding=utf8&useSSL=false"
    user = "root"
    password = "root"
    minConn = 5
    maxConn = 30
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
  }
}

        registry.conf is configured as follows

registry {
  # file ,nacos ,eureka,redis,zk,consul,etcd3,sofa
  type = "eureka"

  eureka {
    serviceUrl = "http://127.0.0.1:8761/eureka"
    application = "default"
    weight = "1"
  }
}

config {
  # file,nacos ,apollo,zk,consul,etcd3
  type = "file"
  
  file {
    name = "file.conf"
  }
}

3.1.5 launch Eureka Registration Center

eureka is configured as follows:

server:
  port: 8761

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

Start eureka

3.1.6 start Seata service

Execute Seata server under the seata-server-1.4.2\bin directory bat (windows)/ seata-server.sh (linux or mac) starts the Seata service

 

open  http://localhost:8761/ , we can see that the seata service has been successfully registered in the eureka registry.  

4, Project architecture

4.1 project description

Business logic for users to purchase goods. The whole business logic is supported by three micro services:

  • Business service: the business logic of purchasing goods and the initiator of transactions.
  • Storage service: deduct the storage quantity for a given commodity.
  • Order service: create an order according to the purchase demand.
  • Account service: deduct the balance from the user account.

4.2 project structure

5, Project construction

5.1 project structure

5.2 table structure initialization

Initialize three tables: order, inventory and account

-- ----------------------------
-- Table structure for t_account
-- ----------------------------
DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL DEFAULT 0,
  `money` bigint(20) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4;

-- ----------------------------
-- Records of t_account
-- ----------------------------
INSERT INTO `t_account` VALUES (1, 10001, 10000);

-- ----------------------------
-- Table structure for t_order
-- ----------------------------
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL DEFAULT 0,
  `commodity_code` varchar(20) CHARACTER SET utf8mb4 NOT NULL DEFAULT '',
  `count` int(10) NOT NULL DEFAULT 0,
  `money` bigint(20) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4;

-- ----------------------------
-- Records of t_order
-- ----------------------------

-- ----------------------------
-- Table structure for t_storage
-- ----------------------------
DROP TABLE IF EXISTS `t_storage`;
CREATE TABLE `t_storage`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(50) CHARACTER SET utf8mb4  NOT NULL DEFAULT '',
  `commodity_name` varchar(255) CHARACTER SET utf8mb4 NOT NULL DEFAULT '',
  `count` int(11) NOT NULL DEFAULT 0,
  `price` bigint(20) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `commodity_code`(`commodity_code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4;

-- ----------------------------
-- Records of t_storage
-- ----------------------------
INSERT INTO `t_storage` VALUES (1, '10001', 'iPhone', 100, 100);

5.3. Service construction

5.3.1 pom dependency

POM of order service, storage service and account service Like the dependency of XML, the business service service does not need to connect to the database, so it does not need to reference mybatis plus boot starter and MySQL connector Java

   <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</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-seata</artifactId>
    </dependency>

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

5.3.2,application.yml

Application of order service, storage service and account service YML is basically the same, and the following areas need to be modified:

server.port

spring.application.name

mybatis-plus.type-aliases-package

seata.application-id

Order service yml configuration

server:
  port: 9001

spring:
  application:
    name: order-service
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/spring-cloud-seata-eureka?characterEncoding=utf-8
    username: root
    password: root

eureka:
  instance:
    instance-id: order-service
    prefer-ip-address: true
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

mybatis-plus:
  global-config:
    banner: false
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #Open sql log
    map-underscore-to-camel-case: true # Open hump
  type-aliases-package: com.seata.order.entity  #Define the package of aliases of all operation classes
  mapper-locations: classpath:mapper/*Mapper.xml

# Seata Config
seata:
  application-id: order-service
  tx-service-group: my_test_tx_group
  service:
    vgroup-mapping:
      # The configuration here corresponds to the Server side configuration registry eureka. Value of application
      my_test_tx_group: default 
  registry:
    type: eureka
    eureka:
      service-url: http://localhost:8761/eureka
      weight: 1

The business service service does not need to connect to the database, so there is no need to configure the datasource and mybatis plus nodes

server:
  port: 9000

spring:
  application:
    name: business-service

eureka:
  instance:
    instance-id: business-service
    prefer-ip-address: true
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

# Seata Config
seata:
  application-id: business-service
  tx-service-group: my_test_tx_group
  service:
    vgroup-mapping:
      # The configuration here corresponds to the Server side configuration registry eureka. Value of application
      my_test_tx_group: default
  registry:
    type: eureka
    eureka:
      service-url: http://localhost:8761/eureka
      weight: 1

5.3.3. seata configuration description (this step does not need to be configured, but just explain the above configuration)

registry. The content in conf has been configured to application YML, so you don't need to reference registry Conf file

If you don't want to apply To configure the content related to seata in YML, you only need to file the script related to seata conf,registry. Copy conf to the project resource directory

File address: https://github.com/seata/seata/tree/v1.4.2/script/client/conf

Where: Registry Conf is modified as follows:

registry {
  # file ,nacos ,eureka,redis,zk,consul,etcd3,sofa,custom
  type = "eureka"

  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    weight = "1"
  }
}

config {
  # file,nacos ,apollo,zk,consul,etcd3,springCloudConfig,custom
  type = "file"

  file {
    name = "file.conf"
  }
}

5.3.4 warehousing services

    /**
     * Deduct inventory
     */
    int deductStorage(String commodityCode, int count);
    
    /**
     * Deduct inventory
     *
     * @param commodityCode Commodity code
     * @param count         quantity
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deductStorage(String commodityCode, int count) {
        log.info("[Inventory service]>------>Start of inventory deduction");
        storageMapper.deductStorage(commodityCode, count);
        log.info("[Inventory service]>------>End of inventory deduction");
        return count;
    }

5.3.5 order service

    /**
     * Create order
     */
    Long createOrder(Long userId, String commodityCode, int count);
    
    /**
     * Create order
     *
     * @param userId
     * @param commodityCode
     * @param count
     * @return
     */
    @Override
    public Long createOrder(Long userId, String commodityCode, int count) {
        log.info("[Order service]>------>Create order start");
        //Deduction of account balance
        Long price = storageService.selectPrice(commodityCode);
        Long money = price * count;
        // Create order
        Order order = new Order();
        order.setUserId(userId);
        order.setCommodityCode(commodityCode);
        order.setMoney(money);
        order.setCount(count);
        orderMapper.insert(order);
        // Deduction account
        log.info("[Order service]>------>Start of deduction account");
        accountService.deductAccount(order.getUserId(), money);
        log.info("[Order service]>------>End of account deduction");

        log.info("[Order service]>------>End of order creation");
        return order.getId();
    }

5.3.6 account services

    /**
     * Deduction account
     */
    Long deductAccount(Long userId, Long money);

    /**
     * Deduction account
     * @param userId
     * @param money
     * @return
     */
    @Override
    public Long deductAccount(Long userId, Long money) {
        log.info("[Account services]>------>Start of deduction account");
        if (10000 == userId) {
            throw new RuntimeException("[Inventory service]>------>Deduction inventory exception");
        }
        accountMapper.deductAccount(userId, money);
        log.info("[Account services]>------>Failed to deduct account");
        return money;
    }

5.3.7 purchase business logic

    /**
     * Deduct inventory - create order
     *
     * @param userId        User Id
     * @param commodityCode Commodity code
     * @param count         quantity
     */
    @Override
    @GlobalTransactional(timeoutMills = 10000, name = "spring-cloud-seata", rollbackFor = Exception.class)
    public Long purchase(Long userId, String commodityCode, int count) {
        log.info("Start global transaction, XID = " + RootContext.getXID());

        log.info("[Procurement services]>------>Start of inventory deduction");
        storageService.deductStorage(commodityCode, count);
        log.info("[Procurement services]>------>End of inventory deduction");

        log.info("[Procurement services]>------>Create order start");
        Long orderId = orderService.createOrder(userId, commodityCode, count);
        log.info("[Procurement services]>------>End of order creation");

        return orderId;
    }

5.3.8. Check service startup

 

order

 

 

 

 

5.3 distributed transaction test

5.3.1 normal business logic test

postman test:

Request address: http://localhost:9000/business/purchase?userId=10001&commodityCode=10001&count=1

In the database: there is already a piece of data in the order table, and the inventory quantity of goods in the inventory table is reduced by 1

 

business

order 

 

 

 

 

 

 

5.3.1 abnormal business logic test

 

 

 

 

 GitHub - jeespring/spring-cloud-seata

Topics: Distribution Spring Cloud eureka seata