什么是LCN模式

LCN模式是TX-LCN分布式事务模式的一种,L-lock-锁定事务单元、C-confirm-确认事务模块状态、
notify-通知事务单元

原理

LCN模式是通过Spring AOP的方式代理Connection的方式实现对本地事务的操作,然后在由TxManager统一协调控制事务。
当本地事务提交回滚或者关闭连接时将会执行假操作,该代理的连接将由LCN连接池管理。

模式特点

  • 该模式对代码的嵌入性为低。
  • 该模式仅限于本地存在连接对象且可通过连接对象控制事务的模块。
  • 该模式下的事务提交与回滚是由本地事务方控制,对于数据一致性上有较高的保障。
  • 该模式缺陷在于代理的连接需要随事务发起方一共释放连接,增加了连接占用的时间。

源码解读

首先我们来看几个关键的类DataSourceAspect-数据源切面类、TransactionAspect事务切面类、LcnConnectionProxylcn
连接代理类、DTXLogicWeaver分布式事务调度器、DTXServiceExecutor分布式事务执行器

DataSourceAspect的作用

  • 源码
@Aspect
@Component
public class DataSourceAspect implements Ordered {
    private static final Logger log = LoggerFactory.getLogger(DataSourceAspect.class);
    private final TxClientConfig txClientConfig;
    private final DTXResourceWeaver dtxResourceWeaver;

    public DataSourceAspect(TxClientConfig txClientConfig, DTXResourceWeaver dtxResourceWeaver) {
        this.txClientConfig = txClientConfig;
        this.dtxResourceWeaver = dtxResourceWeaver;
    }

    @Around("execution(* javax.sql.DataSource.getConnection(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        return this.dtxResourceWeaver.getConnection(() -> {
            return (Connection)point.proceed();
        });
    }

    public int getOrder() {
        return this.txClientConfig.getResourceOrder();
    }
}

由该类的源码,我们能够知道,lcn模式主要对数据库的连接进行了拦截代理。获取到数据库的连接交由lcn
来进行代理。

TransactionAspect 作用

  • 源码
@Aspect
@Component
public class TransactionAspect implements Ordered {
    private static final Logger log = LoggerFactory.getLogger(TransactionAspect.class);
    private final TxClientConfig txClientConfig;
    private final DTXLogicWeaver dtxLogicWeaver;

    public TransactionAspect(TxClientConfig txClientConfig, DTXLogicWeaver dtxLogicWeaver) {
        this.txClientConfig = txClientConfig;
        this.dtxLogicWeaver = dtxLogicWeaver;
    }
    
    @Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.LcnTransaction)")
    public void lcnTransactionPointcut() {
    }

    @Around("lcnTransactionPointcut() && !txcTransactionPointcut()&& !tccTransactionPointcut() && !txTransactionPointcut()")
    public Object runWithLcnTransaction(ProceedingJoinPoint point) throws Throwable {
        DTXInfo dtxInfo = DTXInfo.getFromCache(point);
        LcnTransaction lcnTransaction = (LcnTransaction)dtxInfo.getBusinessMethod().getAnnotation(LcnTransaction.class);
        dtxInfo.setTransactionType("lcn");
        dtxInfo.setTransactionPropagation(lcnTransaction.propagation());
        DTXLogicWeaver var10000 = this.dtxLogicWeaver;
        point.getClass();
        return var10000.runTransaction(dtxInfo, point::proceed);
    }

    public int getOrder() {
        return this.txClientConfig.getDtxAspectOrder();
    }
}

由该类的源码,我们能够明白,通过解析@LcnTransaction注解进行相应的操作。代码会调用到DTXLogicWeaver

