事务失败返回_LCN-分布式事务管理

一、 什么是分布式事务

分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位 于不同的分布式系统的不同节点之上。 举个栗子: 电商系统中的订单系统与库存系统

9c3e9590ed9cad65238ea5a76beba777.png

图中包含了库存和订单两个独立的微服务,每个微服务维护了自己的数据库。在交易系 统的业务逻辑中,一个商品在下单之前需要先调用库存服务,进行扣除库存,再调用订单服务,创建订单记录。

f605416142ed3ccbefbd4a8cd6e59fc7.png

正常情况下,两个数据库各自更新成功,两边数据维持着一致性。 如果在非正常情况下,有可能库存的扣减完成了,随后的订单记录却因为某些原因插入 失败。或者是订单创建成功了,但是库存扣除商品的数据量失败了,这个时候,两边数据就 失去了应有的一致性。

f605416142ed3ccbefbd4a8cd6e59fc7.png

这时候就需要保证事务的一致性了,单数据源的用单机事务来保证。多数据源就需要依 赖分布式事务来处理。

二、 XA 的两阶段提交方案

  1. 什么是 XA 协议
    XA 协议由 OracleTuxedo 首先提出的,并交给 X/Open 组织,作为资源管理器(数据库)与事务管理器的接口标准。目前,Oracle、Informix、DB2 和 Sybase 等各大数据库厂家都提供对 XA 的支持。
    XA 协议采用两阶段提交方式来管理分布式事务。XA 接口提供资源管理器与事务管理器之间进行通信的标准接口。 XA 就是 X/OpenDTP 定义的交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。XA 接口函数由数据库厂商提供。 X/Open 组织(即现在的 OpenGroup)定义了分布式事务处理模型。X/Open DTP 模型 (1994)包括应用程序(AP)、事务管理器(TM)、资源管理器(RM)、通信资源管理 器(CRM)四部分。一般,常见的事务管理器(TM)是交易中间件,常见的资源管理器(RM) 是数据库,常见的通信资源管理器(CRM)是消息中间件。
  2. XA 协议的一阶段提交

4a6f67aaa7eb00de3698dbf673f8acb2.png

如果在程序中开启了事务,那么在应用程序发出提交/回滚请求后,数据库执行操作,而后将成功/失败返回给应用程序,程序继续执行。一阶段提交协议相对简单。优点也很直观,它不用再与其他的对象交互,节省了判断 步骤和时间,所以在性能上是在阶段提交协议中最好的。
但缺点也很明显:数据库确认执行事务的时间较长,出问题的可能性就随之增大。如果有多个数据源,一阶段提交协议无法协 调他们之间的关系。

3. XA 协议的二阶段提交
在一阶段协议的基础上,有了二阶段协议,二阶段协议的好处是添加了一个管理者角色。

1c567c8d2be653a51952fd4ecebf5ebd.png

很明显,二阶段协议通过将两层变为三层,增加了中间的管理者角色,从而协调多个数据源之间的关系,二阶段提交协议分为两个阶段。

5074f19c08de40d68b79ae0f2fee5303.png

应用程序调用了事务管理器的提交方法,此后第一阶段分为两个步骤:
1. 事务管理器通知参与该事务的各个资源管理器,通知他们开始准备事务。
2. 资源管理器接收到消息后开始准备阶段,写好事务日志并执行事务,但不提交,然后将是否就绪的消息返回给事务管理器(此时已经将事务的大部分事情做完,以后的内容耗时极小)。

82c6016a8243a98447d33b9ee2c1cf3e.png

第二阶段也分为两个步骤:
1. 事务管理器在接受各个消息后,开始分析,如果有任意其一失败,则发送回滚命令,否则发送提交命令。
2. 各个资源管理器接收到命令后,执行(耗时很少),并将提交消息返回给事务管理器。 事务管理器接受消息后,事务结束,应用程序继续执行。

