LCN分布式事务框架原理和多模式下的实战使用,基础Dubbo框架为例:
LCN框架 原理
背景
LCN 名称是由早期版本的 LCN 框架命名,在设计框架之初的1.0~2.0的版本时框架设计的步骤是如下,各取其首字母得来的 LCN 命名。
- 锁定事务单元(Lock)
- 确认事务模块状态( Confirm)
- 通知事务( Notify)
框架定位
LCN 并不生产事务,LCN 只是本地事务的协调工。TX-LCN 定位于一款事务协调性框架,框架其本身并不操作事务,而是基于对事务的协调从而达到事务一致性的效果。
事务控制原理
TX-LCN由两大模块组成, TxClient、 TxManager,支持3PC、TCC等事务模式。
TxClient 作为模块的依赖框架,提供 TX-LCN 的标准支持, TxManager 作为分布式事务的控制方。事务发起方或者参与方都由 TxClient 来控制。
通过代理 Connection 的方式实现对本地事务的操作,然后在由 TxManager 统一协调控制事务。当本地事务提交回滚或者关闭连接时将会执行假操作,该代理的连接将由 LCN 连接池管理。
原理图
一、LCN框架3PC模式,实战Dubbo微服务,集成zookeeber、redis等,详情看配置:
1、服务端事务管理器的微服务引入jar
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>trans-lcn</artifactId>
<groupId>com.enjoy</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lcn-tm</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql的支持-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- JDBC连接数据库,因为要用HikariCP,所以需要将SpringBoot中的tomcat-jdbc排除 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--LCN框架jar的核心支持-->
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tm</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.2.0.RELEASE</version>
</plugin>
</plugins>
</build>
</project>
2、application.yml 配置文件设置:
spring.application.name=tm-manager
server.port=7970
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://10.130.166.2466:305199/ZIPKIN?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
spring.datasource.username=mysql
spring.datasource.password=mysql
# TxManager Host Ip
tx-lcn.manager.host=127.0.0.1
# TxClient连接请求端口
tx-lcn.manager.port=8070
# 心跳检测时间(ms)
tx-lcn.manager.heart-time=15000
# 分布式事务执行总时间
tx-lcn.manager.dtx-time=30000
#参数延迟删除时间单位ms
tx-lcn.message.netty.attr-delay-time=10000
tx-lcn.manager.concurrent-level=128
# 开启日志
tx-lcn.logger.enabled=true
logging.level.com.codingapi=debug
#redis 主机
spring.redis.host=127.0.0.1
#redis 端口
spring.redis.port=6379
#redis 密码
#spring.redis.password=123456
3、服务端启动类注解配置:@EnableTransactionManagerServer ,
import com.codingapi.txlcn.tm.config.EnableTransactionManagerServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableTransactionManagerServer
public class TmManager {
public static void main(String[] args) {
SpringApplication.run(TmManager.class, args);
}
}
4、启动类上加 : @EnableDistributedTransaction 注解
然后就可以启动了。
二、客户端服务发现方的创建:
1、微服务jar包的引入:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>trans-lcn</artifactId>
<groupId>com.enjoy</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lcn-busi</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql的支持-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- JDBC连接数据库,因为要用HikariCP,所以需要将SpringBoot中的tomcat-jdbc排除 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--使用swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</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>
<!--dubbo-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.11</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.2.0.RELEASE</version>
</plugin>
</plugins>
</build>
</project>
2、application.yml文件配置:
server:
port: 8090
spring:
application:
name: lcn-busi
datasource:
username: mysql
password: mysql
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://10.130.16.2466:305199/ZIPKIN?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
tx-lcn:
client:
manager-address: 127.0.0.1:8070
zookeeper:
url: zookeeper://192.168.0.128:2181
dubbo:
name: lcn-busi
port: 20880
3、controller层核心代码:
import com.enjoy.service.TransferService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AccountController {
@Autowired
private TransferService transferService;
@GetMapping("/transfer")
public String transfer(int money){
return transferService.transfer(money);
}
}
4、微服务业务层接口加上注解 @LcnTransaction,
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.enjoy.service.TransferService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.client.RestTemplate;
import static com.codingapi.txlcn.tracing.TracingContext.tracing;
@org.springframework.stereotype.Service
public class RestTransferService implements TransferService {
private RestTemplate restTemplate =new RestTemplate();
@Autowired
private JdbcTemplate jdbcTemplate;
@LcnTransaction //lcn子事务 groupid 传递过去
public String transfer(int money) {
String appList = tracing().appMapString();
String groupId = tracing().groupId();
String lcn_param = "&appList="+appList+"&groupId="+groupId;
int resultUser = jdbcTemplate.update("INSERT INTO bank_c(money,user_name)VALUES (?,?)",-money,"james");
String url = "http://localhost:5003/receive?money={money}"+lcn_param;
String resultGood = restTemplate.postForObject(url, "money", String.class, money);
if (money > 20){
throw new RuntimeException("money too large");
}
return resultUser+"-";
}
}
或者用DUBBO配置的实现类:此处只能有一个实现类,因为初始化对象是单例的,否则启动报错。
import com.alibaba.dubbo.config.annotation.Reference;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.codingapi.txlcn.tracing.TracingContext;
import com.enjoy.service.ReceiveService;
import com.enjoy.service.TransferService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
@org.springframework.stereotype.Service
public class DubboTransferService implements TransferService{
@Autowired
private JdbcTemplate jdbcTemplate;
@Reference//dubbo模式的依赖注入
private ReceiveService receiveService;
@LcnTransaction
public String transfer(int money) {
int resultUser = jdbcTemplate.update("INSERT INTO bank_c(money,user_name)VALUES (?,?)",-money,"james");
System.out.println(TracingContext.tracing().groupId());
receiveService.receiveMoney(money);//一样隐藏地传递了groupid
if (money > 20){
throw new RuntimeException("money too large");
}
return resultUser+"-";
}
}
二、客户端服务提供方的创建:
1、pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>trans-lcn</artifactId>
<groupId>com.enjoy</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lcn-service</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql的支持-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- JDBC连接数据库,因为要用HikariCP,所以需要将SpringBoot中的tomcat-jdbc排除 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--使用swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</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>
<!--dubbo-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.11</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.2.0.RELEASE</version>
</plugin>
</plugins>
</build>
</project>
2、applaication.yml配置:
server:
port: 5003
spring:
application:
name: lcn-service
datasource:
username: mysql
password: mysql
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://10.130.166.2466:305199/ZIPKIN?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
tx-lcn:
client:
manager-address: 127.0.0.1:8070
#注册中心
zookeeper:
url: zookeeper://127.0.0.1:2181
#Dubbo相关配置
dubbo:
name: lcn-service
port: 20881
3、controller层核心代码:
import com.codingapi.txlcn.common.util.Maps;
import com.codingapi.txlcn.tracing.TracingConstants;
import com.codingapi.txlcn.tracing.TracingContext;
import com.enjoy.service.ReceiveService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
public class ReceiveController {
@Autowired
private ReceiveService receiveService;
@PostMapping("/receive")
public String receive(HttpServletRequest request) {
//事务组的获取
String groupId = request.getParameter("groupId");
String appList = request.getParameter("appList");
TracingContext.tracing().init(Maps.newHashMap(TracingConstants.GROUP_ID, groupId, TracingConstants.APP_MAP, appList));
int money = Integer.parseInt(request.getParameter("money"));
receiveService.receiveMoney(money);
return "success";
}
}
4、server业务层核心代码:
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.codingapi.txlcn.tracing.TracingContext;
import com.enjoy.service.ReceiveService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
@com.alibaba.dubbo.config.annotation.Service
public class LcnReceiveServiceImpl implements ReceiveService {
@Autowired
private JdbcTemplate jdbcTemplate;
//3pc事务
@LcnTransaction
public void receiveMoney(int money) {
int resultUser = jdbcTemplate.update("INSERT INTO bank_b(money,user_name)VALUES (?,?)",money,"peter");
System.out.println(TracingContext.tracing().groupId());
}
}
5、启动类上加 : @EnableDistributedTransaction 注解,启动此客户端
三、LCN框架提供的TCC模式:
TCC 原理
前面的 LCN 依赖包,其实也实现了 TCC 事务。
TCC其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其业务逻辑对应的确认和补偿(撤销)操作。
其将整个业务逻辑的每个分支显式的分成了 Try、 Confirm、 Cancel 三个操作。Try 部分完成业务的准备工作, Confirm 部分完成业务的提交, Cancel 部分完成事务的回滚。
原理图:
1、基础配置就是 客户端服务提供方的,即都一样,只有server业务层接口实现的内容不同:
import com.codingapi.txlcn.tc.annotation.TccTransaction;
import com.codingapi.txlcn.tracing.TracingContext;
import com.enjoy.service.ReceiveService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import static com.codingapi.txlcn.tracing.TracingContext.tracing;
@com.alibaba.dubbo.config.annotation.Service
public class TccReceiveServiceImpl implements ReceiveService {
@Autowired
private JdbcTemplate jdbcTemplate;
//try 不再是数据操作
//lcn子事务 当前事务上下文中有了groupId,则加入,没有则新建一个
@TccTransaction(confirmMethod = "confirm", cancelMethod = "cancel", executeClass = TccReceiveServiceImpl.class)
public void receiveMoney(int money) {
String groupId = TracingContext.tracing().groupId();
int resultUser = jdbcTemplate.update("INSERT INTO bank_b(money,user_name,status)VALUES (?,?,?)",money,"peter",groupId);
}
//确认接口
public void confirm(int money) {
String groupId = TracingContext.tracing().groupId();
int resultUser = jdbcTemplate.update("UPDATE bank_b SET status = 1 WHERE status = ?", tracing().groupId());
}
//取消回滚接口
public void cancel(int money) {
String groupId = TracingContext.tracing().groupId();
int resultUser = jdbcTemplate.update("INSERT INTO bank_b(money,user_name,status)VALUES (?,?,?)",-money,"peter",groupId);
}
}
2、到此就可以测试了。三个服务启动后访问:http://localhost:8090/transfer?money=12
到此,LCN框架原理和实战分享暂时告一段落,以后有机会分析一下源码,敬请期待!