Explanation of distributed transaction - TX-LNC distributed transaction framework (including LCN, TCC and TXC modes)

Posted by Bee on Sat, 22 Jan 2022 10:48:16 +0100


The TX-LCN framework explained today integrates three transaction modes: LCN, TCC and TXC. TX-LCN is an open source framework developed in China. You can see Chinese comments in the source code, which is more friendly. Let's talk about these three modes in detail, with code implementation.

TX-LCN framework principle

The framework is mainly composed of transaction participant (TxClient) and transaction manager (TxManager). The schematic diagram of transaction control is as follows:

From the figure, we can understand the principle of TX-LCN framework. The fourth step can be used as a stage division line. All operations above the fourth step are like the first stage of 2PC, and all operations below the fourth step and below are like the second stage of 2PC. If you forget 2PC, you can take a look at the previous blog I wrote [distributed transaction explanation - 2PC, 3PC] , this sequence diagram is actually the translation of the framework source code process. Many ingenious methods are also used in the source code to realize the workflow of the overall transaction. I will explain these to you when I introduce the three modes below.

LCN

Principle and main features

  1. LCN mode uses agent technology to maintain the database connection of local transactions without releasing, and uniformly control transactions through TxManager.
  2. In the local transaction, the commit/rollback/close operations performed on the transaction are all false operations. You can see that the commit/rollback/close methods in the database connection after the agent are empty, which restricts the local transaction from performing operations other than SQL execution.
  3. It can be said that the database connection will be maintained after the local transaction executes SQL. The local transaction will not release the database connection until the TxManager sends a commit or rollback operation, which increases the occupation time of the database connection.
  4. The submit or rollback command issued by TxManager in the last stage is sent through Netty, which will cycle all txclients and send instructions.

code implementation

Not only the implementation of LCN mode, but also the implementation of all frameworks mainly consists of three steps:

  1. Induced dependence
  2. Write configuration
  3. append notes to

Like I wrote before [Spring Cloud framework building series] As you can see, let me show you.

Implementation scenario

After placing an order, the user saves the doc record and payment amount. If one operation fails, the order will fail. Otherwise, the overall process will be successful and the order will be successful.

Create databases and tables (three databases, two tables)

-- Users can maintain logs and exception information, and create tables as needed
CREATE DATABASE tx-manager;

CREATE DATABASE lcn-pay;

CREATE TABLE `tbl_pay` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pay_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;


CREATE DATABASE lcn-order;