为什么要分两步执行?
一是因为分两步,就有了事务管理器统一管理的机会;
二是尽可能晚地提交事务,让事务在提交前尽可能地完成所有能完成的工作,这样,最后的提交阶 段将是耗时极短,耗时极短意味着操作失败的可能性也就降低。
同时,二阶段提交协议为了保证事务的一致性,不管是事务管理器还是各个资源管理器,每执行一步操作,都会记录日志,为出现故障后的恢复准备依据。


缺点:
1. 二阶段提交协议的存在的弊端是阻塞,因为事务管理器要收集各个资源管理器的响应 消息,如果其中一个或多个一直不返回消息,则事务管理器一直等待,应用程序也被阻塞, 甚至可能永久阻塞。
2. 两阶段提交理论的一个广泛工业应用是 XA 协议。目前几乎所有收费的商业数据库都 支持 XA 协议。XA 协议已在业界成熟运行数十年,但目前它在互联网海量流量的应用场景 中,吞吐量这个瓶颈变得十分致命,因此很少被用到。

三、 TCC解决方案

  1. TCC 介绍 TCC 是由支付宝架构师提供的一种柔性解决分布式事务解决方案,主要包括三个步骤
    Try:预留业务资源/数据效验
    Confirm:确认执行业务操作
    Cancel:取消执行业务操作

f241732e8c7295ecbf83da8a2788378b.png
  1. TCC 原理 TCC 方案在电商、金融领域落地较多。
    TCC 方案其实是两阶段提交的一种改进。其将整个业务逻辑的每个分支显式的分成了 Try、Confirm、Cancel 三个操作。Try 部分完成 业务的准备工作,confirm 部分完成业务的提交,cancel 部分完成事务的回滚。 基本原理如 下图所示。

c00fcc05b8f3749482bdba7e4d7888a6.png

事务开始时,业务应用会向事务协调器注册启动事务。之后业务应用会调用所有服务的 try 接口,完成一阶段准备。之后事务协调器会根据 try 接口返回情况,决定调用 confirm 接口或者 cancel 接口。如果接口调用失败,会进行重试。 微服务倡导服务的轻量化、易部署,而 TCC 方案中很多事务的处理逻辑需要应用自己 编码实现,复杂且开发量大

3. TCC 的关键流程如下图(以创建订单和扣减库存为例子)

bbd0fa769e0bba90dd05f0b8aa81cd1b.png
  1. TCC 优缺点
    TCC优点:
    让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。
    TCC不足之处:
    对应用的侵入性强。业务逻辑的每个分支都需要实现 try、confirm、cancel 三个操作,应用侵入性较强,改造成本高。实现难度较大。需要按照网络状态、系统故障等不同的失败原因实现不同的 回滚策略。为了满足一致性的要求,confirm 和 cancel 接口必须实现幂等。

四、 分布式事务中间件解决方案

