current position:Home>Spring cloud Alibaba uses Seata to resolve the whole process of distributed transactions

Spring cloud Alibaba uses Seata to resolve the whole process of distributed transactions

2022-01-26 21:53:31 A sharer who loves Java


    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

The above content mainly modifies the registration center and configuration center as Nacos And modified Nacos Address and login account / The login password , Namespace , grouping ;

Configure deployment to Nacos

It's simplified here Nacos Downloaded from the official website config.txt Content , The configuration text downloaded from the official website marks the following contents that need to be modified and needs attention

Copy# Transaction group   Focus on 
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
# Storage mode selection  db The schema is the database 
store.mode=db
# Need to be revised 
store.lock.mode=db
# Need to be revised 
store.session.mode=db
store.publicKey=
# Need to be revised 
store.db.datasource=druid
# Need to be revised 
store.db.dbType=mysql
# Need to be revised 
store.db.driverClassName=com.mysql.jdbc.Driver
# Need to be revised 
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
# Need to be revised 
store.db.user=root
# Need to be revised 
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 be revised 
#jackson Change it to kryo  Solve database Datetime Type problem 
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

This 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 Date Type could not be deserialized successfully , The serialization method needs to be modified to solve this problem : client.undo.logSerialization=kryo

After modifying all configurations, run the... Downloaded from the official website nacos-config.sh The file will the text content to nacos In configuration center :

Copy# -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, it is displayed in Nacos Namespace is 7e3699fa-09eb-4d47-8967-60f6c98da94a(dev) The content in the text can be seen in the configuration management interface of .

Spring Cloud Alibaba Use Seata Resolve the whole process of distributed transactions

Seata database

according to config.txt Create the corresponding database connection information in Seata Database and create the following tables

CopyCREATE 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;

Deploy Seata Server

The above work is ready , Get into bin Directory operation seata-server.bat(windows user )/seata-server.sh(linux user ) that will do .

Seata Application scenario simulation #

Here is a user service. After user login, member service is added to increase membership score. .

Parent project transformation

Project name :spring-cloud-alibaba-version-parent, increase mybatis,seata Serialization depends on version management .

Copy<!-- 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, Add database and Seata rely on , Add user member points interface .

pom.xml

Copy <!-- 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

Copy# Be careful , The previously configured information is omitted here ....
# Be careful , The previously configured information is omitted here ....
# Be careful , The previously configured information is omitted here ....
# Be careful , 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 To configure 
seata:
  enabled: true
  application-id: ${spring.application.name}
  # Corresponding nacos To configure  service.vgroupMapping.my_test_tx_group
  tx-service-group: 'my_test_tx_group'
  service:
    vgroup-mapping:
      # Corresponding nacos To configure  service.vgroupMapping.my_test_tx_group  Value  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.yaml in seata.tx-service-group Configuration items must be configured nacos In configuration center service.vgroupMapping Corresponding my_test_tx_group. In other words, be consistent .
  2. bootstrap.yaml in seata.service.vgroup-mapping.my_test_tx_group Configuration items must be configured nacos The configuration center corresponds to service.vgroupMapping.my_test_tx_group The value of the configuration .

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

establish member_db database

among undo_log The table is Seata Rollback log table , Need to use... In each Seata All business service databases need to be created .

CopySET 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 classes here , Everyone is familiar with the specific content .

CopyMemberIntegralController.java
IMemberIntegralBiz.java
IMemberIntegralBizImpl.java
MemberIntegralMapper.java
MemberIntegral.xml

Here, all the logic of increasing member points is written in the same class MemberIntegralController.java

Copyimport 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) {
        //  The first login every day will add points , I don't judge here , Each call adds an integral record 
        MemberIntegral memberIntegral = new MemberIntegral();
        memberIntegral.setId(IdWorker.getId());
        memberIntegral.setIntegral(10);// Fix 10 integral 
        memberIntegral.setUsername(username);
        memberIntegral.setCredate(new Date());
        memberIntegralBiz.save(memberIntegral);
        return Response.createOk(" Login new member points successfully !", true);
    }
}

function MemberServiceApplication.java Start the service , If you want to know if you have registered successfully :

First, you can see Seata Server Whether there is log output on the client , The log mainly contains 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 Seata Server End registration succeeded

Copy2021-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, Add database and Seata rely on , Add user login interface , Add the interface for calling member service points feign.

