This is the 21st day of my participation in Gwen Challenge

Seata

An overview,

http://seata.io/zh-cn/docs/overview/what-is-seata.html

Two, environment configuration

Download and install

  • Download address https://github.com/seata/seata/releases/tag/v1.0.0

  • Unpack the

  • Modify the file.conf configuration file under \seata\conf

    • The service module customizes the transaction namevgroup_mapping.my_test_tx_group = "fsp_tx_group"
    • Transaction log storage mode and database information
    service {
      #transaction service group mapping
      vgroup_mapping.my_test_tx_group = "fsp_tx_group"
      #only support when registry.type=file, please don't set multiple addresses
      default.grouplist = "127.0.0.1:8091"
      #disable seata
      disableGlobalTransaction = false
    }
    store {
    ## store mode: file, db
      mode = "db"
      ## database store property
      db {
        ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
        datasource = "dbcp"
        ## mysql/oracle/h2/oceanbase etc.
        db-type = "mysql"
        driver-class-name = "com.mysql.jdbc.Driver"
        url = "jdbc:mysql://localhost:3306/seata"
        user = "root"
        password = "root"
      }
    } 
    Copy the code
  • SQL > seata-server-0.9.0\seata\conf

  • Modify the registry. Conf configuration file

    registry {
    # File, NACOS, Eureka, Redis, ZK, Consul, ETCD3, SOFA
      type = "nacos"
      nacos {
        serverAddr = "localhost:8848"
        namespace = ""
        cluster = "default"
      }
    .
    }
    Copy the code
  • Start Nacos and Seata

SEATA’s distributed transaction solution

Business logic for users to purchase goods. The entire business logic is supported by three microservices:

  • Storage service: deduct the storage quantity for a given item.
  • Order service: Creates orders based on purchasing requirements.
  • Account service: deducts balances from user accounts.

Database preparation

CREATE DATABASE seata_order;
USE seata_order;
CREATE TABLE t_order(
    id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
    user_id BIGINT(11) DEFAULT NULL COMMENT 'user id',
    product_id BIGINT(11) DEFAULT NULL COMMENT 'product id',
    count INT(11) DEFAULT NULL COMMENT 'number',
    money DECIMAL(11.0) DEFAULT NULL COMMENT 'value',
    status INT(1) DEFAULT NULL COMMENT 'Order Status: 0 in creation, 1 completed'
)ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;

CREATE DATABASE seata_storage;
USE seata_storage;
CREATE TABLE t_storage(
    id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
    product_id BIGINT(11) DEFAULT NULL COMMENT 'product id',
    total INT(11) DEFAULT NULL COMMENT 'Gross inventory',
    used INT(11) DEFAULT NULL COMMENT 'Used stock',
    residue INT(11) DEFAULT NULL COMMENT 'Surplus stock'
)ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;
INSERT INTO t_storage(id, product_id, total, used, residue) VALUES(1.1.100.0.100);

CREATE DATABASE seata_account;
USE seata_account;
CREATE TABLE t_account(
    id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
    user_id BIGINT(11) DEFAULT NULL COMMENT 'user id',
    total DECIMAL(10.0) DEFAULT NULL COMMENT 'Total amount',
    used DECIMAL(10.0) DEFAULT NULL COMMENT 'Used amount',
    residue DECIMAL(10.0) DEFAULT 0 COMMENT 'Remaining available amount'
)ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;
INSERT INTO t_account(id, user_id, total, used, residue) VALUES(1.1.1000.0.1000);
Copy the code

Create rollback log table

The SQL file is in \seata-server-0.9.0\seata\conf\db_undo_log.sql

Create tables for all four databases

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,
  `ext` varchar(100) DEFAULT NULL.PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Copy the code

Microservice readiness

seata-order-service2001/seata-storage-service2002/seata-account-service2003

Rely on
<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        <exclusions>
            <exclusion>
                <groupId>io.seata</groupId>
                <artifactId>seata-all</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-all</artifactId>
        <version>0.9.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.16</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
</dependencies>
Copy the code
The configuration file
server:
  port: 2001
spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
      seata:
        # Change the transaction group name as specified in the configuration
        tx-service-group: fsp_tx_group
    nacos:
      discovery:
        server-addr: 127.0. 01.: 8848
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: JDBC: mysql: / / 47.95.226.96:3306 / seata? serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=UTF-8
    username: root
    password: admin
feign:
  hystrix:
    enabled: false
logging:
  level:
    io:
      seata: info
mybatis:
  mapper-locations: classpath:mapper/*.xml
Copy the code

Note: put the previously configuredfile.confandregistry.confCopied to theresourcesUnder the

Business code

test

Order module sends a GET request to http://localhost:2001/order/create? UserId = 1&productid =1&count=10&money=100, call Account module and storage module through Feign to reduce Account balance and inventory.

Existing problems

Now add a timeout operation to the Decrease method in the Account module

public void decrease(Long userId, BigDecimal money) {
    LOGGER.info("-- -- -- -- -- -- -- -- -- AccountServiceImpl deductions -- -- -- -- -- -- -- -- --");
    // Create timeout exception
    try {
        TimeUnit.SECONDS.sleep(20);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    accountDao.decrease(userId,money);
}
Copy the code

Continuing with the previous request, the order is created and the inventory is reduced, but the account balance is unchanged, violating the consistency of the data, using seATA distributed transaction resolution.

The solution

Annotate @globalTransactional at the entry point of the business

@GlobalTransactional
public void createOrder(Order order) {
    LOGGER.info("------ New order -------");
    orderDao.createOrder(order);
    LOGGER.info("------Storage inventory reduction -------");
    storageService.decrease(order.getProductId(), order.getCount());
    LOGGER.info("-- -- -- -- -- - the Account deductions -- -- -- -- -- -- --");
    accountService.decrease(order.getUserId(), order.getMoney());
    LOGGER.info("------ Modify order status -------");
    orderDao.updateOrder(order.getUserId(), 0);
    LOGGER.info("------end-------");
}
Copy the code

Implementation process

The default mode is AT mode.

There are two stages:

  • Phase 1: Parses the SQL and obtains two snapshots based on the SQL. One is pre-snapshot, which saves the data before the SQL execution, and the other is post-snapshot, which saves the data after the SQL execution.
  • Stage 2:
    • The snapshot is deleted
    • If the command fails, the snapshot will be rewritten to the database and then deleted.