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#
Copy 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
Copyregistry { # 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 content of config.txt downloaded from the official website of Nacos is simplified. The configuration text downloaded from the official website marks the contents that need to be modified and needs attention
Copy#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 the nacos-config.sh file downloaded from the official website and the text content to the Nacos configuration center last time:
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, 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
Create the Seata database according to the corresponding database connection information in config.txt, 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;
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.
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, increase the dependency between database and Seata, and increase the 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#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 service.vgroupmapping.my for nacos_ test_ tx_ group tx-service-group: 'my_test_tx_group' service: vgroup-mapping: #Configure service.vgroupmapping.my for nacos_ 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:
- The configuration item of seata.tx-service-group in bootstrap.yaml must configure my corresponding to service.vgroupMapping in the nacos configuration center_ test_ tx_ group. In other words, we must be consistent.
- Seata.service.vgroup-mapping.my in bootstrap.yaml_ test_ tx_ The group configuration item must configure the corresponding service.vgroupmapping.my of the nacos configuration center_ 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.
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 categories here, and the specific contents are familiar to everyone.
CopyMemberIntegralController.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
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) { // 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 to start 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
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, adding database and Seata dependency, user login interface and feign interface for calling member service points.
Since the contents are consistent, pom.xml and bootstrap.xml are omitted here (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.
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 categories here, and the specific contents are familiar to everyone.
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 { /** * Login to 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()) {//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
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 the startup class UserServiceApplication.java.
After the successful service transformation, there are three main simulation scenarios:
- 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
- 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.
- 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
In IUserBizImpl.java, the login method adds the distributed transaction annotation @ GlobalTransactional(name = "login_add_member_intergral",rollbackFor = Exception.class) / / start the distributed transaction, where 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:
CopyINSERT 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:
Indicates that the distributed transaction processing is successful without any exceptions.
There is no exception scenario in distributed transaction processing
The login method in IUserBizImpl.java annotates global transactions (distributed transactions) and modifies them to local transactions:
Copy//@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
The login method in IUserBizImpl.java annotates the local transaction and changes it to a global transaction (Distributed Transaction). It doesn't matter if it is changed here. The transaction is successful. There will be no problem whether the local transaction or the global transaction is used. Changing it to a global transaction here is mainly to verify that the global transaction will not affect anything:
Copy@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:
Copy//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)
Original link:
https://www.cnblogs.com/SimpleWu/p/15529920.html
Author: SimpleWu