第一章 分布式事务介绍
一、什么是分布式事务
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
举个栗子:
电商系统中的订单系统与库存系统
图中包含了库存和订单两个独立的微服务,每个微服务维护了自己的数据库。
在交易系统的业务逻辑中,一个商品在下单之前需要先调用库存服务,进行扣除库存,再调用订单服务,创建订单记录。
正常情况下,两个数据库各自更新成功,两边数据维持着一致性。如果在非正常情况下,有可能库存的扣减完成了,随后的订单记录却因为某些原因插入失败。或者是订单创建成功了,但是库存扣除商品的数据量失败了,这个时候,两边数据就失去了应有的一致性。
这时候我们需要保证分布式事务的一致性,单数据源的用单机事务来保证。多数据源就需要依赖分布式事务来处理。
二、XA 的两阶段提交方案
-
什么是XA 协议
XA 协议由Oracle Tuxedo 首先提出的,并交给X/Open 组织,作为资源管理器(数据库)与事务管理器的接口标准。目前,Oracle、Informix、DB2 和Sybase 等各大数据库厂家都提供对XA 的支持。XA 协议采用两阶段提交方式来管理分布式事务。XA 接口提供资源管理器与事务管理器之间进行通信的标准接口。
XA 就是X/Open DTP 定义的交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。XA 接口函数由数据库厂商提供。
X/Open 组织(即现在的Open Group)定义了分布式事务处理模型。X/Open DTP 模型(1994)包括:
应用程序(AP)、
事务管理器(TM)、
资源管理器(RM)、
通信资源管理器(CRM)。一般常见的事务管理器(TM)是交易中间件,常见的资源管理器(RM)是数据库,常见的通信资源管理器(CRM)是消息中间件。
-
XA 协议的一阶段提交
一阶段提交协议相对简单。优点也很直观,它不用再与其他的对象交互,节省了判断步骤和时间,所以在性能上是在阶段提交协议中最好的。
但缺点也很明显:数据库确认执行事务的时间较长,出问题的可能性就随之增大。如果有多个数据源,一阶段提交协议无法协调他们之间的关系。如果在程序中开启了事务,那么在应用程序发出提交/回滚请求后,数据库执行操作,而后将成功/失败返回给应用程序,程序继续执行。
-
XA 协议的二阶段提交
二阶段三角色: 在一阶段协议的基础上,有了二阶段协议,二阶段协议的好处是添加了一个管理者角色。
很明显,二阶段协议通过将两层变为三层,增加了中间的管理者角色,从而协调多个数据源之间的关系,二阶段提交协议分为两个阶段。
应用程序调用了事务管理器的提交方法,此后第一阶段分为两个步骤:
事务管理器通知参与该事务的各个资源管理器,通知他们开始准备事务。
资源管理器接收到消息后开始准备阶段,写好事务日志并执行事务,但不提交,
然后将是否就绪的消息返回给事务管理器(此时已经将事务的大部分事情做完,以后的内容耗时极小)。
第二阶段也分为两个步骤:
a. 事务管理器在接受各个消息后,开始分析,如果有任意其一失败,则发送回滚命令,否则发送提交命令。
b. 各个资源管理器接收到命令后,执行(耗时很少),并将提交消息返回给事务管理器。
事务管理器接受消息后,事务结束,应用程序继续执行。为什么要分两步执行?
一是因为分两步,就有了事务管理器统一管理的机会;
二是尽可能晚地提交事务,让事务在提交前尽可能地完成所有能完成的工作,
这样,最后的提交阶段将是耗时极短,耗时极短意味着操作失败的可能性也就降低。
同时,二阶段提交协议为了保证事务的一致性,不管是事务管理器还是各个资源管理器,
每执行一步操作,都会记录日志,为出现故障后的恢复准备依据。缺点:
1 二阶段提交协议的存在的弊端是阻塞,因为事务管理器要收集各个资源管理器的响应消息,
如果其中一个或多个一直不返回消息,则事务管理器一直等待,应用程序也被阻塞,甚至可能永久阻塞。
2 两阶段提交理论的一个广泛工业应用是XA 协议。目前几乎所有收费的商业数据库都支持XA 协议。
XA 协议已在业界成熟运行数十年,但目前它在互联网海量流量的应用场景中,吞吐量这个瓶颈变得十分致命,因此很少被用到。
三、TCC 解决方案
-
TCC 介绍
TCC 是由支付宝架构师提供的一种柔性解决分布式事务解决方案,主要包括三个步骤
Try:预留业务资源/数据效验
Confirm:确认执行业务操作
Cancel:取消执行业务操作
幂等性: 在进行事务提交时 ,将多次操作合并成为一次并提交 -
TCC 原理
TCC 方案在电商、金融领域落地较多。TCC 方案其实是两阶段提交的一种改进。
其将整个业务逻辑的每个分支显式的分成了Try、Confirm、Cancel 三个操作.
Try 部分完成业务的准备工作,confirm 部分完成业务的提交,cancel 部分完成事务的回滚。
基本原理如下图所示。事务开始时,业务应用会向事务协调器注册启动事务。
之后业务应用会调用所有服务的try 接口,完成一阶段准备。
之后事务协调器会根据try 接口返回情况,决定调用confirm接口或者cancel 接口。
如果接口调用失败,会进行重试。微服务倡导服务的轻量化、易部署,
而TCC 方案中很多事务的处理逻辑需要应用自己编码实现,复杂且开发量大 -
TCC 的关键流程如下图(以创建订单和扣减库存为例子)
-
TCC 优缺点
优点
让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。缺点
对应用的侵入性强。业务逻辑的每个分支都需要实现try、confirm、cancel三个操作,
应用侵入性较强,改造成本高。实现难度较大。
需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。
为了满足一致性的要求,confirm 和cancel 接口必须实现幂等。
四、分布式事务中间件解决方案
分布式事务中间件其本身并不创建事务,而是基于对本地事务的协调从而达到事务一致性的效果。
典型代表有:阿里的GTS(https://www.aliyun.com/aliware/txc)、开源应用LCN。
其实现原理如下:
第二章 LCN分布式事务处理框架介绍
一、什么是LCN 框架
-
LCN 框架的由来
在设计框架之初的1.0 ~ 2.0 的版本时,框架设计的步骤是如下的,各取其首字母得来的LCN 命名。
LCN: 锁定事务单元(lock)、确认事务模块状态(confirm)、通知事务(notify)
它的宗旨 : LCN 并不生产事务,LCN 只是本地事务的协调工 -
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 框架原理及执行步骤
-
LCN 的执行原理
在上图中,微服务A,微服务B,TxManager 事务协调器,都需要去Eureka 中注册服务。
Eureka 是用于TxManager 与其他服务之间的相互服务发现。
redis 是用于存放我们事务组的信息以及补偿的信息。
然后微服务A 与微服务B 他们都需要去配置上我们TxClient 的包架构(代码的包架构);
来支持我们的LCN 框架,以及他们的数据库。 -
LCN 执行步骤
创建事务组
事务组是指的我们在整个事务过程中把各个节点(微服务)单元的事务信息存储在一个固定单元里。
但这个信息并不是代表是事务信息,而是只是作为一个模块的标示信息。
创建事务组是指在事务发起方开始执行业务代码之前先调用TxManager 创建事务组对象,
然后拿到事务标示GroupId 的过程。添加事务组
添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息添加通知给TxManager 的操作。关闭事务组
是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager 的动作。
当执行完关闭事务组的方法以后,TxManager 将根据事务组信息来通知相应的参与模块提交或回滚事务。 -
业务执行流程图
三、什么是LCN 的事务协调机制
如图:
假设服务已经执行到关闭事务组的过程,那么接下来作为一个模块执行通知给TxManager,
然后告诉他本次事务已经完成。那么如图中Txmanager 下一个动作就是通过事务组的id,
获取到本次事务组的事务信息;然后查看一下对应有那几个模块参与,
如果是有A/B/C 三个模块;那么对应的对三个模块做通知、提交、回滚。
那么提交的时候是提交给谁呢?
是提交给了我们的TxClient 模块。然后TxCliient 模块下有一个连接池,就是框架自定义的一个连接池(如图DB 连接池);这个连接池其实就是在没有通知事务之前一直占有着这次事务的连接资源,就是没有释放。
但是他在切面里面执行了close 方法。在执行close的时候。
如果需要(TxManager)分布式事务框架的连接。他被叫做“假关闭”,也就是没有关闭,
只是在执行了一次关闭方法。实际的资源是没有释放的。这个资源是掌握在LCN 的连接池里的。
当TxManager 通知提交或事务回滚的时候呢?
TxManager 会通知我们的TxClient 端。然后TxClient 会去执行相应的提交或回滚。
提交或回滚之后再去关闭连接。这就是LCN 的事务协调机制。
说白了就是代理DataSource 的机制;相当于是拦截了一下连接池,控制了连接池的事务提交。
四、LCN 的事务补偿机制
-
什么是补偿事务机制?
LCN 的补偿事务原理是模拟上次失败事务的请求,然后传递给TxClient 模块然后再次执行该次请求事务。
简单的说:lcn 事务补偿是指在服务挂机和网络抖动情况下txManager 无法通知事务单元时。(通知不到也就两种原因服务挂了和网络出问题)在这种情况下TxManager 会做一个标示;然后返回给发起方。
告诉他本次事务有存在没有通知到的情况。那么如果是接收到这个信息之后,发起方就会做一个标示,
标示本次事务是需要补偿事务的。这就是事务补偿机制。 -
为什么需要事务补偿?
事务补偿是指在执行某个业务方法时,本应该执行成功的操作却因为服务器挂机或者网络抖动等问题
导致事务没有正常提交,此种场景就需要通过补偿来完成事务,从而达到事务的一致性。 -
补偿机制的触发条件?
当执行关闭事务组步骤时,若发起方接受到失败的状态后将会把该次事务识别为待补偿事务,
然后发起方将该次事务数据异步通知给TxManager。TxManager 接受到补偿事务以后
先通知补偿回调地址,然后再根据是否开启自动补偿事务状态来补偿或保存该次切面事务数据。
第三章 LCN分布式事务框架应用
一、LCN 分布式事务框架应用
-
需求
创建两个服务接口项目 springcloud-order-service,springcloud-inventory-servicec
创建三个服务分别为:springcloud-portal、springcloud-order、springcloud-inventory。
在springcloud-portal 服务中处理创建订单的请求,
然后分别请求springcloud-order 以及springcloud-inventory 服务。令其调用对应的接口项目
在springcloud-order 中插入一条订单数据,在springcloud-inventory中对商品的数量做更新。 -
使用技术
数据库:Mysql
开发平台:SpringCloud+SpringBoot+MyBatis -
数据库设计
创建两个数据库分别为:chy_orders,chy_inventory。
springcloud-order 操作 chy_orders 库,springclooud-inventory 操作 chy_inventory 库a. 在 chy_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;
b.在 chy_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;
环境搭建
项目结构
创建入口项目 springcloud-portal
-
通过官网快速构建项目 web启动器,Eureka客户端,feign
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR3</spring-cloud.version> <maven-jar-plugin.version>2.6</maven-jar-plugin.version> </properties> <dependencies> <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> </dependencies>
-
修改配置文件 application.yml(效果同application.properties)
spring: application: name: springcloud-portal server: port: 8080 eureka: client: serviceUrl: defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
-
启动类
@SpringBootApplication @EnableEurekaClient @EnableFeignClients public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
创建接口项目springcloud-order-service ,springcloud-inventory-service
-
通过官网快速构建,添加web启动器,pom文件除项目名外一样
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.8.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR3</spring-cloud.version> <maven-jar-plugin.version>2.6</maven-jar-plugin.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
创建接口实现项目(类似生产者)springcloud-order, springcloud-inventory
-
快速构建SpringBoot项目,修改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 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.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>ah.szxy.lcn</groupId> <artifactId>springcloud-order</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springcloud-portal</name> <description>SpringLCN Project</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR3</spring-cloud.version> <maven-jar-plugin.version>2.6</maven-jar-plugin.version> </properties> <dependencies> <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-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Mybatis启动器 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</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>an.szxy.lcn</groupId> <artifactId>springcloud-order-service</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>
注意:
1.添加web启动器,Eureka客户端,声明式调用feign ,mybatis启动器,mysql数据库驱动,数据库连接池
2.添加相关依赖接口项目的坐标,以便能使使用其抽象方法和实体类 -
修改全局配置文件 application.yml
修改应用名,数据库链接参数,mybatis的别名配置,Eureka注册中心/集群的配置spring: #配置应用名,数据库连接参数 application: name: springcloud-order datasource: driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/chy_orders?useUnicode=true&characterEncoding=gbk&useJDBCCompliantTimezoneShift=true&serverTimezone=UTC username: root password: root type: com.alibaba.druid.pool.DruidDataSource server: #配置端口号 port: 8081 mybatis: #指定POJO扫描包来让mybatis自动扫描到自定义POJO type-aliases-package: ah.szxy.pojo eureka: #配置Eureka服务注册中心地址 client: serviceUrl: defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
-
启动类
完全一样,只要包名一致的话@SpringBootApplication @EnableEurekaClient @MapperScan("ah.szxy.mapper") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
实现业务
实现添加订单业务
-
接口项目中添加接口类
注意: 使用POST方法提交整合对象需要使用RequestBody 注解public interface OrderServiceAPI { @RequestMapping(value="addOrder",method=RequestMethod.POST) void addOrder(@RequestBody Orders orders); //post传递一个对象,使用@RequestBody }
-
在业务接口项目中添加pojo
public class Orders implements Serializable{ private Integer orderid; private Integer itemid; private Integer price; public Integer getOrderid() { return orderid; } public void setOrderid(Integer orderid) { this.orderid = orderid; } public Integer getItemid() { return itemid; } public void setItemid(Integer itemid) { this.itemid = itemid; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } }
-
添加mapper(业务层接口项目)
OrdersMapper .javapublic interface OrdersMapper { void insertOrder(Orders orders); }
OrdersMapper .xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="ah.szxy.mapper.OrdersMapper"> <insert id="insertOrder"> insert into tb_orders(itemid,price) values(#{itemid},#{price}) </insert> </mapper>
-
添加业务层(手动开启事务注解)
public interface OrdersService { void inserOrders(Orders orders); } @Service public class OrdersServiceImpl implements OrdersService { @Autowired private OrdersMapper ordersMapper; @Override @Transactional public void inserOrders(Orders orders) { this.ordersMapper.insertOrder(orders); } }
-
添加controller
思考为什么要使用RestController注解?@RestController public class OrdersController implements OrderServiceAPI{ @Autowired private OrdersService ordersService; @Override public void addOrder(@RequestBody Orders orders) { this.ordersService.inserOrders(orders); } }
实现修改库存商品数量业务
-
接口项目添加接口
public interface InventoryServiceAPI { @RequestMapping(value="updateInventory",method=RequestMethod.POST) void updateInventory(@RequestBody Inventory inventory); }
-
在业务接口项目中添加pojo
public class Inventory implements Serializable{ private Integer inventoryid; private Integer itemid; private Integer itemnum; public Integer getInventoryid() { return inventoryid; } public void setInventoryid(Integer inventoryid) { this.inventoryid = inventoryid; } public Integer getItemid() { return itemid; } public void setItemid(Integer itemid) { this.itemid = itemid; } public Integer getItemnum() { return itemnum; } public void setItemnum(Integer itemnum) { this.itemnum = itemnum; } }
-
添加mapper
public interface InventoryMapper { void updateInventory(Inventory inventory); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="ah.szxy.mapper.InventoryMapper"> <update id="updateInventory"> update tb_inventory set itemnum=#{itemnum} where itemid=#{itemid} </update> </mapper>
-
添加业务层 (手动添加事务注解)
public interface InventoryService { void updateInventory(Inventory inventory); } @Service public class InventoryServiceImpl implements InventoryService { @Autowired private InventoryMapper inventoryMapper; @Override @Transactional public void updateInventory(Inventory inventory) { this.inventoryMapper.updateInventory(inventory); } }
-
添加controller
@RestController public class InventoryController implements InventoryServiceAPI { @Autowired private InventoryService inventoryService; @Override public void updateInventory(@RequestBody Inventory inventory) { this.inventoryService.updateInventory(inventory); } }
实现portal 中的创建订单业务
-
添加实现服务接口的接口
注意:继承的是接口项目的接口类, 但Feign调用的确实他们的业务实现项目package ah.szxy.feign.service; import org.springframework.cloud.openfeign.FeignClient; import ah.szxy.service.InventoryServiceAPI; @FeignClient("springclooud-inventory") public interface Portal_Inventory_service extends InventoryServiceAPI { } package ah.szxy.feign.service; import org.springframework.cloud.openfeign.FeignClient; import ah.szxy.service.OrderServiceAPI; @FeignClient("springcloud-order") public interface Portal_Orders_Service extends OrderServiceAPI{ }
-
添加portalService 接口
public interface PortalService { void addOrder(); } @Service public class PortalServiceImpl implements PortalService{ @Autowired private Portal_Orders_Service orderService; @Autowired private Portal_Inventory_service inventoryService; @Override public void addOrder() { Orders orders = new Orders(); orders.setItemid(100); orders.setPrice(666); Inventory inventory = new Inventory(); inventory.setItemid(100); inventory.setItemnum(6); this.orderService.addOrder(orders); this.inventoryService.updateInventory(inventory); } }
-
添加controller
@RestController public class PortalController { @Autowired private PortalService portalService; @RequestMapping("add") public Map<String,String>addOrder(){ this.portalService.addOrder(); Map<String, String>map=new HashMap<String, String>(); map.put("msg", "ok"); return map; } }
-
添加启动类
使用了feign的注解,所以需要添加@EnableFeignClients@SpringBootApplication @EnableEurekaClient @EnableFeignClients public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
测试
-
在数据库中插入一条出库数据
2.访问入口项目的controller,可以看到访问成功
3.查看数据库,可以看到生成了订单的数据
而且,库存的订单数量由10改为了6 ,由结果来看我们似乎模拟成功了
小结:
如果我们使一个事务的操作出现问题时,例如将添加订单的sql语句故意写错时,
虽然我们手动添加了@Transaction注解,但是我们重新运行项目时,
可以看到订单虽然没有被添加但是库存却可以被修改
因此我们需要明白的是,这个项目只是模拟了没有使用LCN ,即没有使用分布式事务管理的情况,
下面我们将会逐渐引入LCN来让我们理解他的强大之处!!!
使用LCN 实现分布式事务处理_服务端
-
下载LCN 事务协调器(使用的版本为5.0.2)
https://codeload.github.com/codingapi/tx-lcn/zip/5.0.2.RELEASE
-
使用eclipse以Maven的形式导入该源码
-
解压并将整个项目 tx-lcn-5.02.RELEASE 导入到eclipse
问题:
导入后, 源码中大量的log无法通过@Slf4j注解找到log变量,
而且大量的set/get方法注解也都失效了。
解决:a.下载lombok.jar : https://projectlombok.org/download.html
b.将 lombok.jar 复制到 myeclipse.ini 所在的文件夹目录下
c.双击lomok.jar开始安装
d.重启 myeclipse注意:如果是安装在C盘,则在安装的时候提示需要管理员权限 ,如下图
需要我们根据提示,使用管理员权限运行CMD窗口,
然后跳转到lomock所在目录,运行该jar即可
出现下面这个类似红辣椒的标志代表安装成功,重启myeclipse 即可
-
找到tx-lcn下的 txlcn-tm,找到resource下的 .sql文件,在数据库中运行
注意: .sql文件中的代码如下 , 但首先应该创建 tx-manager 这个数据库/* 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;
-
修改配置文件 application.properties
pring.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=root spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect spring.jpa.hibernate.ddl-auto=update #Redis的配置 spring.redis.host=192.168.179.131 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
-
启动项目
访问http://localhost:7970 ,初始密码为codingapi
在配置文件中设置tx-lcn.manager.admin-key=admin
可修改密码为 admin效果如下
使用LCN 实现分布式事务处理_客户端
-
添加相关坐标
<!-- 使用事务管理器TM所需要添加的坐标 --> <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>
-
在需要事务处理的方法上加入@LcnTransaction注解
@Override @LcnTransaction public void addOrder() { Orders orders = new Orders(); orders.setItemid(100); orders.setPrice(666); Inventory inventory = new Inventory(); inventory.setItemid(100); inventory.setItemnum(6); this.orderService.addOrder(orders); this.inventoryService.updateInventory(inventory); }
-
在启动类中添加@EnableDistributedTransaction注解
@SpringBootApplication @EnableEurekaClient @EnableFeignClients @EnableDistributedTransaction //使用lcn时,需要添加 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
注意:
在使用lcn时,需要我们配置数据库连接池,也就需要我们配置数据库的链接参数,
在配置这些东西同时也就需要我们添加mybatis的启动器,数据库连接池以及mysql-connector的坐标
在本项目中,创建三个服务分别为:springcloud-portal、springcloud-order、springcloud-inventory都需要进行这五步操作
a.mybatis的启动器,数据库连接池以及mysql-connector的坐标
<!-- Mybatis启动器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</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>
b.数据库的链接参数
spring:
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/chy_inventory?useUnicode=true&characterEncoding=gbk&useJDBCCompliantTimezoneShift=true&serverTimezone=UTC
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
测试效果
部署完毕后,启动所有项目(包括 lcn-tm项目),
首先访问 txlcn-tm(LCN服务端项目),发现下面三个项目都在运行
这里是重中之重的测试
紧接上次测试 , 上次将添加订单的sql语句故意写错时,
然后运行portal入口项目时, 发现虽然运行报错, 订单表没有插入数据, 但是库存仍可被修改,
现在数据库中,将订单表中的数据删除 ,将库存数据设置为初始值后,
再次运行项目后 ,我们可以看到 ,项目运行同样出错,
然是不同的是 , 订单表没有插入数据 ,同时库存表也是初始值 ,
同时证明了LCN对分布式事务的协调是成功的
附1: 错误页面
附2: 订单表情况
附3 : 库存表情况(仍为初始值)
com.codingapi.txlcn.common.exception.TxManagerException:
attempts to join the non-existent transaction group. [cb2f2e3423878daebdd28d0a684e68c6@springclooud-inventory]
在服务网关中配置LCN
当前LCN-5.0.2版本使用的rpc协议, 所以配置在配置时不收网关的影响
具体请看项目案例: 百战商城项目案例