DTXLogicWeaver 作用

    public Object runTransaction(DTXInfo dtxInfo, BusinessCallback business) throws Throwable {
        if (Objects.isNull(DTXLocalContext.cur())) {
            DTXLocalContext.getOrNew();
            log.debug("<---- TxLcn start ---->");
            DTXLocalContext dtxLocalContext = DTXLocalContext.getOrNew();
            TxContext txContext;
            if (this.globalContext.hasTxContext()) {
                txContext = this.globalContext.txContext();
                dtxLocalContext.setInGroup(true);
                log.debug("Unit[{}] used parent's TxContext[{}].", dtxInfo.getUnitId(), txContext.getGroupId());
            } else {
                txContext = this.globalContext.startTx();
            }

            if (Objects.nonNull(dtxLocalContext.getGroupId())) {
                dtxLocalContext.setDestroy(false);
            }

            dtxLocalContext.setUnitId(dtxInfo.getUnitId());
            dtxLocalContext.setGroupId(txContext.getGroupId());
            dtxLocalContext.setTransactionType(dtxInfo.getTransactionType());
            TxTransactionInfo info = new TxTransactionInfo();
            info.setBusinessCallback(business);
            info.setGroupId(txContext.getGroupId());
            info.setUnitId(dtxInfo.getUnitId());
            info.setPointMethod(dtxInfo.getBusinessMethod());
            info.setPropagation(dtxInfo.getTransactionPropagation());
            info.setTransactionInfo(dtxInfo.getTransactionInfo());
            info.setTransactionType(dtxInfo.getTransactionType());
            info.setTransactionStart(txContext.isDtxStart());
            boolean var15 = false;

            Object var6;
            try {
                var15 = true;
                var6 = this.transactionServiceExecutor.transactionRunning(info);
                var15 = false;
            } finally {
                if (var15) {
                    if (dtxLocalContext.isDestroy()) {
                        synchronized(txContext.getLock()) {
                            txContext.getLock().notifyAll();
                        }

                        if (!dtxLocalContext.isInGroup()) {
                            this.globalContext.destroyTx();
                        }

                        DTXLocalContext.makeNeverAppeared();
                        TracingContext.tracing().destroy();
                    }

                    log.debug("<---- TxLcn end ---->");
                }
            }

            if (dtxLocalContext.isDestroy()) {
                synchronized(txContext.getLock()) {
                    txContext.getLock().notifyAll();
                }

                if (!dtxLocalContext.isInGroup()) {
                    this.globalContext.destroyTx();
                }

                DTXLocalContext.makeNeverAppeared();
                TracingContext.tracing().destroy();
            }

            log.debug("<---- TxLcn end ---->");
            return var6;
        } else {
            return business.call();
        }
    }

以上代码是该类的核心逻辑,可以看出来TX-LCN事务的处理全部都是走的这个类的该方法,最终会调用到DTXServiceExecutor分布式事务执行器

DTXServiceExecutor 作用


    /**
     * 事务业务执行
     *
     * @param info info
     * @return Object
     * @throws Throwable Throwable
     */
    public Object transactionRunning(TxTransactionInfo info) throws Throwable {

        // 1. 获取事务类型
        String transactionType = info.getTransactionType();

        // 2. 获取事务传播状态
        DTXPropagationState propagationState = propagationResolver.resolvePropagationState(info);

        // 2.1 如果不参与分布式事务立即终止
        if (propagationState.isIgnored()) {
            return info.getBusinessCallback().call();
        }

        // 3. 获取本地分布式事务控制器
        DTXLocalControl dtxLocalControl = txLcnBeanHelper.loadDTXLocalControl(transactionType, propagationState);

        // 4. 织入事务操作
        try {
            // 4.1 记录事务类型到事务上下文
            Set<String> transactionTypeSet = globalContext.txContext(info.getGroupId()).getTransactionTypes();
            transactionTypeSet.add(transactionType);

            dtxLocalControl.preBusinessCode(info);

            // 4.2 业务执行前
            txLogger.txTrace(
                    info.getGroupId(), info.getUnitId(), "pre business code, unit type: {}", transactionType);

            // 4.3 执行业务
            Object result = dtxLocalControl.doBusinessCode(info);

            // 4.4 业务执行成功
            txLogger.txTrace(info.getGroupId(), info.getUnitId(), "business success");
            dtxLocalControl.onBusinessCodeSuccess(info, result);
            return result;
        } catch (TransactionException e) {
            txLogger.error(info.getGroupId(), info.getUnitId(), "before business code error");
            throw e;
        } catch (Throwable e) {
            // 4.5 业务执行失败
            txLogger.error(info.getGroupId(), info.getUnitId(), Transactions.TAG_TRANSACTION,
                    "business code error");
            dtxLocalControl.onBusinessCodeError(info, e);
            throw e;
        } finally {
            // 4.6 业务执行完毕
            dtxLocalControl.postBusinessCode(info);
        }
    }

