Spring Cloud Alibaba uses Seata to solve distributed transactions

Posted by phpbeginer on Wed, 05 Jan 2022 17:35:53 +0100

Why do distributed transactions occur?

With the rapid development of business, website system often evolves from single architecture to distributed and micro service architecture, while for database, it changes from stand-alone database architecture to distributed database architecture. At this time, we will split a large application system into multiple application services that can be deployed independently. Remote cooperation between services is required to complete transaction operations. In a microservice project, a large project is usually divided into N sub projects, such as user center service, member center service, payment center service and a series of microservices. When facing various business requirements, it is inevitable that the user center service needs to call the member center service and the payment center service to generate the call link; The communication between services adopts RPC remote call technology, but each service has its own independent data source, that is, its own independent local transaction; When two services communicate with each other, two local transactions do not affect each other, resulting in the cause of distributed transactions.

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.

Seata core terms

TC (Transaction Coordinator) - Transaction Coordinator: maintains the status of global and branch transactions and drives global transaction commit or rollback.

TM (Transaction Manager) - transaction manager: defines the scope of global transactions: start global transactions, commit or roll back global transactions.

RM (Resource Manager) - Resource Manager: manages the resources of branch transaction processing, talks with TC to register branch transactions and report the status of branch transactions, and drives branch transaction submission or rollback.

AT mode working mechanism

According to the official instructions, AT mode is only supported by Java applications that access relational databases that support local ACID transactions through JDBC.

Evolution of the two-stage submission agreement:

  • Phase I: the business data and rollback log records are committed in the same local transaction, and the local lock and connection resources are released.
  • Phase II:
    • Commit asynchronously and complete very quickly.
    • Rollback performs reverse compensation through the rollback log of one stage.

For more details, please refer to the official documents: http://seata.io/zh-cn/docs/dev/mode/at-mode.html

Seata Server deployment

official Seata Configuration center information: https://github.com/seata/seata/blob/develop/script/config-center/config.txt
 official Seata Nacos Configure deployment script: https://github.com/seata/seata/blob/develop/script/config-center/config.txt
 Version information and Seata Server For the download address, please refer to the introduction document on the home page: https://gitee.com/SimpleWu/spring-cloud-alibaba-example

Description of Seata directory structure:

  • bin: run script
  • conf: configuration file
  • lib: dependent package

The current deployment mode adopts Nacos as the registration center and configuration center.

registry.conf

