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

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

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

这时候我们需要保证分布式事务的一致性,单数据源的用单机事务来保证。多数据源就需要依赖分布式事务来处理。
二、XA 的两阶段提交方案
- 什么是XA 协议
XA 协议由Oracle Tuxedo 首先提出的,并交给X/Open 组织,作为资源管理器(数据库)与事务管理器的接口标准。目前,Oracle、Informix、DB2 和Sybase 等各大数据库厂家都提供对XA 的支持。XA 协议采用两阶段提交方式来管理分布式事务。XA 接口提供资源管理器与事务管理器之间进行通信的标准接口。
XA 就是X/Open DTP 定义的交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。XA 接口函数由数据库厂商提供。
一般常见的事务管理器(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 部分完成事务的回滚。
基本原理如下图所示。
- TCC 的关键流程如下图(以创建订单和扣减库存为例子)

- TCC 优缺点
优点
让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。缺点
四、分布式事务中间件解决方案
分布式事务中间件其本身并不创建事务,而是基于对本地事务的协调从而达到事务一致性的效果。
典型代表有:阿里的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 的执行原理
- LCN 执行步骤
创建事务组
事务组是指的我们在整个事务过程中把各个节点(微服务)单元的事务信息存储在一个固定单元里。
但这个信息并不是代表是事务信息,而是只是作为一个模块的标示信息。
创建事务组是指在事务发起方开始执行业务代码之前先调用TxManager 创建事务组对象,
然后拿到事务标示GroupId 的过程。添加事务组
添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息添加通知给TxManager 的操作。关闭事务组
是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager 的动作。
当执行完关闭事务组的方法以后,TxManager 将根据事务组信息来通知相应的参与模块提交或回滚事务。 - 业务执行流程图
三、什么是LCN 的事务协调机制

那么提交的时候是提交给谁呢?
是提交给了我们的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 ,由结果来看我们似乎模拟成功了
小结:
使用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方法注解也都失效了。
解决:注意:如果是安装在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 : Source Host : localhost:3306 Source Schema : tx-manager Target Server Type : MySQL Target Server Version : 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= # 数据库方言 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). 默认为 tx-lcn.manager.heart-time= # 分布式事务执行总时间(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/ 这个配置,配置管理员邮箱信息(如邮箱): spring.mail.host=smtp..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
测试效果


这里是重中之重的测试
紧接上次测试 , 上次将添加订单的sql语句故意写错时,
然后运行portal入口项目时, 发现虽然运行报错, 订单表没有插入数据, 但是库存仍可被修改,
现在数据库中,将订单表中的数据删除 ,将库存数据设置为初始值后,
再次运行项目后 ,我们可以看到 ,项目运行同样出错,
然是不同的是 , 订单表没有插入数据 ,同时库存表也是初始值 ,
同时证明了LCN对分布式事务的协调是成功的
附1: 错误页面

注意 :在将Mapper.xml中数据库更新语句书写正确后 , 然后访问出现如下错误 或ReadTime Out异常,重新刷新页面即可解决
com.codingapi.txlcn.common.exception.TxManagerException: attempts to join the non-existent transaction group. [cb2f2edaebdd28d0a684e68c6@springclooud-inventory]

在服务网关中配置LCN
当前LCN-5.0.2版本使用的rpc协议, 所以配置在配置时不收网关的影响
具体请看项目案例: 百战商城项目案例
如果想继续学习LCN请看这个大佬的博客
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/232645.html原文链接:https://javaforall.net
