SpringCloud集成分布式事务LCN 5.0.2

前言

LCN并不生产事务,LCN只是本地事务的协调工

一、分布式事务

1、什么是分布式事务

分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

2、分布式事务产生的原因

从上面本地事务来看,我们可以看为两块,一个是service产生多个节点,另一个是resource产生多个节点。

①service多个节点的情况

随着互联网快速发展,微服务,SOA等服务架构模式正在被大规模的使用,举个简单的例子,一个公司之内,用户的资产可能分为好多个部分,比如余额,积分,优惠券等等。在公司内部有可能积分功能由一个微服务团队维护,优惠券又是另外的团队维护

这样的话就无法保证积分扣减了之后,优惠券能否扣减成功。

②resource多个节点

同样的,互联网发展得太快了,我们的Mysql一般来说装千万级的数据就得进行分库分表,对于一个支付宝的转账业务来说,你给的朋友转钱,有可能你的数据库是在北京,而你的朋友的钱是存在上海,所以我们依然无法保证他们能同时成功。

二、LCN的原理图

在上图中,微服务A,微服务B,TxManager事务协调器,都需要去Eureka中去注册服务。Eureka是用于TxManager与其他服务之间的相互服务发现。redis是用于存放我们事务组的信息以及补偿的信息。然后微服务A与微服务B他们都需要去配置上我们TxClient的包架构(代码的包架构),来支持我们的LCN框架以及他们的数据库。

三、LCN的执行步骤

模拟场景演示

若存在事务发起方、参与方A、参与方B。调用关系图如下

那么他们正常执行业务的时序图为:

若参与方B出现异常,那么他们的业务时序图为:

若他们的调用关系是这样的情况

此时发生参与方B出现异常时他们的时序图为:

四、LCN的事务协调机制

如图,假设服务已经执行到关闭事务组的过程了,那么接下来作为一个模块执行通知给TxManager,然后告诉它本次事务已经完成。那么如图中的TxManager下一个动作就是通过事务组的id,获取到本次事务组的事务信息,然后查看一下对应有哪几个模块参与,然后如果是有A,B,C三个模块,那么对应的对三个模块做通知、提交、回滚。

那么提交的时候是提交给谁呢?

是提交给了我们的TxClient模块。然后TxClient模块下有一个连接池,就是框架自定义的一个连接池(如图DB连接池);这个连接池其实就是在没有通知事务之前一直占有着这次事务的连接资源,就是没有释放。但是他在切面里面执行了close方法。在执行close方法的时候,其实是“假关闭”,也就是没有真正的关闭,实际的资源是没有释放的。这个资源是掌握在LCN 的连接池里的。

​ 然后当TxManager通知提交或事务回滚的时候呢?

TxManager会通知我们的TxClient端。然后TxClient会去执行相应的提交或回滚。提交或回滚之后再去关闭连接。这就是LCN的事务协调机制。说白了就是代理DataSource的机制;相当于是拦截了一下连接池,控制了连接池的事务提交。

五、补偿机制

为什么需要事务补偿?

事务补偿是指在执行某个业务方法时,本应该执行成功的操作却因为服务器挂机或者网络抖动等问题导致事务没有正常提交,此种场景就需要通过补偿来完成事务,从而达到事务的一致性。

补偿机制的触发条件?

当执行关闭事务组步骤时,若发起方接受到失败的状态后将会把该次事务识别为待补偿事务,然后发起方将该次事务数据异步通知给TxManager。TxManager接受到补偿事务以后先通知补偿回调地址,然后再根据是否开启自动补偿事务状态来补偿或保存该次切面事务数据。

补偿事务机制?

LCN的补偿事务原理是模拟上次失败事务的请求,然后传递给TxClient模块然后再次执行该次请求事务。