Due to the consistency of the contents, the following items are omitted pom.xml,bootstrap.xml( Note that the database should be modified to serve users ).

establish user_db database

among undo_log The table is Seata Rollback log table , Need to use... In each Seata All business service databases need to be created .

Copy
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 classes here , Everyone is familiar with the specific content .

CopyUserController.java
IUserBiz.java
IUserBizImpl.java
UserMapper.java
UserMapper.xml
MemberInfoControllerClient.java

MemberInfoControllerClient.java

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

IUserBiz.java

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

}

IUserBizImpl.java

Copypackage 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()) {// Increase points successfully , Or points have been added 
            // The integration interface is called successfully , Modify the current user login time 
            loginUser.setLastLoginDate(new Date());
            updateById(loginUser);
            // Suppose an exception occurs here , It is normal to not only modify the current user's login time, but also roll back the new member points information 
            int i = 0 / 0;
            return true;
        } else {
            // Failed to increase points 
            return false;
        }
    }
}

UserController.java

Copyimport 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 startup class UserServiceApplication.java.

After the service transformation is successful, three scenarios are simulated :

  1. There are scenarios where exceptions occur in distributed transaction processing : The integration interface is called successfully , An exception occurred after modifying the current user login time , The modification of user table is rolled back , At the same time, the point data corresponding to the new users of the member service is also rolled back
  2. There is no exception scenario in distributed transaction processing : The integration interface is called successfully , An exception occurred after modifying the current user login time , The modification of user table is rolled back , The data added by the user member is not rolled back , Data exception caused here .
  3. Normal execution scenario : The integration interface is called successfully , No exception occurred after modifying the current user login time , All operations take effect .

There are scenarios where exceptions occur in distributed transaction processing

IUserBizImpl.java in login Method to add distributed transaction annotations @GlobalTransactional(name = "login_add_member_intergral",rollbackFor = Exception.class)// Open distributed transaction ,name Is the property name ,rollbackFor Rollback exception specified for .

First, insert a piece of user data into the user service table , As a login user :

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

Spring Cloud Alibaba Use Seata Resolve the whole process of distributed transactions

And current member services t_member_integral There is no data in the table, and the data has not been initialized , The current scene operation will be modified t_user.LAST_LOGIN_DATE, And to the t_member_integral Insert data into the table ; But in the end, an exception occurs, causing the operation to fail , And there are distributed transaction annotations , All services will be rolled back DML operation .

Request user login interface :

Spring Cloud Alibaba Use Seata Resolve the whole process of distributed transactions

After the request is successful, view t_user And t_member_integral Still nothing has changed :

Spring Cloud Alibaba Use Seata Resolve the whole process of distributed transactions

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

There is no exception scenario in distributed transaction processing

IUserBizImpl.java in login Method to comment out the global transaction ( Distributed transactions ), And change to local transaction :

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

Request user login interface :

Spring Cloud Alibaba Use Seata Resolve the whole process of distributed transactions

The exception occurred at this time caused the modification in the user service LAST_LOGIN_DATE The operation was rolled back successfully , however t_member_integral Integral data is still inserted in the table and has not been rolled back :

Spring Cloud Alibaba Use Seata Resolve the whole process of distributed transactions

Spring Cloud Alibaba Use Seata Resolve the whole process of distributed transactions

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

Normal execution scenario

IUserBizImpl.java in login Method to comment out local transactions , And change it to global transaction ( Distributed transactions ), It doesn't matter whether it's changed here , Transactions are successful , There is no problem with local and global transactions , The change to global transaction here is mainly to verify that the global transaction will not affect anything :

[email protected](name = "login_add_member_intergral",rollbackFor = Exception.class)// Open distributed transaction 
//@Transactional

At the same time login The exception handling in the method removes :

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

Request user login interface , At this point, all operations are successful , User service modification LAST_LOGIN_DATE success , also t_member_integral The data in the table is added successfully ; There is no mapping here , Waste everyone's traffic .

summary #

  • The database corresponding to each business service needs to contain undo_log surface , This table is mainly used to record the log of global transaction operations , Subsequent exceptions Seata Transaction rollback compensation will be performed through this log ;
  • Seata When rolling back deserialization Date Type cannot be deserialized , So revise Seata The serialization of is :kryo;( This question will be 1.5 edition Seata Completely solve after release )

copyright notice
author[A sharer who loves Java],Please bring the original link to reprint, thank you.
https://en.cdmana.com/2022/01/202201262153305456.html

Random recommended