The configuration is located in the conf directory. Press the following comment area to modify it

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

  nacos {
    # Register to nacos app name
    application = "seata-server"
    # nacos ip
    serverAddr = "127.0.0.1:8848"
    # Group
    group = "EXAMPLE-GROUP"
    # Namespace
    namespace = "7e3699fa-09eb-4d47-8967-60f6c98da94a"
    # Cluster
    #cluster = "default"
    username = "nacos"
    password = "nacos"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file,nacos ,apollo,zk,consul,etcd3
  # Manage configuration using nacos
  type = "nacos"

  nacos {
    # nacos ip
    serverAddr = "127.0.0.1:8848"
    # Namespace
    namespace = "7e3699fa-09eb-4d47-8967-60f6c98da94a"
    # Group
    group = "EXAMPLE-GROUP"
    username = "nacos"
    password = "nacos"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    appId = "seata-server"
    apolloMeta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

The above content mainly modifies the registration center and configuration center to Nacos, and modifies the Nacos address, login account / login password, namespace and grouping;

Configure deployment to Nacos

Here, the config. File downloaded from the official website of Nacos is simplified Txt content, the configuration text downloaded from the official website. The following content marks that need to be modified and needs attention

#Focus of the transaction team
service.vgroupMapping.my_test_tx_group=default
#Service segment packet address
service.default.grouplist=127.0.0.1:8091
#Keep default
service.enableDegrade=false
#Keep default
service.disableGlobalTransaction=false
#If db mode is selected as the storage mode, the database
store.mode=db
#Need to modify
store.lock.mode=db
#Need to modify
store.session.mode=db
store.publicKey=
#Need to modify
store.db.datasource=druid
#Need to modify
store.db.dbType=mysql
#Need to modify
store.db.driverClassName=com.mysql.jdbc.Driver
#Need to modify
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
#Need to modify
store.db.user=root
#Need to modify
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
client.undo.dataValidation=true
#Need to modify
#jackson changed to kryo to solve the problem of database Datetime type
client.undo.logSerialization=kryo
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
log.exceptionRate=100
transport.serialization=seata
transport.compressor=none

The configuration needs to focus on service vgroupMapping. my_ test_ tx_ Group = default the configuration here must be consistent with that in the microservice application, which will be described later.

Because the time type is Seata rollback deserialization, the Date type cannot be deserialized successfully. The serialization method needs to be modified to solve the problem: client undo. logSerialization=kryo

After modifying all configurations, run nacos config. Downloaded from the official website SH file to the nacos configuration center:

# -h ip -p port - t namespace - g grouping
sh nacos-config.sh -h localhost -p 8848 -t 7e3699fa-09eb-4d47-8967-60f6c98da94a -g EXAMPLE-GROUP

After the configuration file is deployed, you can see the contents in the text in the configuration management interface with the Nacos namespace of 7e3699fa-09eb-4d47-8967-60f6c98da94a(dev).

Seata database

According to config Txt to create the Seata database and the following tables

CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(96),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;
Deploying Seata Server

When the above work is ready, enter the bin directory and run Seata server Bat (Windows user) / Seata server Sh (Linux user).

Seata application scenario simulation

Here is a user service user login successful after calling member services to add member integration scenarios.

Parent project transformation

Project Name: spring cloud Alibaba version parent, adding mybatis, seata serialization and other dependent version management.

<!-- properties Add version number -->
<!-- mybatis -->
<mybatis.plus.version>3.4.2</mybatis.plus.version>
<mybatis.plus.ds.version>2.5.4</mybatis.plus.ds.version>
<seata.serializer.kryo.version>1.3.0</seata.serializer.kryo.version>

<!-- dependencyManagement Add the following dependencies -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>${mybatis.plus.version}</version>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-serializer-kryo</artifactId>
    <version>${seata.serializer.kryo.version}</version>
</dependency>
Member service engineering transformation

Project Name: spring cloud Alibaba service member, increase the dependency between database and Seata, and increase the user member points interface.

pom.xml

 <!-- Seata  & mybatis-plus -->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<dependency>
	<groupId>io.seata</groupId>
	<artifactId>seata-serializer-kryo</artifactId>
</dependency>
<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>

bootstrap.yaml

#Note that the previously configured information is omitted here
#Note that the previously configured information is omitted here
#Note that the previously configured information is omitted here
#Note that the previously configured information is omitted here
#Database information configuration
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/member_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 123456
#Seata configuration
seata:
  enabled: true
  application-id: ${spring.application.name}
  #Configure the service corresponding to nacos vgroupMapping. my_ test_ tx_ group
  tx-service-group: 'my_test_tx_group'
  service:
    vgroup-mapping:
      #Configure the service corresponding to nacos vgroupMapping. my_ test_ tx_ The value of group is default
      my_test_tx_group: 'default'
  registry:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      namespace: ${spring.cloud.nacos.discovery.namespace}
      group: ${spring.cloud.nacos.discovery.group}
      #cluster: ${spring.cloud.nacos.discovery.cluster}
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      namespace: ${spring.cloud.nacos.discovery.namespace}
      group: ${spring.cloud.nacos.discovery.group}

matters needing attention:

  1. bootstrap. Seata in yaml The TX service group configuration item must configure the service in the nacos configuration center My corresponding to vgroupmapping_ test_ tx_ group. In other words, we must be consistent.
  2. bootstrap. Seata in yaml service. vgroup-mapping. my_ test_ tx_ The group configuration item must configure the corresponding service of the nacos configuration center vgroupMapping. my_ test_ tx_ The value of the group configuration.

If you don't pay attention to the above two points, the startup times will be: no available service 'default' found, please make sure registry config correct.

Create member_db database

Where undo_ The log table is a Seata rollback log table, which needs to be created in each business service database that uses Seata.

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_member_integral
-- ----------------------------
DROP TABLE IF EXISTS `t_member_integral`;
CREATE TABLE `t_member_integral`  (
  `ID` bigint(20) NOT NULL COMMENT 'Primary key',
  `USERNAME` varchar(55) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'User name',
  `INTEGRAL` int(11) DEFAULT NULL COMMENT 'integral',
  `CREDATE` datetime(0) DEFAULT NULL COMMENT 'time',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime(0) NOT NULL,
  `log_modified` datetime(0) NOT NULL,
  `ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

New member points CRUD

I add the following categories here, and the specific contents are familiar to everyone.

MemberIntegralController.java
IMemberIntegralBiz.java
IMemberIntegralBizImpl.java
MemberIntegralMapper.java
MemberIntegral.xml

Here, all the logic for increasing member points is written in the same class, memberintegralcontroller java

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.gitee.eample.member.service.biz.IMemberIntegralBiz;
import com.gitee.eample.member.service.domain.MemberIntegral;
import com.gtiee.example.common.exception.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

/**
 * User points
 *
 * @author wentao.wu
 */
@RestController
@RequestMapping("/member/integral")
public class MemberIntegralController {
    @Autowired
    private IMemberIntegralBiz memberIntegralBiz;

    @PostMapping("/login/{username}")
    public Response<Boolean> login(@PathVariable("username") String username) {
        // If you log in for the first time every day, you will add points. I won't judge here. Each call adds a new point record
        MemberIntegral memberIntegral = new MemberIntegral();
        memberIntegral.setId(IdWorker.getId());
        memberIntegral.setIntegral(10);//Fixed 10 points
        memberIntegral.setUsername(username);
        memberIntegral.setCredate(new Date());
        memberIntegralBiz.save(memberIntegral);
        return Response.createOk("Login new member points successfully!", true);
    }
}

Run memberserviceapplication Java starts the service. If you want to know whether the registration is successful:

First, you can see whether there is log output on the Seata Server side. The log content is mainly the database information of the registered business service.

Second, you can see whether the business service outputs the following logs. If the following logs are output, the Seata Server side registration is successful

2021-11-05 09:56:30.962  INFO 16420 --- [           main] i.s.c.r.netty.NettyClientChannelManager  : will connect to 2.0.4.58:8091
2021-11-05 09:56:30.962  INFO 16420 --- [           main] i.s.c.rpc.netty.RmNettyRemotingClient    : RM will register :jdbc:mysql://localhost:3306/member_db
2021-11-05 09:56:30.967  INFO 16420 --- [           main] i.s.core.rpc.netty.NettyPoolableFactory  : NettyPool create channel to transactionRole:RMROLE,address:2.0.4.58:8091,msg:< RegisterRMRequest{resourceIds='jdbc:mysql://localhost:3306/member_db', applicationId='service-member', transactionServiceGroup='my_test_tx_group'} >
User service engineering transformation

Project Name: spring cloud Alibaba service member, adding database and Seata dependency, user login interface and feign interface for calling member service points.

POM is omitted here due to the consistency of contents xml,bootstrap. XML (note that the database should be modified to serve users).

Create user_db database

Where undo_ The log table is a Seata rollback log table, which needs to be created in each business service database that uses Seata.

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `ID` bigint(20) NOT NULL COMMENT 'Primary key',
  `USERNAME` varchar(55) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'user name',
  `PWD` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'password',
  `ADDR` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'address',
  `LAST_LOGIN_DATE` datetime(0) DEFAULT NULL COMMENT 'Last login time',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'test1', '123456', '123', NULL);

-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime(0) NOT NULL,
  `log_modified` datetime(0) NOT NULL,
  `ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

New user login CRUD

I add the following categories here, and the specific contents are familiar to everyone.

UserController.java
IUserBiz.java
IUserBizImpl.java
UserMapper.java
UserMapper.xml
MemberInfoControllerClient.java

MemberInfoControllerClient.java

/**
 * service-member Service remote call interface
 *
 * @author wentao.wu
 */
@FeignClient(name = "service-member")
public interface MemberInfoControllerClient {
    /**
     * Login to send points
     *
     * @param username
     * @return
     */
    @PostMapping("/member/integral/login/{username}")
    Response<Boolean> login(@PathVariable("username")String username);
}

IUserBiz.java

public interface IUserBiz extends IService<User> {
    /**
     * The user logs in and gives the first login points
     *
     * @param command
     * @return
     */
    boolean login(UserLoginCommand command);

}

IUserBizImpl.java

package com.gitee.eample.user.service.biz;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gitee.eample.user.service.controller.command.UserLoginCommand;
import com.gitee.eample.user.service.dao.UserMapper;
import com.gitee.eample.user.service.domain.User;
import com.gitee.eample.user.service.feign.MemberInfoControllerClient;
import com.gtiee.example.common.exception.Response;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.Date;

@Service
public class IUserBizImpl extends ServiceImpl<UserMapper, User> implements IUserBiz {

    @Autowired
    private MemberInfoControllerClient client;

    @GlobalTransactional(name = "login_add_member_intergral",rollbackFor = Exception.class)//Open distributed transaction
    @Override
    public boolean login(UserLoginCommand command) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, command.getUsername())
                .eq(User::getPwd, command.getPwd());
        User loginUser = getOne(wrapper);
        if (ObjectUtils.isEmpty(loginUser)) {
            return false;
        }
        //Call the member login interface to increase points
        Response<Boolean> response = client.login(command.getUsername());
        if (response.isOk()) {//Points increased successfully, or points have been increased
            //The integration interface is called successfully. Modify the login time of the current user
            loginUser.setLastLoginDate(new Date());
            updateById(loginUser);
            //Assuming an exception occurs here, it is normal not only to modify the current user's login time, but also to roll back the new member points information
            int i = 0 / 0;
            return true;
        } else {
            //Failed to increase points
            return false;
        }
    }
}

UserController.java

import com.gitee.eample.user.service.biz.IUserBiz;
import com.gitee.eample.user.service.controller.command.UserLoginCommand;
import com.gtiee.example.common.exception.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * User Business Controller
 *
 * @author wentao.wu
 */
@RestController
@RequestMapping("/users/")
public class UserController {
    private Logger logger = LoggerFactory.getLogger(UserController.class);
    @Autowired
    private IUserBiz userBiz;

    @PostMapping("/login")
    public Response<Boolean> login(UserLoginCommand command) {
        try {
            boolean result = userBiz.login(command);
            if (result) {
                return Response.createOk("Login and give points successfully!", result);
            }else{
                return Response.createError("Account or password does not exist!", result);
            }
        } catch (Exception e) {
            logger.error("Login failed!", e);
            return Response.createError("The server is busy. Please try again later!", false);
        }
    }

}

Run the startup class userserviceapplication java.

After the successful service transformation, there are three main simulation scenarios:

  1. There are abnormal scenarios in distributed transaction processing: the integration interface is called successfully, and an exception occurs after modifying the current user's login time. The modification of the user table is rolled back, and the integration data corresponding to the new user of the member service is rolled back
  2. There is no exception scenario in distributed transaction processing: the integration interface is called successfully, an exception occurs after modifying the current user's login time, the modification of the user table is rolled back, and the new data of the user member is not rolled back, resulting in data exception here.
  3. Normal execution scenario: the integration interface is called successfully, no exception occurs after modifying the current user's login time, and all operations take effect.
There are scenarios where exceptions occur in distributed transaction processing

IUserBizImpl. Add the distributed transaction annotation @ GlobalTransactional(name = "login_add_member_intergral", rollbackFor = Exception.class) to the login method in Java. / / start the distributed transaction. Name is the attribute name and rollbackFor is the specified rollback exception.

First, insert a piece of user data in the user service table as the login user:

INSERT INTO `user_db`.`t_user`(`ID`, `USERNAME`, `PWD`, `ADDR`, `LAST_LOGIN_DATE`) VALUES (1, 'test1', '123456', '123', NULL);

And current member services t_ member_ There is no data in the integral table and the data has not been initialized. The current scenario operation will modify t_user.LAST_LOGIN_DATE, and to t_ member_ Insert data into the integral table; However, in the end, an exception causes the operation to fail, and there are distributed transaction annotations. At this time, all service DML operations will be rolled back.

Request user login interface:

View t after successful request_ User and t_ member_ There is still no change in integral:

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-nocj7h3o-1641200562406) (C: \ users \ administrator \ appdata \ roaming \ typora \ user images \ image-20211105103353597. PNG)]

