This is the ninth day of my participation in the August More text Challenge. For details, see: August More Text Challenge
Author: Tom brother wechat public account: Micro technology
The atomicity and persistence of transactions ensure that within a single transaction, multiple updates will either succeed or fail. Within a system, we can use database transactions to ensure data consistency. If a transaction spans multiple systems and multiple databases, it can’t be handled with a single database transaction.
This is where you need to introduce distributed transactions. There are many solutions for distributed transactions on the market. I have written an article on how to solve distributed transactions before.
This article focuses on Seata, alibaba’s open source framework, which has 20,000 stars on Github and is very popular!!
Introduction to the Seata framework
Seata is an open source distributed transaction solution dedicated 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.
Advantages:
-
Non-intrusive to business: that is, reduce the intrusion of distributed transaction problems brought by microservitization of technical architecture to business
-
High performance: Reduced performance costs associated with distributed transaction solutions
Overall mechanism of AT mode:
-
Phase 1: Service data and rollback log records are committed in the same local transaction, releasing local locks and connection resources.
-
Stage 2:
-
The rollback logs are automatically asynchronously cleared in batches.
-
By rolling back logs, compensation operations are automatically generated to complete data rollback.
Core principles:
Seata’s JDBC data source agent parses business SQL to organize data mirrors of business data before and after updates into rollback logs. Taking advantage of the ACID feature of local transactions, updates of business data and writes of rollback logs are committed in the same local transaction.
This ensures that any committed updates to the business data will have a corresponding rollback log.
If the TC decides to roll back the system globally, the TC informs the RM to roll back the system. The TC uses XID to find the corresponding rollback log record and generates reverse update SQL using the rollback record to perform the update rollback.
TCC mode, transaction support that does not depend on the underlying data resource:
-
One-stage PREPARE behavior: Calls custom prepare logic.
-
Two-phase COMMIT behavior: Calls custom commit logic.
-
Two-phase ROLLBACK behavior: Invokes custom ROLLBACK logic.
The TCC mode supports the integration of custom branch transactions into global transaction management.
Seata process
Seata has three basic components:
-
Transaction Coordinator (TC) : Maintains the state of global and branch transactions and drives global commit or rollback.
-
Transaction manager (TM) : Defines the scope of a global transaction: start a global transaction, commit or roll back a global transaction.
-
Resource Manager (RM) : Manages resources for branch transactions being processed, talks to TCS to register branch transactions and report branch transaction status, and drives commit or rollback of branch transactions.
The entire transaction process:
-
The TM requests the TC to start a global transaction. The global transaction is created successfully and a globally unique XID is generated
-
The XID is propagated in the context of the microservice invocation link
-
The RM registers the branch transaction with the TC and puts it under the jurisdiction of the xID-based global transaction
-
TM initiates a global commit or rollback resolution for XID to the TC
-
The TC schedules all branch transactions under the jurisdiction of XID to complete commit or rollback requests.
Example Demo
Install Seata Server in Docker mode
Pull mirror image:
docker pull seataio/seata-server
Copy the code
Starting an instance:
docker run --name seata-server -p 8091:8091 seataio/seata-server
Copy the code
Service scenario: The service logic of the user purchasing the product. The entire business logic is supported by three microservices:
-
Warehousing service: deducting inventory for a given item.
-
Order service: Create orders based on purchase requirements.
-
Account service: Deduct the balance from the user’s account.
To introduce POM dependencies first, Spring Boot provides an out-of-the-box starter component
< the dependency > < groupId > IO. Seata < / groupId > < artifactId > seata - spring - the boot - starter < / artifactId > < version > 1.0.0 < / version > </dependency>Copy the code
Four independent microservice projects were built, and the system was called through the restful interface to meet the business requirements
engineering | instructions | address |
---|---|---|
spring-boot-bulking-seata-business | Main service entry | http://127.0.0.1:8090/api/business/purchase/commit |
spring-boot-bulking-seata-storage | The inventory service | http://127.0.0.1:8083/api/storage/deduct?commodityCode=6666&count=1 |
spring-boot-bulking-seata-order | Order service | http://127.0.0.1:8082/api/order/debit?userId=101&commodityCode=6666&&count=1 |
spring-boot-bulking-seata-account | Account service | http://127.0.0.1:8081/account/debit?userId=101&orderMoney=10 |
The interaction between the systems is shown in the following figure:
Three databases were created for storage, Order, and Account microservices, and corresponding business tables were created in different libraries, as follows:
Database: db_seata_1 create table account ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` varchar(255) NOT NULL , 'money' int(11) DEFAULT 0, PRIMARY KEY (' id ')) ENGINE=InnoDB DEFAULT CHARSET= UTf8MB4 COMMENT=' account table '; insert into account(id,user_id,money) value (1,"101",500); insert into account(id,user_id,money) value (2,"102",500); Database: db_seata_2 create table `order`( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` varchar(255) NOT NULL , `commodity_code` varchar(255) , `count` int(11) default 0, `money` int(11) default 0, PRIMARY KEY (' id ') ENGINE=InnoDB DEFAULT CHARSET= UTf8MB4 COMMENT=' order table '; Database: db_seata_3 create table storage( `id` bigint(20) NOT NULL AUTO_INCREMENT, `commodity_code` varchar(255) default null, `count` int(11) default 0, PRIMARY KEY (`id`), Unique Key (' commodity_code ') ENGINE=InnoDB DEFAULT CHARSET= UTf8MB4 COMMENT=' inventory table '; insert into storage(id,commodity_code,count) value (1,'6666',1000)Copy the code
Undo_log roll back log table. Seata framework uses this table to roll back transaction data.
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Copy the code
Db_seata_1, db_seata_2, and db_seata_3. Create an undo_log table for each database.
The application. Properties configuration items of the three projects are similar. The following uses the Account project as an example:
spring.application.name=spring-boot-bulking-seata-account server.port=8081 Spring. The datasource. Url = JDBC: mysql: / / 127.0.0.1:3306 / db_seata_1? UseSSL = false&serverTimezone = UTC spring.datasource.username=root spring.datasource.password=111111 mybatis.mapper-locations=classpath*:mapper/*Mapper.xml Seata. Tx - service - group = my_test_tx_group seata. Service. Grouplist = 127.0.0.1:8091 logging level. IO. Seata = info logging.level.io.seata.samples.account.persistence.AccountMapper=debugCopy the code
BusinessController provides two API entries to simulate a successful order and an exception rollback
@GlobalTransactional public void purchase(String userId, String commodityCode, int orderCount) { LOGGER.info("purchase begin ... , xid: "+ rootContext.getxid ()); // Deduct inventory storageClient. Nt (commodityCode, orderCount); Orderclient. create(userId, commodityCode, orderCount); }Copy the code
Add @globalTransactional to the method to describe starting a global transaction
For example, if we use the Spring framework’s RestTemplate to access a remote service through an Http interface, how do we pass the transaction id XID across systems?
@Component public class StorageClient { @Autowired private RestTemplate restTemplate; Private static String storageURL = "HTTP: / / http://127.0.0.1:8083/api/storage/deduct? commodityCode=%s&count=%s"; Public void NT (String commodityCode, int orderCount) {system.out.println (" Invoke storage, xid: " + RootContext.getXID()); String url = String.format(storageURL, commodityCode, orderCount); try { ResponseEntity<String> result = restTemplate.getForEntity(url, String.class); System.out.println("[StorageClient] invoke, result=" + result.getBody()); } catch (Exception e) { log.error("deduct url {} ,error:", url, e); throw new RuntimeException(); }}}Copy the code
We rewrite the ClientHttpRequestInterceptor interceptors, and injected into the RestTemplate instance. Each time an Http request is sent, the XID is fetched from ThreadLocal and written to the Header before the request is sent to the target.
@Component public class RestTemplateInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest); String xid = RootContext.getXID(); if (StringUtils.isNotEmpty(xid)) { requestWrapper.getHeaders().add(RootContext.KEY_XID, xid); } return clientHttpRequestExecution.execute(requestWrapper, bytes); }}Copy the code
Test Case:
Seata transactions regardless of success or rolled back, will be physically deleted undo_log table records, in Order to validate the intermediate process, we are in the lower Order system of com. Weiguanjishu. Service. OrderService# create set a breakpoint, temporary interrupt request, Then look at the data changes for each table
Then release the breakpoint, the request succeeds, and look at the data of each table
Write at the end:
The biggest difference between Seata and other distributed transactions is that it commits each branch transaction in the first commit phase. Seata believes that in a normal business, each service commits a transaction with a high probability of success. This transaction commits saves both phases of lock holding time, thus improving overall execution efficiency.
The code address
https://github.com/aalansehaiyang/spring-boot-bulking a module: spring-boot-bulking-seata-business spring-boot-bulking-seata-storage spring-boot-bulking-seata-order spring-boot-bulking-seata-accountCopy the code
Author introduction: Tom brother, computer graduate student, recruited into Ali, P7 technology expert, patent, CSDN blog expert. In charge of e-commerce transactions, community fresh, traffic marketing, Internet finance and other businesses, many years of first-line team management experience