简单的说:lcn事务补偿是在在服务挂机和网络抖动情况下;服务挂机是指在完成三个核心步骤的时候
尤其也只有最后一步关闭事务组时,去执行关闭事务组的时候,比如本次事务是要提交的。txManager接收到提交的请求再去通知的时候发现通知不到了(讲解:由于LCN并不生产事务,LCN只是本地事务的协调工,所以最后真正的事务提交还是通过txManager去通知各个服务提供者去提交各自的事务,通知服务提供者。如果通知不到的话,那就返回标志给发起方 注意:是通知服务提供方,而不是通知服务发起方)。
(通知不到也就两种原因服务挂了和网络出问题)在这种情况下TxManager会做一个标示;然后返回给发起方。告诉他本次事务有存在没有通知到的情况。
那么如果是接收到这个信息之后呢,发起方就会做一个标示,标示本次事务是需要补偿事务的。这就是事务补偿机制。

LCN是怎么去实现事务补偿呢?

首先他会根据发起方拿到的TxManager的标示,判断是否需要做事务补偿,如果需要,他会首先在本地去记录一下日志 ,然后再把本次切面的信息,也就是发起方本次事务切面的信息
以及事务组的id信息提交给TxManager;然后TxManager接收到这次数据之后,他会查询到本次事务组的整个信息,获取到本次事务组信息之后呢,他会把这些信息一块保存到redis下,也就是做为补偿数据一块存下来。
在存下来的时候,他会先执行一次叫做回调接口的请求;这个回调接口其实是指的是回调给第三方服务的一个地址,也就是我们自己的服务地址。这里是作为一次通知用的,我们来看下4.0的界面
当补偿发生之后,TxManager记录完数据以后会通知这个回调接口地址如图:

告诉你有补偿信息存在。这个地方我们就可以做一些通知,例如邮件、短信提醒功能,通知给相应的人员。让他们知道现在我们的服务里存在了补偿,要及时处理。

当然你也可以开启自动补偿功能(如上图),当开启自动补偿之后的话,每当触发补偿机制的时候,就会通知上面的补偿回调接口(讲解:回调接口这个地方我们就可以做一些通知,例如邮件、短信提醒功能,通知给相应的人员。让他们知道现在我们的服务里存在了补偿,要及时处理。),通知完之后,他会把本次事务去补偿一下。他是如何执行的呢?上面说到他会把切面的信息上传上来,他会把切面的信息上传给发起方,传递给发起方以后让发起方重复执行本次事务。但是有一个差异性的地方是在于,他在去做模块提交的时候呢,会根据历史的提交数据(补偿数据)做一个逆向的操作,也就是说,必须如下图来说:

梳理一下流程:当关闭事务组之后,txManager会陆续通知各个服务参与方(注:是通知参与方而不是通知发起方)去提交各自的事务,先通知Demo2提交,然后再通知Demo3提交,最后如果都提交成功,那么txManager就会给发起方Demo1发送一个成功标志,然后Demo1自己再提交一下即可完成整个事务组事务的提交了。

如果在中间某一步出了异常,比如我们这三个模块Demo1、Demo2、Demo3.那么现在如果通知Demo2提交成功,然后在通知Demo3提交的时候由于网络抖动的原因导致txManager通知Demo3失败了。然后这一次作为补偿通知(注:这个时候就触发了补偿机制)给了发起方Demo1,发起方调用TxManager,告诉Demo1这一次需要补偿
那么TxManager发信息给Demo1,Demo1执行补偿的时候呢,首先启动方(也就是“发起方”)事务是要回滚的;那么Demo2、Demo3是否回滚取决于上次的事务请求。上面说Demo2提交成功了,也就是说他要回滚(讲解:因为我们要进行重试机制嘛,所以必须要清除之前所做的一切操作),Demo3失败,他是要补偿的,是要提交的。那么在TxManager内部怎么去实现这一点呢?下面说说它的原理

原理如下:还是跟以前一样,就是说其实这次是与之前的执行事务流程是相同的;唯一不同的是在于执行关闭,就是在这次补偿的事务过程中的关闭事务的时候。
TxManager会判断历史数据,会根据历史数据(补偿数据)做差异性的通知,是判断谁要需要提交,谁需要回滚。这就是补偿机制的原理。

本文参考自;https://blog.csdn.net/gududedabai/article/details/83012487#4、补偿机制

六、如何使用LCN?

第一步:

