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
- LCN mode uses agent technology to maintain the database connection of local transactions without releasing, and uniformly control transactions through TxManager.
- 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.
- 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.
- 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:
- Induced dependence
- Write configuration
- 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
- Using Postman calls[ http://localhost:1000/add-order], fill in the corresponding parameters and the execution result:
- View database data:
Test transaction execution failed
- 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"; }
-
Using Postman calls[ http://localhost:1000/add-order], fill in the corresponding parameters, and the execution results are as follows:
-
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
- 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.
- 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.
- 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.
- 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
-
Using Postman calls[ http://localhost:1000/add -Order TCC], fill in the corresponding parameters, and the execution results are as follows:
-
View database data:
Test transaction execution failed
- 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"; }
-
Using Postman calls[ http://localhost:1000/add -Order TCC], fill in the corresponding parameters, and the execution results are as follows:
-
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
- 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.
- 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.
- It will not occupy the connection resources of the database.
- 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