通过以上代码可以看出,该类是整个事务执行关键类。

以上就是LCN模式比较核心的代码,其他的分支代码就不一一赘述了

实战

由上一篇分布式事务之TX-LCN 我们规划了俩个TC分别是lcn-order
服务和lcn-pay服务,我们的思路是订单服务调用支付服务,分别在订单服务表t_order和支付服务表t_pay中插入插入数据。

订单服务核心代码和数据表脚本

  • 代码
/**
 * @author:triumphxx
 * @Date:2021/10/24
 * @Time:2:13 下午
 * @微信公众号:北漂码农有话说
 * @网站:http://blog.triumphxx.com.cn
 * @GitHub https://github.com/triumphxx
 * @Desc:
 **/
@RestController
public class LcnOrderController {

    @Autowired
    TOrderDao tOrderDao;

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/add-order")
    @Transactional(rollbackFor = Exception.class)
    @LcnTransaction
    public String add(){
        TOrder bean = new TOrder();
        bean.setTId(1);
        bean.setTName("order");
        restTemplate.postForEntity("http://lcn-pay/add-pay","",String.class);
//        int i = 1/0;
        tOrderDao.insert(bean);
        return "新增订单成功";
    }
}
  • 脚本
CREATE TABLE `t_order` (
   `t_id` int(11) NOT NULL,
   `t_name` varchar(45) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1

支付服务核心代码和数据表脚本

  • 代码
/**
 * @author:triumphxx
 * @Date:2021/10/24
 * @Time:2:26 下午
 * @微信公众号:北漂码农有话说
 * @网站:http://blog.triumphxx.com.cn
 * @GitHub https://github.com/triumphxx
 * @Desc:
 **/
@RestController
public class LcnPayController {
    @Autowired
    TPayDao tPayDao;

    @PostMapping("/add-pay")
    @Transactional(rollbackFor = Exception.class)
    @LcnTransaction
    public String addPay(){
        TPay tPay = new TPay();
        tPay.setTId(1);
        tPay.setTName("t_pay");
        int i = tPayDao.insertSelective(tPay);
        return "新增支付成功";

    }
}
  • 脚本
CREATE TABLE `t_pay` (
     `t_id` int(11) NOT NULL,
     `t_name` varchar(45) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1

测试流程

  • 启动Redis
  • 启动TM
  • 启动注册中心eureka-server
  • 启动服务lcn-order
  • 启动服务lcn-pay
  • 请求接口http://localhost:8001/add-order
  • 代码创造异常看数据是否进行回滚

小结

本篇我们分析了TX-LCN分布式事务的lcn模式的原理及相关源码,以及搭建服务的进行测试。希望能对大家有所帮助。
源码地址源码传送门

TX-LCN分布式事务之LCN模式的更多相关文章

  1. LCN分布式事务管理(一)

    前言 好久没写东西了,9月份换了份工作,一上来就忙的要死.根本没时间学东西,好在新公司的新项目里面遇到了之前没遇到过的难题.那遇到难题就要想办法解决咯,一个请求,调用两个服务,同时操作更新两个数据库. ...

  2. 基于Dubbo的分布式事务框架(LCN)

    原文地址:http://原文地址:https://github.com/1991wangliang/transaction 基于Dubbo的分布式事务框架(LCN) 该框架依赖Redis/dubbo/ ...

  3. Dubbo学习系列之十五(Seata分布式事务方案TCC模式)

    上篇的续集. 工具: Idea201902/JDK11/Gradle5.6.2/Mysql8.0.11/Lombok0.27/Postman7.5.0/SpringBoot2.1.9/Nacos1.1 ...

  4. Dubbo学习系列之十四(Seata分布式事务方案AT模式)

    一直说写有关最新技术的文章,但前面似乎都有点偏了,只能说算主流技术,今天这个主题,我觉得应该名副其实.分布式微服务的深水区并不是单个微服务的设计,而是服务间的数据一致性问题!解决了这个问题,才算是把分 ...

  5. 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾

    https://mp.weixin.qq.com/s/67NvEVljnU-0-6rb7MWpGw 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾 原创 蚂蚁金 ...

  6. 通过Dapr实现一个简单的基于.net的微服务电商系统(十九)——分布式事务之Saga模式

    在之前的系列文章中聊过分布式事务的一种实现方案,即通过在集群中暴露actor服务来实现分布式事务的本地原子化.但是actor服务本身有其特殊性,场景上并不通用.所以今天来讲讲分布式事务实现方案之sag ...

  7. 微服务分布式事务之LCN、TCC

    在亿级流量架构之分布式事务解决方案对比中, 已经简单阐明了从本机事务到分布式事务的演变过程, 文章的最后简单说明了TCC事务, 这儿将会深入了解TCC事务是原理, 以及理论支持, 最后会用Demo举例 ...

  8. 分布式事务(Seata) 四大模式详解

    前言 在上一节中我们讲解了,关于分布式事务和seata的基本介绍和使用,感兴趣的小伙伴可以回顾一下<别再说你不知道分布式事务了!> 最后小农也说了,下期会带给大家关于Seata中关于sea ...

  9. lcn 分布式事务协调者集群原理

    lcn集群原理图: 1.实现原理: 1.1 LCN获取tm协调者注册地址 1. LCN客户端项目启动的时候,首先会注册到事务协调者中去,然后读取配置文件tm事务协调者的注册地址. 2. 向该事务协调者 ...

随机推荐

  1. Throwable中3个异常的方法

  2. Swagger-初见

    目录 Swagger简介 SpringBoot集成Swagger 配置Swagger 配置扫描接口 配置Swagger开关 配置API分组 实体配置 常用注解 Swagger简介 前后端分离 前端 - ...

  3. CodeForce-811B Vladik and Complicated Book(水题)

    http://codeforces.com/problemset/problem/811/B 题意: 给定一个长度为 N 不重复的数字序列,然后对其进行 M 次询问. 每次询问含L,R,X三个值,问如 ...

  4. AS插件快速生成javabean

    https://blog.csdn.net/u010227042/article/details/103803198

  5. 【PHP数据结构】交换排序:冒泡、快排

    上篇文章中我们好好地学习了一下插入类相关的两个排序,不过,和交换类的排序对比的话,它们真的只是弟弟.甚至可以说,在所有的排序算法中,最出名的两个排序都在今天要介绍的交换排序中了.不管是冒泡.还是快排, ...

  6. PHP的OpenSSL加密扩展学习(一):对称加密

    我们已经学过不少 PHP 中加密扩展相关的内容了.而今天开始,我们要学习的则是重点中的重点,那就是 OpenSSL 加密扩展的使用.为什么说它是重点中的重点呢?一是 OpenSSL 是目前 PHP 甚 ...

  7. 使用metaweblog API实现通用博客发布 之 版本控制

    使用metaweblog API实现通用博客发布 之 版本控制 接上一篇本地图片自动上传以及替换路径,继续解决使用API发布博客的版本控制问题. 当本地文档修订更新以后,如何发现版本更新,并自动发布到 ...

  8. ssh 执行 shell脚本执行jps时:-bash: jps: command not found

    转至: https://www.codeleading.com/article/67592908468/ 我构建了hadoop集群.我们一定会写一个shell脚本去每一个节点上去jps,查看每个节点的 ...

  9. markdown写作系统

    markdown 电脑本地使用typora,可保存为md文件直接上传到有道云笔记中 直接使用博客园做为图床,可以存为草稿,然后把复制连接 有道云笔记 粘贴博客园的图片连接

  10. SQL连接查询的执行顺序?

    sql和mysql执行顺序,发现内部机制是一样的.最大区别是在别名的引用上. 一.sql执行顺序 (1)from (2)on (3)join (4)where (5)group by(开始使用sele ...