先在lcn官网【http://www.txlcn.org/】 找到GitHub 地址【https://github.com/codingapi/tx-lcn】,拷下所有的源码

第二步:

解压下载的zip,放置在一个目录下,用IDEA打开【注意打开父层项目】



问题:
git上的源码是1.5.4版本的Spring Boot,若使用2.0版本,项目启动会报错,主要原因是2.0版本移除了context.embedded包,导致EmbeddedServletContainerInitializedEvent.java对象找不到(该对象主要和Listener配合使用,“通过监听,在刷新上下文后将要发布的事件,用于获取A的本地端口运行服务器。”TxClient主要从该Event中获取服务器端口号。)
导入完整的jar包,然后下面就要开始更改源码中不支持spring boot 2.X的部分

第三步:
修改transaction-springcloud 项目下com.codingapi.tx.springcloud.listener包中的ServerListener.java

源码更改为:

@Component
public class ServerListener implements ApplicationListener<ApplicationEvent> {private Logger logger = LoggerFactory.getLogger(ServerListener.class);private int serverPort;@Value("${server.port}")private String port;@Autowiredprivate InitService initService;@Overridepublic void onApplicationEvent(ApplicationEvent event) {//        logger.info("onApplicationEvent -> onApplicationEvent. "+event.getEmbeddedServletContainer());//        this.serverPort = event.getEmbeddedServletContainer().getPort();//TODO Spring boot 2.0.0没有EmbeddedServletContainerInitializedEvent 此处写死;modify by youngthis.serverPort = Integer.parseInt(port);Thread thread = new Thread(new Runnable() {@Overridepublic void run() {// 若连接不上txmanager start()方法将阻塞initService.start();}});thread.setName("TxInit-thread");thread.start();}public int getPort() {return this.serverPort;}public void setServerPort(int serverPort) {this.serverPort = serverPort;}
}

@Component注解会自动扫描配置文件中的server.port值;

第四步:

修改tx-manager项目下com.codingapi.tm.listener包中的ApplicationStartListener.java

@Component
public class ApplicationStartListener implements ApplicationListener<ApplicationEvent> {@Overridepublic void onApplicationEvent(ApplicationEvent event) {//TODO Spring boot 2.0.0没有EmbeddedServletContainerInitializedEvent 此处写死;modify by young
//        int serverPort = event.getEmbeddedServletContainer().getPort();String ip = getIp();Constants.address = ip+":48888";//写死端口号,反正TxManager端口也是配置文件配好的(●′ω`●)}private String getIp(){String host = null;try {host = InetAddress.getLocalHost().getHostAddress();} catch (UnknownHostException e) {e.printStackTrace();}return host;}
}

第五步:
改写tx-manager项目下com.codingapi.tm.manager.service.impl包中MicroServiceImpl.java类的getState()方法

@Overridepublic TxState getState() {TxState state = new TxState();String ipAddress = "";//TODO Spring boot 2.0.0没有discoveryClient.getLocalServiceInstance() 用InetAddress获取host;modify by young//String ipAddress = discoveryClient.getLocalServiceInstance().getHost();try {ipAddress = InetAddress.getLocalHost().getHostAddress();} catch (Exception e) {e.printStackTrace();}if (!isIp(ipAddress)) {ipAddress = "127.0.0.1";}state.setIp(ipAddress);state.setPort(Constants.socketPort);state.setMaxConnection(SocketManager.getInstance().getMaxConnection());state.setNowConnection(SocketManager.getInstance().getNowConnection());state.setRedisSaveMaxTime(configReader.getRedisSaveMaxTime());state.setTransactionNettyDelayTime(configReader.getTransactionNettyDelayTime());state.setTransactionNettyHeartTime(configReader.getTransactionNettyHeartTime());state.setNotifyUrl(configReader.getCompensateNotifyUrl());state.setCompensate(configReader.isCompensateAuto());state.setCompensateTryTime(configReader.getCompensateTryTime());state.setCompensateMaxWaitTime(configReader.getCompensateMaxWaitTime());state.setSlbList(getServices());return state;}

注:有些服务器这个方法获取不到ip,注册到tx.manager的ip为127.0.0.1

 @Value("${spring.cloud.client.ip-address}")private String ipAddress;public TxState getState() {TxState state = new TxState();//      String ipAddress = "";//TODO Spring boot 2.0.0没有discoveryClient.getLocalServiceInstance() 用InetAddress获取host;modify by young//String ipAddress = discoveryClient.getLocalServiceInstance().getHost();
//        try {
//           ipAddress = InetAddress.getLocalHost().getHostAddress();
//        } catch (Exception e) {
//           e.printStackTrace();
//        }if (!isIp(ipAddress)) {ipAddress = "127.0.0.1";}
...
}

第七步:
改写tx-client项目下TransactionServerFactoryServiceImpl.java第60行;

//分布式事务已经开启,业务进行中 **/if (info.getTxTransactionLocal() != null || StringUtils.isNotEmpty(info.getTxGroupId())) {//检查socket通讯是否正常 (第一次执行时启动txRunningTransactionServer的业务处理控制,然后嵌套调用其他事务的业务方法时都并到txInServiceTransactionServer业务处理下)if (SocketManager.getInstance().isNetState()) {if (info.getTxTransactionLocal() != null) {return txDefaultTransactionServer;} else {
//                    if(transactionControl.isNoTransactionOperation() // 表示整个应用没有获取过DB连接
//                        || info.getTransaction().readOnly()) { //无事务业务的操作
//                        return txRunningNoTransactionServer;
//                    }else {
//                        return txRunningTransactionServer;
//                    }if(!transactionControl.isNoTransactionOperation()) { //TODO 有事务业务的操作 modify by youngreturn txRunningTransactionServer;}else {return txRunningNoTransactionServer;}}} else {logger.warn("tx-manager not connected.");return txDefaultTransactionServer;}}//分布式事务处理逻辑*结束***********/

重新maven deploy打出jar包,上传自己的maven服务器;
就此Spring Boot2.0引入LCN分布式事务正常启动。

优化
git项目上引用tx.manager.url(txmanager服务器路径)需要在各自的微服务中添加TxManagerHttpRequestService.java和TxManagerTxUrlService.java的实现类;

可将两个方法继承到springcloud源码包中
(1)添加TxManagerConfiguration.java

@Configuration
@ConditionalOnProperty(value = "tx.manager.url")
public class TxManagerConfiguration {@Bean@RefreshScope@ConfigurationProperties(prefix = "tx.manager")public TxManagerProperity txManagerProperity(){return new TxManagerProperity();};@Beanpublic TxManagerTxUrlService txManagerTxUrlService(){return new TxManagerTxUrlServiceImpl(txManagerProperity());}@Beanpublic TxManagerHttpRequestService txManagerHttpRequestService(){return new TxManagerHttpRequestServiceImpl();}
}

(2) 添加TxManagerProperity .java

public class TxManagerProperity {private String url;public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}
}

(3)修改TxManagerTxUrlServiceImpl.java

public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService {private TxManagerProperity property;public TxManagerTxUrlServiceImpl(TxManagerProperity property) {this.property = property;}@Overridepublic String getTxUrl() {return property.getUrl();}
}

未完待续:
原文参考自;https://www.jianshu.com/p/453741e0f28f

SpringCloud集成分布式事务LCN相关推荐