分布式事务中间件其本身并不创建事务,而是基于对本地事务的协调从而达到事务一致性的效果。典型代表有:阿里的 GTS(https://www.aliyun.com/aliware/txc)、开源应用 LCN。 其实现原理如下:

17f4ab92725e38ae03d49c5e2e5afa6f.png

五、 什么是 LCN 框架

  1. LCN 框架的由来 LCN 并不生产事务,LCN 只是本地事务的协调工。 在设计框架之初的 1.0~2.0 的版本时,框架设计的步骤是如下的,各取其首字母得来 的 LCN 命名。 锁定事务单元(lock)、确认事务模块状态(confirm)、通知事务(notify)
  2. LCN 框架相关资料
    tx-lcn 官方地址:https://www.txlcn.org/
    tx-lcn Github 地址:https://github.com/codingapi/tx-lcn
    tx-lcn 服务下载地址:https://pan.baidu.com/s/1cLKAeE#list/path=%2F
    tx-lcn 服务源码地址:https://github.com/codingapi/tx-lcn/tree/master/tx-manager

六、 LCN 框架原理及执行步骤

  1. LCN 的执行原理 LCN 原理

a0cdac2ffaf6edf7e5926cc27785d215.png

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

2. LCN 执行步骤

1. 创建事务组:
事务组是指的我们在整个事务过程中把各个节点(微服务)单元的事务信息存储在一 个固定单元里。但这个信息并不是代表是事务信息,而是只是作为一个模块的标示信息。 创建事务组是指在事务发起方开始执行业务代码之前先调用 TxManager 创建事务组 对象,然后拿到事务标示 GroupId 的过程。
2. 添加事务组:
添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息添加通知给 TxManager 的操作。
3. 关闭事务组:
是指在发起方执行完业务代码以后,将发起方执行结果状态通知给 TxManager 的动 作。当执行完关闭事务组的方法以后,TxManager 将根据事务组信息来通知相应的参与模 块提交或回滚事务。
4. 业务执行流程图

cee29a8fce21dfc10a3a8199eae4e099.png

七、 什么是 LCN 的事务协调机制

1a6a98c0bb1e0f2777bdb474ab3f0852.png

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

1. 那么提交的时候是提交给谁呢?
提交给了我们的 TxClient 模块。然后 TxCliient 模块下有一个连接池,就是框架自定义的一个连接池(如图 DB 连接池);这个连接池其实就是在没有通知事务之前一直占有着 这次事务的连接资源,就是没有释放。但是他在切面里面执行了 close 方法。在执行 close 的时候。如果需要(TxManager)分布式事务框架的连接。他被叫做“假关闭”,也就是没有关闭,只是在执行了一次关闭方法。实际的资源是没有释放的。这个资源是掌握在 LCN 的连接池里的。

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

八、 什么是 LCN 的事务补偿机制

  1. 什么是补偿事务机制?
    LCN 的补偿事务原理是模拟上次失败事务的请求,然后传递给 TxClient 模块然后再次执行该次请求事务。 简单的说:lcn 事务补偿是指在服务挂机和网络抖动情况下 txManager 无法通知事务单元时。(通知不到也就两种原因服务挂了和网络出问题)在这种情况下 TxManager 会做一个标示;然后返回给发起方。告诉他本次事务有存在没有通知到的情况。那么如果是接收到这个信息之后呢,发起方就会做一个标示,标示本次事务是需要补偿事务的。这就是事务补偿机制。
  2. 为什么需要事务补偿?
    事务补偿是指在执行某个业务方法时,本应该执行成功的操作却因为服务器挂机或者网络抖动等问题导致事务没有正常提交,此种场景就需要通过补偿来完成事务,从而达到事务的一致性。
  3. 补偿机制的触发条件?
    当执行关闭事务组步骤时,若发起方接受到失败的状态后将会把该次事务识别为待补偿事务,然后发起方将该次事务数据异步通知给 TxManager。TxManager 接受到补偿事务以后先通知补偿回调地址,然后再根据是否开启自动补偿事务状态来补偿或保存该次切面事务数据。

九、 LCN 分布式事务框架应用

官方文档 https:// txlcn.org/zh-cn/docs/pr eface.html

1. LCN 应用案例设计

  1. 需求
    创建三个服务分别为:springcloud-portal、springcloud-order、springcloud-inventory。
    在 springcloud-portal 服务中处理创建订单的请求,然后分别请求 springcloud-order 以及 springcloud-inventory服务。在springcloud-order中插入一条订单数据,在springcloud-inventory 中对商品的数量做更新。
  2. 使用技术
    数据库:Mysql
    开发平台:SpringCloud+SpringBoot+MyBatis
  3. 数据库设计
    创建两个数据库分别为: sxt_orders, sxt_inventory。
    springcloud-order 操作 sxt_orders 库, springclooud-inventory 操作 sxt_inventory 库
  4. 1sxt_orders 库中的表设计
    在 sxt_orders 库中包含一个 tb_orders 表。 表结构如下:
CREATE TABLE `tb_orders` (
`orderid` int(11) NOT NULL AUTO_INCREMENT,
`itemid` int(11) DEFAULT NULL,
`price` int(11) DEFAULT NULL,
PRIMARY KEY (`orderid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

sxt_inventory库中表设计 在 sxt_inventory 库中包含一个 tb_inventory 表。 表结构如下:

CREATE TABLE `tb_inventory` (
`inventoryid` int(11) NOT NULL AUTO_INCREMENT,
`itemid` int(11) DEFAULT NULL,
`itemnum` int(11) DEFAULT NULL,
PRIMARY KEY (`inventoryid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2. 创建服务

  1. 创建项目

springcloud-portal pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.7.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>cn.xyl.lcn</groupId>
	<artifactId>SpringCloud-portal</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>SpringCloud-portal</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
		<!-- 解决springboot快速创建的项目pom文件第一行报错问题 -->
		<maven-jar-plugin.version>2.6</maven-jar-plugin.version>
	</properties>

	<dependencies>
		<!-- mysql数据库驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.0</version>
		</dependency>
		<!-- druid数据库连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.0.9</version>
		</dependency>
		<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>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<!-- 使用Apache HttpClient替换Feign原生httpURLConnection -->
		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-httpclient</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>cn.xyl.lcn</groupId>
			<artifactId>SpringCloud-Inventory-Server</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
		<dependency>
			<groupId>cn.xyl.lcn</groupId>
			<artifactId>SpringCloud-Order-Server</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

application.yml

server:
  port: 8080
  compression:
    enabled: true   #是否启用压缩  
    mime-types:    #配置压缩支持的 MIME TYPE
    - application/json,application/ xml,text/html,text/xml,text/plain
spring:
  application:
    name: SpringCloud-Portal
  #数据源
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/tx-manager
    username: root
    password: ********    
    type: com.alibaba.druid.pool.DruidDataSource

eureka:
  client:
    serviceUrl:
      defaultZone: http://admin:admin@localhost:8761/eureka
      
feign:
  httpclient:
    enabled: true     
    
tx-lcn:
    client:
        manager-address: 127.0.0.1:9999

springcloud-order pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>cn.xyl.lcn</groupId>
	<artifactId>SpringCloud-Order</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>SpringCloud-Order</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
		<!-- 解决springboot快速创建的项目pom文件第一行报错问题 -->
		<maven-jar-plugin.version>2.6</maven-jar-plugin.version>
	</properties>

	<dependencies>
	<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>
		<!-- mysql数据库驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>

		<!-- druid数据库连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.0.9</version>
		</dependency>
		<dependency>
			<groupId>cn.xyl.lcn</groupId>
			<artifactId>SpringCloud-Order-Server</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.0</version>
		</dependency>
	<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<!-- 使用Apache HttpClient替换Feign原生httpURLConnection -->
		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-httpclient</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

application.yml

server:
  port: 8081

spring:
  application:
    name: SpringCloud-Order
    #数据源
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/orders
    username: root
    password: ********    
    type: com.alibaba.druid.pool.DruidDataSource

#在启动器上添加注解:@MapperScan("cn.xyl.mapper")//设置mybatis的映射文件的包
mybatis:
       type-aliases-package: cn.xyl.pojo


eureka:
  client:
    serviceUrl:
      defaultZone: http://admin:admin@localhost:8761/eureka
      
feign:
  httpclient:
    enabled: true   

tx-lcn:
    client:
        tmRpcTimeout: 20000
        manager-address: localhost:8081
        
        

springcloud-inventory pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.7.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>cn.xyl.lcn</groupId>
	<artifactId>SpringCloud-Inventory</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>SpringCloud-Inventory</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
		<!-- 解决springboot快速创建的项目pom文件第一行报错问题 -->
		<maven-jar-plugin.version>2.6</maven-jar-plugin.version>
	</properties>

	<dependencies>
		<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>
		<!-- mysql数据库驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<!-- druid数据库连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.0.9</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>cn.xyl.lcn</groupId>
			<artifactId>SpringCloud-Inventory-Server</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.0</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<!-- 使用Apache HttpClient替换Feign原生httpURLConnection -->
		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-httpclient</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

application.yml

server:
  port: 8082

spring:
  application:
    name: SpringCloud-Inventory
  #数据源
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/inventory
    username: root
    password: *********    
    type: com.alibaba.druid.pool.DruidDataSource

#在启动器上添加注解:@MapperScan("cn.xyl.mapper")//设置mybatis的映射文件的包
mybatis:
       type-aliases-package: cn.xyl.pojo

eureka:
  client:
    serviceUrl:
      defaultZone: http://admin:admin@localhost:8761/eureka
feign:
  httpclient:
    enabled: true        
    connection-timeout: 30000  
    
tx-lcn:
    client:
        manager-address: localhost:8070

3.大致实现步骤

实现业务
实现添加订单业务
添加 mapper
添加业务层
添加 controller
添加启动类
在业务接口项目中添加 pojo
实现修改库存商品数量业务
添加 mapper
添加业务层
添加 controller
添加启动类
在业务接口项目中添加 pojo
实现 portal 中的创建订单业务
添加实现服务接口的接口
添加 portalService 接口
添加 controller
添加启动类

4. 测试服务是否正常

启动服务,调用portal的add方法,查看数据库数据是否插入和修改

5. 创建CLN的事务管理器

https:// blog.csdn.net/weixin_42 629535/article/details/88646965

1. 下载源码包 https://codeload.github.com/codingapi/tx-lcn/zip/5.0.2.RELEASE

2. 在数据库中添加库和表

/*
 Navicat Premium Data Transfer
 Source Server         : local
 Source Server Type    : MySQL
 Source Server Version : 100309
 Source Host           : localhost:3306
 Source Schema         : tx-manager
 Target Server Type    : MySQL
 Target Server Version : 100309
 File Encoding         : 65001
 Date: 29/12/2018 18:35:59
*/
CREATE DATABASE IF NOT EXISTS  `tx-manager` DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
USE `tx-manager`;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_tx_exception
-- ----------------------------
DROP TABLE IF EXISTS `t_tx_exception`;
CREATE TABLE `t_tx_exception`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `group_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `unit_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `mod_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `transaction_state` tinyint(4) NULL DEFAULT NULL,
  `registrar` tinyint(4) NULL DEFAULT NULL,
  `ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 待处理 1已处理',
  `remark` varchar(10240) NULL DEFAULT NULL COMMENT '备注',
  `create_time` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 967 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
  1. 解压并将整个项目导入到eclips
  2. e
    问题: 源码中大量的log无法通过@Slf4j注解找到log变量,而且大量的set/get方法注解也都失效了。
    解决:
    1. 下载lombok.jar https://projectlombok.org/download.html
    2. 将 lombok.jar 复制到 myeclipse.ini 所在的文件夹目录下
    3. 双击lomok.jar开始安装
    4. 重启 myeclipse
  3. 修改配置文件
spring.application.name=TransactionManager
server.port=7970
#数据库配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=xxxx
#方言配置
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update
#Redis配置
spring.redis.host=192.168.153.136
spring.redis.port=6379
spring.redis.password=

官方示例

spring.application.name=TransactionManager
server.port=7970

# JDBC 数据库配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456

# 数据库方言
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

# 第一次运行可以设置为: create, 为TM创建持久化数据库表
spring.jpa.hibernate.ddl-auto=validate

# TM监听IP. 默认为 127.0.0.1
tx-lcn.manager.host=127.0.0.1

# TM监听Socket端口. 默认为 ${server.port} - 100
tx-lcn.manager.port=8070

# 心跳检测时间(ms). 默认为 300000
tx-lcn.manager.heart-time=300000

# 分布式事务执行总时间(ms). 默认为36000
tx-lcn.manager.dtx-time=8000

# 参数延迟删除时间单位ms  默认为dtx-time值
tx-lcn.message.netty.attr-delay-time=${tx-lcn.manager.dtx-time}

# 事务处理并发等级. 默认为机器逻辑核心数5倍
tx-lcn.manager.concurrent-level=160

# TM后台登陆密码,默认值为codingapi
tx-lcn.manager.admin-key=codingapi

# 分布式事务锁超时时间 默认为-1,当-1时会用tx-lcn.manager.dtx-time的时间
tx-lcn.manager.dtx-lock-time=${tx-lcn.manager.dtx-time}

# 雪花算法的sequence位长度,默认为12位.
tx-lcn.manager.seq-len=12

# 异常回调开关。开启时请制定ex-url
tx-lcn.manager.ex-url-enabled=false

# 事务异常通知(任何http协议地址。未指定协议时,为TM提供内置功能接口)。默认是邮件通知
tx-lcn.manager.ex-url=/provider/email-to/***@**.com
注意(NOTE)
(1) TxManager所有配置均有默认配置,请按需覆盖默认配置。
(2) 特别注意 TxManager进程会监听两个端口号,一个为TxManager端口,另一个是事务消息端口。TxClient默认连接事务消息端口是8070, 所以,为保证TX-LCN基于默认配置运行良好,请设置TxManager端口号为8069 或者指定事务消息端口为8070
(3) 分布式事务执行总时间 a 与 TxClient通讯最大等待时间 b、TxManager通讯最大等待时间 c、微服务间通讯时间 d、微服务调用链长度 e 几个时间存在着依赖关系。 a >= 2c + (b + c + d) * (e - 1), 特别地,b、c、d 一致时,a >= (3e-1)b。你也可以在此理论上适当在减小a的值,发生异常时能更快得到自动补偿,即 a >= (3e-1)b - Δ(原因)。 最后,调用链小于等于3时,将基于默认配置运行良好
(4) 若用tx-lcn.manager.ex-url=/provider/email-to/xxx@xx.xxx 这个配置,配置管理员邮箱信息(如QQ邮箱):
spring.mail.host=smtp.qq.com
spring.mail.port=587
spring.mail.username=xxxxx@**.com
  1. 启动项目 访问http://localhosy:7970 初始密码为codingapi 在配置文件中设置 tx-lcn.manager.admin-key=codingapi 可修改密码

6. 在服务中添加事务客户端

添加依赖

<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>

在启动类上添加注解

@SpringBootApplication
@EnableEurekaClient
@MapperScan("cn.xyl.mapper")
@EnableDistributedTransaction
public class SpringCloudInventoryApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringCloudInventoryApplication.class, args);
    }
}

在进行事务处理的方法上添加注解

@Transactional
@LcnTransaction
public void updateInventory(TbInventory inventory) {
    inventoryMapper.updateByPrimaryKeySelective(inventory);
}

注意 假设一个服务中,没有调用数据库,但是一个分布事务处理的开始,tm需要DataSource,需要在配置文件配置DataSource。才能正常启动,可能需要日志记录。

修改tm的事务地址。 在tm配置文件中也要在配置文件中修改地址。 在client中添加

tx-lcn:
    client:
        manager-address: 127.0.0.1:9999

十、 项目过程中遇到的异常

  1. 创建order项目和inventory项目时,发现所有请求都404.检查发现控制器没有被实例化,最终检查得出在启动类上注解写错了,MapperScan写成了@ComponentScan,mapperScan是用来扫描mybatis的接口,@ComponentScan用来扫描bean,在@ComponentScan中写了包名,只会扫描该包,导致其他类不能实例化。
  2. 导入lcn项目时项目报错
    通过添加lombok.jar 解决
  3. 添加lombok.jar 是导入错误,编译时提示Java Bulider出错,项目右键,设置,builder去掉Java Builder后不报错,但项目不能正常启动,重新安装lombok.jar 解决
  4. 因为tm没有在eureka注册,尝试添加依赖和注解,实现注册中心注册,失败。
    因为5.0.2版的tx-lcn不需要走注册中心,程序间独立的通过socket与TM建立联系。
  5. 当feign 调用时 feign 接口有fallbackFactory =xxx.class,那么调用方就不会回滚 解决: 在Fallback方法里面加上这个 DTXUserControls.rollbackGroup(TracingContext.tracing().groupId());
    来自 https://github.com/codingapi/tx-lcn/issues/404
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值