Contents of this article:

  • demand
  • What are distributed transactions
  • Distributed transaction solutions
  • What is Seata?
  • The preparatory work
  • Code demonstration
  • Start the service feature demo
  • Seata transaction grouping description
  • Seata distributed transaction principle explained
  • Project source code address

Back-end tools and environments

  • IDE:IDEA
  • Registry: NACOS 1.1.3
  • Spring Cloud:Greenwich.SR3
  • Speing Alibaba Cloud: 2.1.1. RELEASE
  • Seata: 0.9.0
  • MybatisPlus: 3.2.0

A demand.

Working on my open source project prex , to join the workflow, solving the problem of workflow user synchronization with the current users of the system, involving the remote invocation transaction problem produced by two database operation, such as the system user when increase users synchronizing workflow, users of the system to add success, workflow user does not have added successfully, the data inconsistency problem, local affairs cannot be rolled back, then use the distributed Transaction solution. Open source project :gitee.com/kaiyuantuan…

What are distributed transactions?

A large operation consists of different small operations distributed on different servers. Distributed transactions need to ensure that all of these small operations either succeed or fail. In essence, distributed transactions ensure data consistency across different databases. In a popular sense, a single application is divided into micro-service applications. The original module is divided into three independent applications using independent data sources respectively. Business operations need to be completed by invoking three services.

Distributed transaction solution

Distributed transactions are a big problem in microservice applications. Among the existing solutions, I think Seata is the lightest one

What is Seata?

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.

AT mode

The premise

  • Based on a relational database that supports local ACID transactions.
  • Java applications, access the database through JDBC.

The whole mechanism

Evolution of the two-phase commit protocol:

  • Phase one: Business data and rollback log records are committed in the same local transaction, freeing local locks and connection resources.
  • Stage 2:
    • Commits are asynchronous and done very quickly.
    • Rollback is compensated in reverse by the rollback log of one phase.

Write the isolation

  • Make sure you get the global lock before committing a phase 1 local transaction.
  • Unable to commit local transaction because global lock could not be obtained.
  • Attempts to acquire the global lock are limited to a certain range, beyond which the local transaction is abandoned and rolled back to release the local lock.

As an example, two global transactions tx1 and TX2 update the m field of table A, respectively, with the initial value of M 1000.

Tx1 start, open local transaction, get local lock, update operation m = 1000-100 = 900. Before a local transaction is committed, the global lock of the record is obtained. The local commit releases the local lock. Tx2 start, open local transaction, get local lock, update operation m = 900-100 = 800. Before the local transaction is committed, the global lock of the record is held by TX1. Tx2 needs to retry to wait for the global lock.

Tx1 two-phase global commit, release global lock. Tx2 takes the global lock and commits the local transaction.

If tx1 has a two-phase global rollback, then TX1 needs to re-acquire the local lock of the data and perform the reverse compensation update operation to achieve the rollback of the branch.

At this point, if TX2 is still waiting for a global lock for that data and holds a local lock, the branch rollback of TX1 will fail. The branch rollback will continue to retry until tx2’s global lock times out, the global lock is abandoned and the local transaction is rolled back to release the local lock, and the branch rollback of TX1 finally succeeds.

Because the global lock is held by TX1 until tx1 ends, dirty writes do not occur.

Read isolation

The default global isolation level for Seata (AT mode) is Read Uncommitted, based on the database local transaction isolation level Read Committed or above.

If the application is applied in certain scenarios, it must require that the global read has been committed. Currently, Seata is brokered through SELECT FOR UPDATE statements.

The execution of the SELECT FOR UPDATE statement requests a global lock, and if the global lock is held by another transaction, the local lock is released (roll back the local execution of the SELECT FOR UPDATE statement) and retry. In this process, the query is held by the block and is not returned until the global lock is taken, i.e. the relevant data read is committed.