  1. springCloud集成分布式事务LCN 5.0.2

    TX-LCN的3种模式 LCN5.0.2有3种模式,分别是LCN模式,TCC模式,TXC模式 LCN模式: LCN模式是通过代理Connection的方式实现对本地事务的操作,然后在由TxManage ...

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

    框架特点 支持各种基于spring的db框架 兼容springcloud.dubbo 使用简单,代码完全开源 基于切面的强一致性事务框架 高可用,模块可以依赖dubbo或springcloud的集群方 ...

  3. SpringCloud入门 - 分布式事务【概念、常见框架选择 - tx-lcn】

    分布式事务简介: 事务: 指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行. 本地事务:  SqlSessionfactory   --> 一个数据库范围类事务管理. 分 ...

  4. SpringCloud(6) 分布式事务【概念、常见框架选择 - tx-lcn】

    分布式事务简介: 事务: 指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行. 本地事务:  SqlSessionfactory   --> 一个数据库范围类事务管理. 分 ...

  5. SpringCloud 之分布式事务解决方案

    文章目录 分布式事务几种方案 2PC 模式 柔性事务-TCC 事务补偿型方案 柔性事务-可靠消息+最终一致性方案(异步确保型) 分布式事务几种方案 2PC 模式 是一种刚性事务,遵循ACID原则,强一 ...

