Explanation: In a distributed architecture, a request requires multiple microservices to implement. When a request involves multiple microservices, the transaction problem becomes troublesome.
Problem Description
Now there are three services, namely account service, inventory service and order service. To generate an order, it is necessary to ensure whether the inventory of the product is sufficient and the balance of the account is sufficient. The actual operation is three statements (add order, modify account balance, and modify inventory quantity). These three statements form a transaction.
For example, 2.3 Insufficient inventory, modifying the inventory quantity of the database reports an error, at this time, the operation on the account table and the new order operation on the order table have been completed, and the normal business logic is to roll back all the operations related to this business.
Business code
Step1: Initial state of database related tables
Step2: Create order code
Step3: Send request
The requested quantity is greater than the stock quantity
Step4: request error
Inventory is a positive integer, the modified result is less than 0, and the modified error occurs;
Step5: Database status changes
Amount deducted, transaction not fully rolled back
solution
It can be solved using Seata technology . Seata is jointly open sourced by Ant Financial and Alibaba. It is committed to providing high-performance and simple medical distributed transaction services to solve distributed transaction problems.
Seata solves the problem of distributed transactions. Simply speaking, it establishes a TC (Transaction Coordinator) to maintain the status of global and branch transactions, and coordinate global transaction commit/rollback, as shown in the following figure:
The TM (Transaction Manager, Transaction Manager) in the figure can be understood as the scope of the @GlobalTransactional annotation mark, such as the new order method above; RM (Resource Manager, Resource Manager) represents the resources managed by the branch office, which can be simply understood as the database table controlled by the branch office;
The specific implementation of Seata distributed transactions is divided into four modes, the characteristics are as follows:
This article introduces the use of the first two modes in detail. For others, please refer to: http://t.csdn.cn/XZotl
Code
Step 1: Add dependencies
Before using Seata, add dependencies first, because each microservice needs to be added, and I add it to the parent module here.
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!--版本较低,1.3.0,因此排除-->
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<!--seata starter 采用1.4.2版本-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
Step 2: Set up the configuration file
Set the configuration file of seata, note that each service module needs to be added
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
# 参考tc服务自己的registry.conf中的配置
type: nacos
nacos: # tc
server-addr: 127.0.0.1:8848
namespace: ""
group: DEFAULT_GROUP
application: seata-tc-server # tc服务在nacos中的服务名称
cluster: SH
tx-service-group: seata-demo # 事务组,根据这个获取tc服务的cluster名称
service:
vgroup-mapping: # 事务组与TC服务cluster的映射关系
seata-demo: SH
When adding configuration, you can click "seata" to see if there is a prompt in the idea, and verify whether the dependency is added;
Step 3: Set up the seata configuration file
Set the configuration file of seata on nacos, refer to: http://t.csdn.cn/MOuRz for details;
Step 4: Create a database
Create a special database for seata to record transaction information, and the database name should be consistent with the database name configured on nacos
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
# 分支事务信息
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`resource_group_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`branch_type` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`status` tinyint(4) NULL DEFAULT NULL,
`client_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime(6) NULL DEFAULT NULL,
`gmt_modified` datetime(6) NULL DEFAULT NULL,
PRIMARY KEY (`branch_id`) USING BTREE,
INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
# 全局事务信息
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`application_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_service_group` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`timeout` int(11) NULL DEFAULT NULL,
`begin_time` bigint(20) NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime NULL DEFAULT NULL,
`gmt_modified` datetime NULL DEFAULT NULL,
PRIMARY KEY (`xid`) USING BTREE,
INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
# 全局锁信息
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
`row_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`xid` varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`branch_id` bigint(20) NOT NULL,
`resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`table_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`pk` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime NULL DEFAULT NULL,
`gmt_modified` datetime NULL DEFAULT NULL,
PRIMARY KEY (`row_key`) USING BTREE,
INDEX `idx_branch_id`(`branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
Step 5: Create a snapshot table
Create a table (updo_log) to record data snapshot information, which is created in the business database;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` longblob NOT NULL COMMENT 'rollback info',
`log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` datetime(6) NOT NULL COMMENT 'create datetime',
`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
AT mode (default)
schematic diagram
The AT mode is characterized by executing SQL first, and then rolling back the data according to the data snapshot when the branch transaction fails.
Step 1: Add configuration
The mode of adding distributed transactions in each service module is AT:
seata:
data-source-proxy-mode: AT
Step 2: Modify Notes
On the method that needs to add a transaction, modify the "@Transactional" annotation to "@GlobalTransactional"
Step 3: Start the test
The breakpoint is set on the line of code to determine whether the inventory quantity is greater than the requested quantity, and check the status of each table in the database
The state of each table
Let go of the breakpoint and check the status of each table. You can see that because the execution of the inventory judgment failed, the request quantity is greater than the inventory quantity, the transaction rolls back, and each table rolls back to the initial state, and the distributed transaction problem is solved.
summary
Because the AT mode is to execute the SQL statement first, so when multiple threads are concurrent, if the SQL statement is executed first, and other threads operate the database at this time, data dirty reading occurs. When using AT mode, the problem of data dirty reading needs to be solved.
Seata's solution is to introduce a global lock , that is, to establish a lock for the global transaction. After the branch transaction completes the operation of the database, other threads are not allowed to operate the data at this time. Only after the execution of the global transaction is completed, the lock is released.
(When breaking the point, add a global lock)
(let go of the breakpoint, release the lock)
XA mode
schematic diagram
The structure of XA mode is much clearer than that of AT mode, and it is very simple. The transaction coordinator coordinates and executes it in a unified manner. Once a branch transaction fails to be executed, all branch transactions are rolled back, as follows:
(normal circumstances)
(abnormal situation)
Step 1: Modify the configuration
Modify the configuration of each module, change the mode to XA , and restart all services;
seata:
registry:
data-source-proxy-mode: XA
Step 2: Start the test
Similarly, hit the breakpoint on the line of code to judge the inventory, and check the contents of each table;
For the other tables, it can be found that the content of the table is consistent with the XA implementation description ;
The breakpoint is removed, and the program reports an error;
No changes were found in the data, and the XA mode also solved the problem of distributed transactions;
summary
The XA mode needs to lock the database resources and wait for the end of the second phase to release them. The performance is poor, but the implementation is simple, and there is no need to consider the problem of dirty data reading.
Summarize
Seata can solve distributed transaction problems and provides four modes, AT is the default mode;