CREATE TABLE `tbl_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

Create project (four)

Transaction management system (LCN TM)

Induced dependence
<!-- txManager Required dependencies -->
<dependency>
    <groupId>com.codingapi.txlcn</groupId>
    <artifactId>txlcn-tm</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.codingapi.txlcn</groupId>
    <artifactId>txlcn-tc</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<!-- be used for txManager notice txClient -->
<dependency>
    <groupId>com.codingapi.txlcn</groupId>
    <artifactId>txlcn-txmsg-netty</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<!--tm-->
Write configuration
# The WEB access port on the server side of the TM transaction manager. Provide a visual interface. Port customization.
server.port=3000

# TM transaction manager needs to access the database to realize distributed transaction status recording.
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/tx-manager?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=wk3515134

# Name the spring application.
spring.application.name=tx-lcn-transaction-manager

# TM transaction manager provides the login password of the WEB management platform. No user name. The default is the coding API
tx-lcn.manager.admin-key=wk3515134
# journal. Log if required. If it is enabled and the value is set to true, t will be automatically added to the TX manager database_ Logger table.
tx-lcn.logger.enabled=true
append notes to

Add annotation in LCN TM project startup class

@EnableTransactionManagerServer

Eureka registration and discovery system (Eureka)

Induced dependence
<dependency>
   <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
Write configuration
spring:
  application:
    name: cloud-eureka
eureka:
  instance:
    # Modify the contents in the hosts file
    hostname: euk-server-one.com
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      # 5 24
      defaultZone: http://euk-server-one.com:8080/eureka/
append notes to

Add annotation in eureka project startup class

@EnableEurekaServer

If eureka fails to start after configuration, move to one of my blogs [the way to build complete components of Spring Cloud Eureka].

Order system (LCN order)

Induced dependence
<!-- lcn -->
<dependency>
    <groupId>com.codingapi.txlcn</groupId>
    <artifactId>txlcn-tc</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>

<dependency>
    <groupId>com.codingapi.txlcn</groupId>
    <artifactId>txlcn-txmsg-netty</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>

<!-- json-lib -->
<dependency>
    <groupId>net.sf.json-lib</groupId>
    <artifactId>json-lib</artifactId>
    <version>2.4</version>
    <classifier>jdk15</classifier>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>

<!-- mysql:MyBatis Correlation dependency -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>

<!-- mysql:mysql drive -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- mysql:Alibaba database connection pool -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
</dependency>
Write configuration
server:
  port: 1000
# Service name
spring:
  application:
    name: lcn-order
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/lcn-order?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: wk3515134
    dbcp2:
      initial-size: 5
      min-idle: 5
      max-total: 5
      max-wait-millis: 200
      validation-query: SELECT 1
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false

mybatis:
  mapper-locations:
  - classpath:mapper/*.xml

eureka:
  client:
    service-url:
      # Register in eureka
      defaultZone: http://euk-server-one.com:8080/eureka/

# For the address of the transaction processor, you need to start the LCN TM system and log in. The port is shown in the following figure
tx-lcn:
  client:
    manager-address: 127.0.0.1:3100

append notes to

Add annotation in LCN order project startup class

@EnableDistributedTransaction
Start the bean whose class defines RestTemplate (used to call LCN pay system method)
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
    return new RestTemplate();
}
Add test Controller class (OrderController)
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.hepai.lcnorder.dao.TblOrderDao;
import com.hepai.lcnorder.entity.TblOrder;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class OrderController {
    @Autowired
    private TblOrderDao tblOrderDao;
    @Autowired
    private RestTemplate restTemplate;
    @PostMapping("/add-order")
    @Transactional(rollbackFor = Exception.class) //Be sure to add it. The LCN source code will judge this annotation
    @LcnTransaction // The annotation must be added. The LCN mode can be added on the method that calls the distributed system
    public String add(@RequestBody TblOrder bean){
        JSONObject date = new JSONObject();
        date.put("payName",bean.getOrderName()+"pay");
		// Call the specified method of payment system
        restTemplate.postForEntity("http://lcn-pay/add-pay",date,String.class);
//        int i = 1/0;
		// Insert sql, Service, Mapper and Entity. Please write it yourself. Just write it according to the ordinary business logic
        tblOrderDao.insert(bean);
        return "Successfully added order";
    }
}

Payment system (LCN pay)

Reference dependency (same as LCN order system dependency)
<!-- lcn -->
<dependency>
    <groupId>com.codingapi.txlcn</groupId>
    <artifactId>txlcn-tc</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.codingapi.txlcn</groupId>
    <artifactId>txlcn-txmsg-netty</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<!-- json-lib -->
<dependency>
    <groupId>net.sf.json-lib</groupId>
    <artifactId>json-lib</artifactId>
    <version>2.4</version>
    <classifier>jdk15</classifier>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>

<!-- mysql:MyBatis Correlation dependency -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>

<!-- mysql:mysql drive -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- mysql:Alibaba database connection pool -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
</dependency>
Write configuration
server:
  port: 2000
# service name
spring:
  application:
    name: lcn-pay
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/lcn-pay?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: wk3515134
    dbcp2:
      initial-size: 5
      min-idle: 5
      max-total: 5
      max-wait-millis: 200
      validation-query: SELECT 1
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
mybatis:
  mapper-locations:
  - classpath:mapper/*.xml
eureka:
  client:
    service-url:
      defaultZone: http://euk-server-one.com:8080/eureka/
# For the address of the transaction processor, you need to start the LCN TM system and log in, and the port is in the position shown in the figure below
tx-lcn:
  client:
    manager-address: 127.0.0.1:3100

append notes to

Add annotation in LCN pay project startup class

@EnableDistributedTransaction
Add test Controller class (PayController)
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.hepai.lcnpay.dao.TblPayDao;
import com.hepai.lcnpay.entity.TblPay;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PayController {
    @Autowired
    private TblPayDao tblPayDao;
    @PostMapping("/add-pay")
    @Transactional(rollbackFor = Exception.class)
    @LcnTransaction
    public String addPay(@RequestBody TblPay bean){
        tblPayDao.insert(bean);
        return "Payment added successfully";

    }
}

Perform test

The test transaction was executed successfully

  1. Using Postman calls[ http://localhost:1000/add-order], fill in the corresponding parameters and the execution result:
  2. View database data:

Test transaction execution failed

  1. Modify the OderController class method and add an exception
public String add(@RequestBody TblOrder bean){
   JSONObject date = new JSONObject();
   date.put("payName",bean.getOrderName()+"pay");
   restTemplate.postForEntity("http://lcn-pay/add-pay",date,String.class);
// Add exception
   int i = 1/0;
   tblOrderDao.insert(bean);
   return "Successfully added order";
}
  1. Using Postman calls[ http://localhost:1000/add-order], fill in the corresponding parameters, and the execution results are as follows:

  2. View database data:

    It can be seen that there is no change in the database data, or the data successfully inserted during the transaction execution just now. Therefore, the distributed transaction takes effect.

TCC

Principle and main features

  1. During the implementation of TCC mode, three methods (business execution method, submission method and cancellation method) need to be written for the business class of each transaction participating system. That is, when executing business, if the distributed transaction fails as a whole, execute the cancellation method. If the distributed transaction succeeds as a whole, execute the submission method, It increases the complexity of code implementation.
  2. TCC mode is applicable to the situation without local transaction control, such as MongoDB, Redis and other middleware that do not support transactions. TCC mode can be used to realize distributed transactions.
  3. TCC mode is mainly divided into two stages. In the first stage, each transaction participant directly executes SQL locally and submits it, and feeds back the execution results to the transaction manager; In the second stage, the transaction manager judges whether all transaction participants execute [cancel method] or [submit method] according to the feedback in the first stage.
  4. If the cancel method is executed in the second stage, the reverse SQL of the first stage SQL needs to be executed, that is, similar to the Undo log of Mysql, the insert statement executed in the first stage needs to be delete d to recover, and the update statement needs to be reversed to update to recover, which increases the complexity of the business and the difficulty of implementing the code.

be careful:
In the first stage of TCC mode, SQL is directly submitted after execution, and preparation for inverse SQL is very necessary.

Code implementation (add TCC code based on LCN test code)

Order system (LCN order)

New test Controller class (OrderTccController)

import com.codingapi.txlcn.tc.annotation.TccTransaction;
import com.hepai.lcnorder.dao.TblOrderDao;
import com.hepai.lcnorder.entity.TblOrder;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

@RestController
public class OrderTccController {
    @Autowired
    private TblOrderDao tblOrderDao;
    @Autowired
    private RestTemplate restTemplate;
    @PostMapping("/add-order-tcc")
    @Transactional(rollbackFor = Exception.class)
    @TccTransaction  // Using the TCC mode of the framework to realize distributed transactions
    public String add(@RequestBody TblOrder bean){
        JSONObject date = new JSONObject();
        date.put("payName",bean.getOrderName()+"pay");
        restTemplate.postForEntity("http://lcn-pay/add-pay-tcc",date,String.class);
        tblOrderDao.insert(bean);
        Integer id = bean.getId();
        maps.put("a",id);
//        int i = 1/0;
        return "Successfully added order 1";
    }
    public String confirmAdd(TblOrder bean){
        System.out.println("order confirm ");
        return "Successfully added order 2";
    }
    private static Map<String,Integer> maps = new HashMap<>();
    public String cancelAdd(TblOrder bean){
        Integer a = maps.get("a");
        System.out.println("a:"+a);
        // Cancel the method to perform the sql inverse operation
        tblOrderDao.deleteByPrimaryKey(a);
        System.out.println("order cancel ");
        return "Failed to add order";
    }
}

be careful:
cancelAdd() and confirmAdd() do not need to be annotated, because the framework source code indicates that if the annotations of submission method and cancellation method are not found, the first letter of the method name annotated with [@ TccTransaction] is capitalized by default and prefixed with [cancel] and [confirm] as the method names of submission method and cancellation method respectively.

Payment system (LCN pay)

New test Controller class (PayTccController)

import com.codingapi.txlcn.tc.annotation.TccTransaction;
import com.hepai.lcnpay.dao.TblPayDao;
import com.hepai.lcnpay.entity.TblPay;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;

@RestController
public class PayTccController {
    @Autowired
    private TblPayDao tblPayDao;
    @PostMapping("/add-pay-tcc")
    @Transactional(rollbackFor = Exception.class)
    @TccTransaction
    public String addPay(@RequestBody TblPay bean){
        tblPayDao.insert(bean);
        Integer id = bean.getId();
        maps.put("a",id);
        int i = 1/0;
        return "Successfully added payment 1";
    }
    public String confirmAddPay(TblPay bean){
        System.out.println("pay confirm");
        return "Successfully added payment 2";

    }
    private static Map<String,Integer> maps = new HashMap<>();
    /**
     * Inverse sql
     * @param bean
     * @return
     */
    public String cancelAddPay(TblPay bean){
        Integer a = maps.get("a");
        System.out.println("a:"+a);
        System.out.println("pay cancel");
        tblPayDao.deleteByPrimaryKey(a);
        return "Payment failed";
    }
}

Perform test

The test transaction was executed successfully

  1. Using Postman calls[ http://localhost:1000/add -Order TCC], fill in the corresponding parameters, and the execution results are as follows:

  2. View database data:

Test transaction execution failed

  1. Modify the OderTccController class method and add an exception
public String add(@RequestBody TblOrder bean){
   JSONObject date = new JSONObject();
    date.put("payName",bean.getOrderName()+"pay");
    restTemplate.postForEntity("http://lcn-pay/add-pay-tcc",date,String.class);
    tblOrderDao.insert(bean);
    Integer id = bean.getId();
    maps.put("a",id);
    // Add exception
    int i = 1/0;
    return "Successfully added order 1";
}
  1. Using Postman calls[ http://localhost:1000/add -Order TCC], fill in the corresponding parameters, and the execution results are as follows:

  2. View database data:

    It can be seen that there is no change in the database data, or the data successfully inserted during the transaction execution just now. Therefore, the distributed transaction takes effect.

TXC

Principle and main features

  1. In the workflow of TXC mode, query the affected data before executing SQL. If the amount of data is large, the efficiency will be very low.
  2. TXC mode can only support the transaction module in SQL mode, because first, it must query the impact data first, so it is a major premise to have SQL to be executed.
  3. It will not occupy the connection resources of the database.
  4. In my opinion, for article 1, if there is a large amount of data and hundreds of impact data tables involved, this method is the least recommended. This mode requires very high query efficiency.

summary

In the TX-LCN framework, it is recommended to use the LCN mode in the case of distributed transactions in which the participant supports transactions locally. When the participant does not support transactions locally, only TCC can be used.

Source address

In case of other problems when you use it, I share the source code, hoping to help you.

https://gitee.com/hepai123/lcn-demo.git

Topics: Java Database Distribution