  6. 3分钟搞定SpringBoot+Mybatis+druid多数据源和分布式事务

    在一些复杂的应用开发中,一个应用可能会涉及到连接多个数据源,所谓多数据源这里就定义为至少连接两个及以上的数据库了. 下面列举两种常用的场景: 一种是读写分离的数据源,例如一个读库和一个写库,读库负责各 ...

  7. SpringCloud微服务实战——搭建企业级开发框架(二十七):集成多数据源+Seata分布式事务+读写分离+分库分表

      读写分离:为了确保数据库产品的稳定性,很多数据库拥有双机热备功能.也就是,第一台数据库服务器,是对外提供增删改业务的生产服务器:第二台数据库服务器,主要进行读的操作.   目前有多种方式实现读写分 ...

  8. SpringCloud(7) LCN分布式事务框架入门

    官网:http://www.txlcn.org/ LCN原理:https://github.com/codingapi/tx-lcn/wiki/LCN%E5%8E%9F%E7%90%86 入门测试: ...

  9. SpringCloud - LCN分布式事务框架

    官网:http://www.txlcn.org/ LCN原理:https://github.com/codingapi/tx-lcn/wiki/LCN%E5%8E%9F%E7%90%86 入门测试: ...

  10. SpringCloud整合TX-LCN5.0.2使用LCN模式实现分布式事务

    一.TM配置 pom.xml文件中添加依赖: <dependency><groupId>com.codingapi.txlcn</groupId><artif ...

最新文章

  1. ListView详解(二)
  2. mysql pos点是什么,MySQL 5.6 主从报错一例
  3. java基础(十一) 枚举类型
  4. pythonqueue函数_如何将函数和参数放入python队列?
  5. html5的api有什么区别,HTML入门--浅谈HTML和HTML5有什么区别?
  6. 广度优先搜索(BFS)
  7. 转换整形数字为16进制字符串
  8. 【语音处理】基于matlab GUI语音信号处理与滤波【含Matlab源码 1663期】
  9. Android IntentService的使用与源码解析
  10. 免积分下载百度文库的文章
  11. 台电tbook10s官方固件_【11月1日】台电官方系统固件更新
  12. 卷积操作改进之Involution: Inverting the Inherence of Convolution for Visual Recognition
  13. 信息收集 | 利用Google搜索语法进行Google Hacking
  14. 如何pull gcr上的镜像
  15. VMware:在部分链上无法执行所调用的函数,请打开父虚拟磁
  16. 防疫与复工同行,长沙望城进入“双统筹”的关键时刻
  17. C语言如何产生一个随机数,rand函数的使用
  18. 数组中出现次数超过一半的数字
  19. 计算机上的字体太小怎么办,Win7电脑网页字体变小了怎么办?
  20. 糗事百科爬虫用户统计

热门文章

  1. 数据结构与算法-散列表
  2. 您需要来自Administration的权限才能对此文件夹进行更改’怎么删除文件,window10删除2345流氓软件
  3. 计算机配置作业2000,求一组近期装计算机配置清单 价格清楚
  4. 正点原子i.mx6ullMini开发板用SPI驱动RC522门禁卡模块
  5. Xbox one VS. ps4
  6. Matlab中Fatal Error On Startup
  7. html5 边框效果图,图片边框border-image的用法
  8. 请教FlashCs3导入图片时出现无法导入问题。
  9. hdu 3987 最小割边数模板题
  10. 【BUG】unresolvable R_ARM_THM_CALL relocation against symbol `strlen'