Indicates that the distributed transaction processing is successful without any exceptions.

There is no exception scenario in distributed transaction processing

IUserBizImpl. The login method in Java comments out global transactions (distributed transactions) and modifies them to local transactions:

//@GlobalTransactional(name = "login_add_member_intergral",rollbackFor = Exception.class) / / start distributed transaction
@Transactional

Request user login interface:

The exception occurred at this time caused the user service to modify last_ LOGIN_ The date operation was rolled back successfully, but t_member_integral data is still inserted into the integral table and has not been rolled back:

Indicates that there is no distributed transaction under cross service invocation, which will lead to data inconsistency and transaction exceptions.

Normal execution scenario

IUserBizImpl. The login method in Java annotates the local transaction and changes it to a global transaction (Distributed Transaction). It doesn't matter if you change it here. The transaction is successful. There will be no problem whether you use local transactions or global transactions. Changing it to a global transaction here is mainly to verify that the global transaction will not affect anything:

@GlobalTransactional(name = "login_add_member_intergral",rollbackFor = Exception.class)//Open distributed transaction
//@Transactional

At the same time, remove the exception handling in the login method:

//Assuming an exception occurs here, it is normal not only to modify the current user's login time, but also to roll back the new member points information
int i = 0 / 0;

Request the user to log in to the interface. At this time, all operations are successful, and the user service modifies LAST_LOGIN_DATE succeeded and t_ member_ The data in the integral table is added successfully; There is no mapping here, wasting everyone's traffic.

summary

  • Undo needs to be included in the database corresponding to each business service_ Log table, which is mainly used to record the log of global transaction operations. In case of subsequent exceptions, Seata will compensate for transaction rollback through this log;
  • When Seata rolls back deserialization, the Date type cannot be deserialized, so the serialization of Seata should be modified to: kryo; (this problem will be completely solved after the release of version 1.5 of Seata)

Source code storage address

gitee: https://gitee.com/SimpleWu/spring-cloud-alibaba-example.git
cnblogs: https://www.cnblogs.com/SimpleWu
Continuously update directory: https://www.cnblogs.com/SimpleWu/p/15476427.html

Topics: Java Spring Cloud Microservices