LCN分布式事务框架原理和多模式下的实战使用

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

优点 :跟 2PC 比起来,实现以及流程相对简单了一些,但数据的一致性比 2PC 也要差一些。
 
缺点 TCC 属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,而且补偿的时候也有可能失败,在一些场景中,一些 业务流程可能用 TCC 不太好定义及处理。
 

到此,LCN框架原理和实战分享暂时告一段落,以后有机会分析一下源码,敬请期待!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寅灯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值