FOR overall performance, Seata’s current approach does not proxy all SELECT statements, only SELECT statements FOR UPDATE.

Working mechanism

Take an example to illustrate how the entire AT branch works.

Business table: Product

Field Type Key
id bigint(20) PRI
name varchar(100)
since varchar(100)

The business logic of an AT branch transaction:

update product set name = ‘GTS’ where name = ‘TXC’;

A phase

Process:

  1. SQL: SQL type (UPDATE), table (product), condition (where name = ‘TXC’), etc.
  2. Image before query: Generates a query statement to locate data based on the condition information obtained through parsing.
select id, name, since from product where name = 'TXC';
Copy the code

Get the front image:

id name since 1 TXC 2014

  1. Execute business SQL: update this record with name ‘GTS’.
  2. Mirror after query: Locate data by primary key based on the result of the former mirror.
select id, name, since from product where id = 1`;
Copy the code

After obtaining the mirror image:

id name since 1 GTS 2014

  1. Insert rollback log: The rollback log records are composed of mirror data and service SQL information and inserted into the UNDO_LOG table.
{
	"branchId": 641789253,
	"undoItems": [{
		"afterImage": {
			"rows": [{
				"fields": [{
					"name": "id"."type": 4."value": 1}, {"name": "name"."type": 12."value": "GTS"
				}, {
					"name": "since"."type": 12."value": "2014"}}]]."tableName": "product"
		},
		"beforeImage": {
			"rows": [{
				"fields": [{
					"name": "id"."type": 4."value": 1}, {"name": "name"."type": 12."value": "TXC"
				}, {
					"name": "since"."type": 12."value": "2014"}}]]."tableName": "product"
		},
		"sqlType": "UPDATE"}]."xid": "xid:xxx"
}
Copy the code
  1. Before submitting, register branch with TC: apply global lock for records in the product table whose primary key value is 1.
  2. Local transaction commit: The update of the business data is committed along with the UNDO LOG generated in the previous step.
  3. The local transaction submission result is reported to the TC.

Phase 2 – Rollback

  1. After receiving the branch rollback request from the TC, start a local transaction and perform the following operations:
  2. UNDO LOG records are found based on the XID and Branch ID.
  3. Data verification: Compare the mirror in UNDO LOG with the current data. If there is a difference, the data has been modified by an action other than the current global transaction. In this case, 4. You need to handle the problem according to the configuration policy. Details are described in another document. Generate and execute a rollback statement based on the former image and business SQL information in UNDO LOG:
update product set name = 'TXC' where id = 1;
Copy the code
  1. Commit a local transaction. In addition, the execution result of the local transaction (that is, the rollback result of the branch transaction) is reported to the TC.

Phase two – Commit

  1. After receiving the branch commit request from TC, it puts the request into an asynchronous task queue and immediately returns the result of successful commit to TC.
  2. A branch submit request in the asynchronous task phase deletes the corresponding UNDO LOG records asynchronously and in batches.

The appendix

Rollback log tables

UNDO_LOG Table: Different databases have slightly different types.

Take MySQL as an example:

Field Type
branch_id bigint PK
xid varchar(100)
context varchar(128)
rollback_info longblob
log_status tinyint
log_created datetime
log_modified datetime
Context 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, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;Copy the code

TCC mode

Review the overview description: a distributed global transaction, the whole is a two-phase commit model. Global transactions are composed of several branch transactions. Branch transactions should meet the requirements of the two-phase commit model, that is, each branch transaction should have its own:

  • Stage 1 prepare behavior
  • Phase 2 Commit or ROLLBACK actions

According to the behavior modes of the two phases, we divided the Branch transactions into Automatic (Branch) Transaction Mode and Manual (Branch) Transaction Mode.

The AT schema (see link TBD) is based on a relational database that supports local ACID transactions:

  • Phase 1 Prepare behavior: The local transaction submits the service data update and the corresponding rollback log.
  • Phase-2 Commit behavior: Automatically and asynchronously clears rollback logs in batches after the logs are successfully completed immediately.
  • Two-phase rollback: the system automatically generates compensation operations to rollback data by rolling back logs.

Accordingly, TCC mode, independent of transaction support for underlying data resources:

  • One-stage Prepare behavior: The customized prepare logic is invoked.
  • Two-stage COMMIT behavior: Custom commit logic is invoked.
  • Two-stage ROLLBACK behavior: Invoke custom ROLLBACK logic. The TCC pattern supports the integration of custom branch transactions into the management of global transactions

Saga mode

Saga mode is a long transaction solution provided by SEATA. In Saga mode, each participant in the business process submits a local transaction, and when a participant fails, the previous successful participant is compensated. One-stage forward service and two-stage compensation service are implemented by business development.

Theoretical basis: Hector & Kenneth published paper Sagas (1987)

Applicable scenarios:

  • Long business process, many business process
  • Participants include other corporate or legacy system services that do not provide the three interfaces required by the -TCC pattern

Advantage:

  • One-stage commit local transaction, no lock, high performance
  • Event-driven architecture, asynchronous execution of participants, high throughput
  • Compensation services are easy to implement

Disadvantages:

  • Isolation is not guaranteed (see user documentation for solutions)

Five. Preparation

  • Here we useNacosAs a registry, Nacos is available for installation and use
  • We’ll download it from the official websiteseata-serverDownload seata-server-0.9.0.zip from seata-server-0.9.0.zipGithub.com/seata/seata…

    Github address is slow to download, you can reply in the background of the public accountSeata installation packageQuick access to Baidu cloud download links
  • After the download is complete, decompress the seata-Server installation package to the specified directory

After unpacking, we have several folders

  • Bin Stores the seata Server startup scripts for each system
  • Conf contains the configuration information required for seata Server startup and the table construction clauses required in database mode
  • Lib List of dependencies needed to run Seata Server

Configuration Seata Server

All of the seata Server configuration is in the CONF folder, and there are two files in that folder that we must cover in detail.

Seata Server uses file (file mode) to store transaction logs and transaction running information by default. This parameter can be specified in the form of -m db script parameter. Currently, only file and DB are supported.

  • Conf This file is used to configure the storage mode and NIO of transparent transaction information. By default, this file corresponds to the file mode configuration in registry
  • Registry. conf Seata server core configuration file, which can be used to configure the service registration mode and read mode. The registration mode supports file, NACOS, Eureka, Redis, ZK, Consul, ETCD3, and SOFA. The default registration mode is File, which corresponds to the registration mode information in file.conf. The configuration information can be read in file, nacos, Apollo, ZK, Consul, or ETCD3. The default value is file, which corresponds to the configuration in file.

Modify the configuration file file.conf in the conf directory, including the user-defined transaction group name, transaction log storage mode, and database connection information

transport { ... Omitted} service {#vgroup->rgroup
  vgroup_mapping.prex_tx_group = "default" Change the transaction group name to prex_tx_group
  #only support single node
  default.grouplist = "127.0.0.1:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
  #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  max.commit.retry.timeout = "1"
  max.rollback.retry.timeout = "1"
}

## transaction log store
store {
  ## store mode: file, db
  mode = "db" The transaction information is stored in the DB database here

  ## database store
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "druid"
    ## mysql/oracle/h2/oceanbase etc.
    db-type = "mysql"
    driver-class-name = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://localhost:3306/seat" Change the database connection address
    user = "root" Change the database user name
    password = "root" # Change the database password
    min-conn = 1
    max-conn = 3
    global.table = "global_table"
    branch.table = "branch_table"
    lock-table = "lock_table"
    query-limit = 100
  }
}
Copy the code

Description:

  • Transaction logs can be stored in either a file file or a DB database
  • Since we use db mode to store transaction logs, we need to create a seat database and create a table SQL in /conf/db_store.sql of seata-server

  • Modify the file in the conf directoryregistry.confConfiguration file, specify the registry as NACOS, and modify the NACOS connection information;
registry {
  # File, NACOS, Eureka, Redis, ZK, Consul, ETCD3, SOFA
  type = "nacos"

  nacos {
    serverAddr = "localhost:8848"
    namespace = ""
    cluster = "default"}... Omit}}Copy the code

Start Seata after the configuration is complete

The script for starting the Seata Server is stored in the bin file. The seata-server.sh script is used to start the Seata server in Linux or Mac operating systems, and the Seata-server. bat script is used to start the Seata server in Windows operating systems.

The following is an example of a Linux/Mac boot device:

Nohup sh seta-server. sh -p 8091 -h 127.0.0.1 -m db &> seta.log &Copy the code

Run the nohup command to run the Seata Server in the background.

Script parameters:

  • -p Specifies the port number for starting the Seata server.
  • -h Specifies the host bound to the Seata server. The specified HOST IP address must be consistent with the configuration file in the service, for example, -h 192.168.1.10. 192.168.1.10 must be configured in the service configuration file even on the same host.
  • -m Specifies the mode for storing transaction logs and transaction execution information. The mode supports file and DB. For details about how to build tables, see config/db_store. SQL and config/db_undo_log.sql.

Viewing startup Logs

There are no other error messages when we see -server started and our Seata Server has been started successfully

Six. Actual combat demonstration

Let’s start with a microservice example of the business logic for a user to purchase an item. The entire business logic is supported by three microservices:

  • Storage service: deduct the storage quantity for a given item.
  • Order service: Creates orders based on purchasing requirements.
  • Account service: deducts balances from user accounts.

Architecture diagram

The database

Creating a Service Database

Db-order: database for storing orders db-storage: database for storing inventory db-account: database for storing account information

Order List:

DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
  `id` int(20) NOT NULL AUTO_INCREMENT COMMENT 'primary key Id',
  `user_id` int(20) DEFAULT NULL COMMENT 'user Id',
  `pay_money` decimal(11,0) DEFAULT NULL COMMENT 'Payment Amount',
  `product_id` int(20) DEFAULT NULL COMMENT 'commodity Id',
  `status` int(11) DEFAULT NULL COMMENT 'state',
  `count` int(11) DEFAULT NULL COMMENT 'Quantity of goods',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC COMMENT='Order sheet';

SET FOREIGN_KEY_CHECKS = 1;
Copy the code

Product List:

DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
  `id` int(20) NOT NULL COMMENT 'primary key',
  `product_id` int(11) DEFAULT NULL COMMENT 'commodity Id',
  `price` decimal(11,0) DEFAULT NULL COMMENT 'price',
  `count` int(11) DEFAULT NULL COMMENT 'Stock quantity',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC COMMENT='Warehouse Services';

-- ----------------------------
-- Records of product
-- ----------------------------
BEGIN;
INSERT INTO `product` VALUES (1, 1, 50, 100);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
Copy the code

Account table:

DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `id` int(20) NOT NULL AUTO_INCREMENT COMMENT 'primary key Id',
  `user_id` int(20) DEFAULT NULL COMMENT 'user Id',
  `balance` decimal(11,0) DEFAULT NULL COMMENT 'the balance',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;

-- ----------------------------
-- Records of account
-- ----------------------------
BEGIN;
INSERT INTO `account` VALUES (1, 1, 100);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
Copy the code

Create a log rollback table

To create a log rollback table for each database, create the table SQL in /conf/db_undo_log.sql of seata-server.

Distributed transaction problems occur

Three services, one order service, one storage service and one account service. When the user places an order, an order will be created in the order service, and then the inventory of the ordered goods will be deducted by calling the inventory service remotely, and then the balance in the user account will be deducted by calling the account service remotely, and finally the order status will be changed to completed in the order service. This operation spans three databases, has two remote calls, and obviously has distributed transaction issuesCopy the code

Engineering structure

Nacos-seata-account-server Account service nacOS-seata-order-server Order service nacOS-seata-storage-server Storage service

Client Configuration

  • Configure three SEATA clients: nacos-seata-account-server, nacos-seata-order-server and nacos-seata-storage-server. Their configurations are roughly the same. The configuration of nacos-seata-account-server is used as an example.

  • Modify the application.yml file to customize the transaction group name

spring:
  cloud:
    alibaba:
      seata:
        tx-service-group: prex_tx_group Set transaction group name to seata-server
Copy the code
  • Add and modify the file.conf configuration file, mainly modifying the custom transaction group name
service {
  #vgroup->rgroup
  vgroup_mapping.prex_tx_group = "default" Change the name of the custom transaction group
  #only support single node
  default.grouplist = "127.0.0.1:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
  #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  max.commit.retry.timeout = "1"
  max.rollback.retry.timeout = "1"
  disableGlobalTransaction = false
}
Copy the code

Add and modify the registry. Conf configuration file, mainly to change the registry to NACOS

registry {
  Nacos, Eureka, Redis, zk
  type = "nacos" # change to nacos

  nacos {
    serverAddr = "localhost:8848" Change to the connection address of nacOS
    namespace = ""
    cluster = "default"}}Copy the code

The code only shows the core code specific code article tail link

  • Cancel automatic creation of data sources in the startup class
@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@MapperScan("com.xd.example.seata.mapper") public class NacosSeataAccountServerApplication { public static void main(String[] args) { SpringApplication.run(NacosSeataAccountServerApplication.class, args); }}Copy the code
  • Configure MybatisPlus to proxy the data source using Seata

MyBatisPlusConfig:

/** * @class name MyBatisPlusConfig * @author Created by Lihaodong (alias[email protected] * @date 2019-11-25 11:21 * @version 1.0 */ @configuration public class MyBatisPlusConfig { @Value("${mybatis-plus.mapper-locations}")
    private String mapperLocations;

    /**
     * @param sqlSessionFactory SqlSessionFactory
     * @return SqlSessionTemplate
     */
    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        returnnew SqlSessionTemplate(sqlSessionFactory); } /** * / druid/druid/druid/druid/druid/druid/druid/druid"spring.datasource"
     *
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    public DataSource hikariDataSource() {
        returnnew HikariDataSource(); } /** * Construct the datasource proxy object instead of the original datasource ** @param hikariDataSource * @return
     */
    @Primary
    @Bean("dataSource")
    public DataSourceProxy dataSourceProxy(DataSource hikariDataSource) {
        return new DataSourceProxy(hikariDataSource);
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSourceProxy);
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resolver.getResources(mapperLocations));

        SqlSessionFactory factory = null;
        try {
            factory = bean.getObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        returnfactory; } /** ** MP has its own paging plug-in ** @return
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor page = new PaginationInterceptor();
        page.setDialectType("mysql");
        returnpage; }}Copy the code
  • Start distributed transactions using the @GlobalTransactional annotation
package com.xd.example.seata.service.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.xd.example.seata.domain.Order; import com.xd.example.seata.mapper.OrderMapper; import com.xd.example.seata.service.IOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.xd.example.seata.service.RemoteAccountService; import com.xd.example.seata.service.RemoteStorageService; import io.seata.core.context.RootContext; import io.seata.spring.annotation.GlobalTransactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** ** <p> ** @author lihaodong * @since 2019-11-25 */ @slf4j@service public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService { @Autowired private RemoteStorageService remoteStorageService; @Autowired private RemoteAccountService remoteAccountService; @GlobalTransactional(rollbackFor = Exception.class) @Override public void createOrder(Order order) { log.info("Order start, User :{}, commodity :{}, Quantity :{}, Amount :{}", order.getUserId(), order.getProductId(), order.getCount(), order.getPayMoney()); // Create order order.setStatus(0); boolean save = save(order); log.info("Save order {}", save ? "Success" : "Failure");
        log.info("XID: {}", RootContext.getXID()); // Call the inventory service remotely to deduct the inventory log.info("Destocking begins.");
        remoteStorageService.decrease(order.getProductId(), order.getCount());
        log.info("Inventory reduction completed."); Log.info ()"Deduction of balance begins");
        remoteAccountService.decrease(order.getUserId(), order.getPayMoney());
        log.info("End of deduction balance"); // Change the order status to completed log.info("Start to modify order status");
        update(Wrappers.<Order>lambdaUpdate().set(Order::getStatus, 1).eq(Order::getUserId, order.getUserId()));
        log.info("End of modifying order status");

        log.info("Order closed"); }}Copy the code

7. Start service function demonstration

  1. Three services, nacOS-seata-order-server, nacos-Seata-storage-server and nacOS-Seata-account-server, are respectively run

    You can see that seATA was successfully registered

  2. Example Query the initial database information

  3. Open a browser/Postman call interface for order operation: http://localhost:8081/order/create? UserId = 1 & productId = 1 & count = 1 & payMoney = 50 results:

    View console print: Order Service:

Warehousing services:

Account Service:

  1. Database query again
  2. After making a timeout exception (or any other exception) in nacOS-seata-account-server, we call the order interface
package com.xd.example.seata.service.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.xd.example.seata.domain.Account; import com.xd.example.seata.mapper.AccountMapper; import com.xd.example.seata.service.IAccountService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import io.seata.core.context.RootContext; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.Optional; /** * <p> ** @author lihaodong * @since 2019-11-25 */ @slf4j@service Public Class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements IAccountService { @Override public boolean reduceBalance(Integer userId, BigDecimal balance) throws Exception { log.info("XID: {}", RootContext.getXID());
        checkBalance(userId, balance);

        log.info("Start deducting user {} balance", userId); Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } Integer record = baseMapper.reduceBalance(userId, balance); log.info("End deduction user {} balance result :{}", userId, record > 0 ? "Operation successful" : "Failed to deduct balance");
        return record > 0;
    }

    private void checkBalance(Integer userId, BigDecimal price) throws Exception {
        log.info("Check user {} balance", userId);

        Optional<Account> account = Optional.ofNullable(baseMapper.selectOne(Wrappers.<Account>lambdaQuery().eq(Account::getUserId, userId)));
        if (account.isPresent()) {
            BigDecimal balance = account.get().getBalance();
            if (balance.compareTo(price) == -1) {
                log.warn("User {} balance is insufficient, current balance :{}", userId, balance);
                throw new Exception("Insufficient balance"); }}}}Copy the code

After the modification, the account service is restarted and the request is sent again

It can be seen that the order is normal, the inventory is normal, and the account service read timeout is abnormal

  1. No change in database data was observed after the order was placed

  2. Let’s comment out @globalTransactional in seata-order-service and see what happens


//    @GlobalTransactional(name = "prex-create-order",rollbackFor = Exception.class)
    @Override
    public void createOrder(Order order) {
        log.info("XID: {}", RootContext.getXID());
        log.info("Order start, User :{}, commodity :{}, Quantity :{}, Amount :{}", order.getUserId(), order.getProductId(), order.getCount(), order.getPayMoney()); // Create order order.setStatus(0); boolean save = save(order); log.info("Save order {}", save ? "Success" : "Failure"); . Omit code}Copy the code

Save restart order service, request interface again due to nacos-seata-account-server timeout will cause the order status is not set to complete after inventory and account amount deduction

Seata transaction grouping

Next update

Seata distributed transaction principle explained

Next update

Project source code address

Gitee.com/li_haodong/…

References:

seata.io/zh-cn/