TX-LCN分布式事务框架 - 图1
image.png

一、分布式事务

1、分布式事务

在分布式系统中,事务参与者在不同的分布式节点上(节点可以操作同一个数据源也可以操作的是不同的数据源),这些情况产生的事务都叫做分布式事务。
注意:参与者不在同一个节点上(项目)上,才能叫做分布式事务。如果所有参与者都在同一个项目中,这时叫做本地事务。

2、什么时候使用分布式事务管理

事务是在数据库中接触到的,一个事务(本地事务)就是一系列SQL语句的集合,只要在执行过程中一条SQL出错就会导致整个事务的失败,回滚到原点。
而分布式系统中存在多个模块完成一次业务。那么就存在一个业务由多模块操作同一个数据源。
image.png
甚至可能存在一个业务横跨多种数据源节点的可能。这些问题都可以由分布式事务解决方案TX-LCN解决。

3、分布式事务最常见解决方案

3.1 二阶段提交(2PC)(XA Transactions)

  1. 2PCTwo-phase commit protocol),二阶段提交是一种强一致性设计,2PC引入了一个事务协调者的角色来协调管理各个参与者,也可以称之为各本地资源的提交和回滚,二阶段分别指的是准备和提交两个阶段。<br />具体流程:<br />准备阶段协调者会给各个参与者发送准备命令,你可以把准备命令理解成除了提交事务之外啥事都做完了。<br />同步等待所有资源的响应之后就进入第二阶段即提交阶段(注意提交阶段不一定是提交事务,也可能是回滚事务)。<br />假如在第一阶段所有参与者都返回准备成功,那么协调者则向所有参与者发送提交事务命令,然后等待所有事务都提交成功之后,返回事务执行成功。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22310212/1637371743285-7a7e84c1-4baf-49bd-9f27-e1ca2159e864.png#clientId=u0484f6ae-5b72-4&from=paste&height=240&id=u48cf9d87&margin=%5Bobject%20Object%5D&name=image.png&originHeight=480&originWidth=761&originalType=binary&ratio=1&size=270844&status=done&style=none&taskId=u89cda719-e6dd-477c-a6a4-37e83be7ba4&width=380.5)<br />假如在第一阶段由一个参与者返回失败,那么协调者就会向所有参与者发送回滚事务的请求,即分布式事务执行失败。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22310212/1637371819910-71ee3268-764f-4ddd-9c97-9ec5c55e19fb.png#clientId=u0484f6ae-5b72-4&from=paste&height=249&id=u1059f3b0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=497&originWidth=763&originalType=binary&ratio=1&size=282709&status=done&style=none&taskId=u12a1a952-92a9-44e4-87c0-8aeacc45782&width=381.5) <br />假如第二阶段提交失败的话,以下有两种情况。<br />第一种是第二阶段执行的是回滚的事务操作,那么答案是不断重试,直到所有参与者都回滚了,不然那些在第一阶段准备成功的参与者会一直阻塞着。<br />第二种是第二阶段执行的是提交事务的操作,那些答案也就是不断重试,因为有可能一些参与者的事务已经提交成功了,这个时候只有一条路,就是不断的重试,直到提交成功,到最后真的不行了只能人工介入处理。<br />2PC是一种尽量保持强一致性的分布式事务,因此它是同步阻塞的,而同步阻塞就导致长久的资源锁定的问题,总体而言效率低,并且存在单点故障的问题,在极端条件下存在数据不一致的风险。<br />2PC适用于数据库层面的分布式事务场景。

3.2 三阶段提交(3PC)(XA Transaction)

3PC的出现是为了解决2PC的一些问题,相比于2PC它在参与者中也引入了超时机制,并且新增了一个阶段使得参与者可以利用这一个阶段统一各自的状态。
3PC包含了三个阶段,分别是准备阶段、预提交阶段和提交阶段。
image.png
3PC相对于2PC做了一定的改进:引入了参与者超时机制,并且增加了预提交阶段使得故障恢复后协调者的决策复杂度降低,但整体的交互过程更长了,性能有所下降,并且还是会存在数据不一致的问题。
所以2PC和3PC都不能保证数据100%一致,因此一般都需要有定时扫描补偿机制。

3.3 Try Confirm Cancel(TCC)

2PC和3PC都是数据库层面的,而TCC是业务层面的分布式事务,就像之前所说分布式事务不仅仅包括数据库的操作,还包括发送短信等,此时可用TCC。
TCC 指的是Try - Confirm - Cancel。

  • Try指的是预留,即资源的预留和锁定,注意是预留。
  • confirm指的是确认操作,这一步其实就是真正的执行了。
  • cancel指的是撤销操作,可以理解为把预留阶段的动作撤销了 。

其实从思想上看和2PC差不多,都是先试探性的执行,如果都可以那就真正的执行,如果不行就回滚。
比如说一个事务要执行A、B、C三个操作,那么先对三个操作执行预留动作。如果都预留成功了那么就执行确认操作,如果有一个预留失败那就执行撤销动作。
流程中,TCC模型还有一个事务管理者的角色,用来记录TCC全局事务状态并提交或者回滚事务。
image.png
难点在于业务上的定义,对于每一个操作你都需要定义三个动作分别对应Try - Confirm - Cancel。
因此TCC对业务的侵入性较大和业务紧耦合,需要根据特定的场景和业务逻辑来设计相对应的操作。
还有一点要注意,撤销和确认操作的执行可能是需要重试,因此还需要保证操作的幂等性。
相对于2PC、3PC、TCC使用的范围更大,但是开发量也更大。所以TCC可以跨数据库、跨不同的业务系统来实现事务。

3.4 MQ非事务消息(加独立消息服务或者本地事务表)

image.png

  • 将消息先发送到一个我们自己编写的一个“独立消息服务”应用中,刚开始处于Prepare状态。
  • 业务逻辑处理成功后,确认发送消息,这个时候“独立消息服务”才会真正的把消息发送给消息队列。
  • 消费者消费成功后,ack时,除了对消息队列进行ack,对于独立消息服务也要进行ack,“独立消息服务”一般是把这条消息删除,而定时扫描prepare状态的消息,向消息发送端(生产者)确认的工作也由独立消息服务来完成。

    3.5 本地信息表(异步确保一致性)(可靠事件模式)

    image.png
    本地信息表其实就是利用了各系统本地事务来实现分布式事务。
    本地表就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候将业务的执行和消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
    然后再去调用下一个操作,如果下一个操作调用成功好说,消息表的消息状态可以直接改成已成功。
    如果调用失败也没事,会有后台任务定时去读取本地消息表,筛选出还没成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。
    这个时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等性的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理。

    3.6 总结

    2PC和3PC是一种强一致性事务,不过还是有数据不一致,阻塞等风险,而且只能用在数据库层面。
    而TCC是一种补偿性事务思想, 适用的范围更广,在业务层面实现,因此对业务的侵入性较大,每一个操作都需要实现对应的三个方法。
    本地消息、事务消息通知其实都是最终一致性事务,因此适用于一些对时间不敏感的业务。

    二、 分布式事务理论依据

    分布式事务存在两大理论依据:CAP定理和BASE理论。

    1、CAP定理(分布式一致性定理)

    CAP定理是指在一个分布式系统中consistency(一致性),Availability(可用性),partition tolerance(分区容错性),最多同时满足两个,三者不可兼得,多用AP。
    TX-LCN分布式事务框架 - 图8

    2、BASE理论

    是指Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。
    BASE理论是对CAP中一致性和可用性权衡的结果,是基于CAP演化而来的。
    BASE理论核心思想:即使无法做到强一致性,每个应用都可以根据自身业务特点,采用适当的方式达到最终的一致性。
    TX-LCN分布式事务框架 - 图9

三、TX-LCN

1、TX-LCN原理

TX-LCN有两大模块组成,TxClient和TxManger
TxClient作为模块的依赖框架,提供了Tx-LCN的标准支持,事务发起方和参与方都属于TxClient。TxManager作为分布式事务的控制方,控制整个事务。
image.png

1.1 原理中核心内容

  1. 创建事务组

指在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标识GroupId的过程。

  1. 加入事务组

添加事务组是指参与方在执行完业务方法后,将该模块的事务信息通知给TxManger的操作。

  1. 通知事务组

指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManger,TxManger将根据事务最终状态和事务组的信息来通知响应的参与模块提交或者回滚事务,并返回结果给事务发起方。

2、TX-LCN事务模式

TX-LCN分布式事务框架 - 图11

2.1 LCN模式

  1. 原理

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

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

LCN模式适合能用JDBC连接的所有支持事务的数据库

2.2 TCC事务模式

  1. 原理

TCC事务机制相对于传统的事务机制(X/Open XA Two_Phase-Commit),其特征在于它不依赖资源管理器(RM)对XA的支持,而是通过对(由业务系统提供的)业务逻辑的调度来实现分布式事务。主要由三部操作:Try:尝试执行业务(添加了@TccTransaction注解的方法)、Confirm:确认执行业务(确认方法作用编写提交事务的代码。当分布式事务执行成功时之执行的方法,此方法需要程序员自己提交,自己维护。对于Mongo或者Redis这种不支持事务的数据库都不去写此方法)、Cancel:取消执行业务(需要程序员自己编写,自己维护,编写回滚事务的逻辑)。

  1. 模式特点
  • 该模式对代码的侵入性高,要求每个业务需要写三个以上步骤的操作。
  • 该模式对有无本地事务控制都可以支持,适用面广。
  • 数据一致性控制几乎完全由开发者控制,对业务开发难度要求高。
  1. 总结

Tcc模式应用于所有不支持XA事务的软件,例如Redis和mongodb等。

2.3 TXC事务模式

  1. 原理

TXC模式命名来源于淘宝,实现原理是在执行SQL之前,先查询SQL的影响数据,然后保存执行的SQL信息和创建锁。当需要回滚的时候就采用这些记录数据回滚数据库,目前锁实现依赖redis分布式锁控制。(在使用LCN时必须要配置redis参数)

  1. 特点
  • 该模式同样对代码的嵌入性低。
  • 该模式仅限于对支持SQL方式的模块支持。
  • 该模式由于每次执行SQL之前需要先查询影响数据,因此相比LCN模式消耗资源与时间要多。
  • 该模式不会占用数据库的连接资源。
  1. 总结

只能用在支持SQL的数据库。对资源消耗较多。建议使用LCN模式。、

四、TxManager搭建

1、添加依赖

新建项目TxManager,并添加依赖。
依赖包含了Spring-boot的依赖,版本是2.0.5,如果希望把版本改变成2.5.2或其他版本只需要添加spring-boot-starter-parent继承即可。

  1. <!--TX-manager依赖-->
  2. <dependency>
  3. <groupId>com.codingapi.txlcn</groupId>
  4. <artifactId>txlcn-tm</artifactId>
  5. <version>5.0.2.RELEASE</version>
  6. </dependency>

2、执行SQL文件

执行tx-manager.sql文件(在任意的数据库下执行即可)
tx-manager.sql在txlcn-tm-5.0.2.RELEASE.zip压缩包中。
image.png
tm在通知tc进行事务的回滚和提交时,可能会因为网络或者超时参数配置原因出现失败,如果出现失败,tm会给t_tx_exception表中插入一条记录,然后自动进行事务补偿,即:重试通知。
在MySQL生成tx-manager的数据库。image.png
注意:默认情况下tx-manager需要记录日志信息的,需要在项目中配置日志连接数据库相关参数,其中日志存储数据库没有要求,可以存储到任意数据库中,当运行后会自动在数据库中生成一个日志表。如果不希望记录日志可以直接设置tx-lcn.logger.enabled=false,关闭日志功能,下面的日志连接数据库参数也可以不用配置。
在实际案例演示中会把所有的日志记录功能关闭。如果希望记录记录日志需要把下面代码在所有引用tx-lcn的项目的配置文件中进行配置。在配置文件中会对一下内容进行相对应的配置。

tx-lcn.logger.enabled=true
tx-lcn.logger.driver-class-name=com.mysql.jdbc.Driver
tx-lcn.logger.jdbc-url=jdbc:mysql://localhost:3306/tx-manager?characterEncoding=UTF-8
tx-lcn.logger.username=root
tx-lcn.logger.password=root

3、配置配置文件

在TxManager项目的resource下新建application.properties。tx-lcn在当前版本有个bug只能使用properties文件,使用yml文件会导致配置文件无法被加载的问题。
配置文件中内容上半部分是tx-manager数据库的连接信息。中间包含redis连接信息(此处连接的是redis单机版,端口默认,没有密码),下面是关闭日志记录功能

#设置日志文件在mysql的访问路径
spring.datasource.driver-class-name=com.mysql.cj.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
# 关闭日志记录功能
tx-lcn.logger.enabled=false
spring.application.name=TransactionManager
server.port=7970
# redis的地址 端口号默认为6379
spring.redis.host=192.168.80.128
#修改TM服务的端口号 默认为8070
tx-lcn.manager.port=8971
#修改TM服务的IP 默认为127.0.0.1
tx-lcn.manager.host=192.168.58.162
#修改TM服务登录密码 默认为codingapi
tx-lcn.manager.admin-key=123456

小提示:

  • 依赖Redis,所以需要安装Redis。
  • 7970是客户端访问端口,是Txmanager可视化界面访问端口,此端口任意。
  • 更加详细配置信息可以看txlcn-tm-5.0.2.RELEASE.zip中application.properties

    4、新建启动类

    添加注解@EnableTransactionManagerServer 必须有。
    @SpringBootApplication
    //启动事务管理器服务器
    @EnableTransactionManagerServer
    public class TxManagerApplication {
      public static void main(String[] args) {
          SpringApplication.run(TxManagerApplication.class, args);
      }
    }
    

    5、访问管理界面

    在浏览器输入:http://localhost:7970访问
    密码默认为codingapi
    可以在配置文件中修改登录密码
    image.png

    五、LCN事务模式

    1 创建数据库表

    注意:不要给student表添加外键约束。如果添加会导致分布式事务执行时student新增失败,因为teacher没有提交时student的tid值无法获取。

1 创建项目

案例使用聚合项目进行演示。
创建父项目,名称为LcnParent

1.1 配置pom.xml

txlcn-tc 是TX-LCN的客户端包
txlcn-txmsg-netty 是LCN客户端连接TxManager需要的包

| <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
<dependencies>
<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.2</version>
</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**><br />        <**groupId**>mysql</**groupId**><br />        <**artifactId**>mysql-connector-java</**artifactId**><br />        <**version**>5.1.48</**version**><br />        <**scope**>runtime</**scope**><br />    </**dependency**><br />    <**dependency**><br />        <**groupId**>org.projectlombok</**groupId**><br />        <**artifactId**>lombok</**artifactId**><br />        <**optional**>true</**optional**><br />    </**dependency**><br />    <**dependency**><br />        <**groupId**>com.codingapi.txlcn</**groupId**><br />        <**artifactId**>txlcn-tc</**artifactId**><br />        <**version**>5.0.2.RELEASE</**version**><br />    </**dependency**><br />    <**dependency**><br />        <**groupId**>com.codingapi.txlcn</**groupId**><br />        <**artifactId**>txlcn-txmsg-netty</**artifactId**><br />        <**version**>5.0.2.RELEASE</**version**><br />    </**dependency**><br /></**dependencies**>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
| | —- |

2 新建pojo项目

把实体类提出了
新建两个实体类。
新建com.bjsxt.pojo.Teacher

@Data
public class Teacher {
private Long id;
private String name;
}

新建com.bjsxt.pojo.Student

@Data
public class Student {
private Long id;
private String name;
private Long tid;
}

3 创建项目teacher_insert

新建teacher_insert项目

3.1 配置pom.xml

依赖pojo

<dependencies>
<dependency>
<artifactId>pojo</artifactId>
<groupId>com.bjsxt</groupId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>

3.2 编写配置文件

新建application.yml.
数据源连接的是Teacher表所在数据库
eureka单机版可以省略。
manager-address 配置TxManager项目的ip及端口。端口是内部访问端口,而不是可视化页面的端口。

| spring:
datasource:
url: jdbc:mysql://localhost:3306/microservice
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
application:
name: teacher-insert
server:
port: 8080

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/

tx-lcn:
client:
manager-address: 127.0.0.1:8070
| | —- |

3.3 新建mapper

新建com.bjsxt.mapper.TeacherMapper

@Mapper
public interface TeacherMapper {
@Insert(“insert into teacher values(#{id},#{name})”)
int insert(Teacher teacher);
}

3.4 新建service及实现类

新建com.bjsxt.service.TeacherService及实现类。
方法上@Transactional一定要有。本地事务控制。
@LcnTransaction表示当前方法加入到分布式事务控制。
@LcnTransaction属性propagation可取值
DTXPropagation.REQUIRED:默认值,表示如果当前没有事务组创建事务组,如果有事务组,加入事务组。多用在事务发起方。
DTXPropagation.SUPPORTS:如果当前没有事务组以本地事务运行,如果当前有事务组加入事务组。多用在事务参与方法。

public interface TeacherService {
int insert(Teacher teacher);
}
@Service
public class TeacherServiceImpl implements TeacherService {
@Autowired
private TeacherMapper teacherMapper;
@Override
@LcnTransaction
@Transactional
public int insert(Teacher teacher) {
return teacherMapper.insert(teacher);
}
}

3.5 新建控制器

新建com.bjsxt.controller.TeacherController。
由于在student_insert中通过OpenFeign进行条件,参数使用请求体数据,所以控制器方法的参数需要添加@RequestBody

| @Controller
public class TeacherController {
@Autowired
private TeacherService teacherService;

@RequestMapping(**"/insert"**)<br />    @ResponseBody<br />    **public int **insert(@RequestBody Teacher teacher){<br />        System.**_out_**.println(**"taecher"**+teacher);<br />        **return teacherService**.insert(teacher);<br />    }<br />}<br /> |

| —- |

3.6 新建启动器

新建com.bjsxt.TeacherInsertApplication。
一定要有注解@EnableDistributedtransaction表示启动分布式事务

@SpringBootApplication
@EnableDistributedTransaction
public class TeacherInsertApplication {
public static void main(String[] args) {
SpringApplication.run(TeacherInsertApplication.class,args);
}
}

4 新建项目student_insert

4.1 编写pom.xml

添加对pojo依赖

<dependencies>
<dependency>
<artifactId>pojo</artifactId>
<groupId>com.bjsxt</groupId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>

4.2 创建配置文件

新建application.yml

| spring:
datasource:
url: jdbc:mysql://localhost:3306/microservice
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
application:
name: student-insert
server:
port: 8081

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/

tx-lcn:
client:
manager-address: 127.0.0.1:8070 | | —- |

4.3 新建Feign接口

新建com.bjsxt.feign.TeacherInsertFeign

@FeignClient(“teacher-insert”)
public interface TeacherInsertFeign {
@RequestMapping(“/insert”)
int insert(Teacher teacher);
}

4.4 新建Mapper

新建com.bjsxt.mapper.StudentMapper

@Mapper
public interface StudentMapper {
@Insert(“insert into student values(#{id},#{name},#{tid})”)
int insert(Student student);
}

4.5 新建service及实现类

新建com.bjsxt.service.StudentService。
实现类中对Teacher和Student的主键都是随机数,为了测试时多次测试方便,所以没有给固定值。
Student的姓名通过客户端请求参数传递的,其他都不需要通过参数设置。

public interface StudentService {
int insert(Student student);
}
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Autowired
private TeacherInsertFeign teacherInsertFeign;
@Override
@LcnTransaction
@Transactional
public int insert(Student student) {
Teacher teacher = new Teacher();
Random random = new Random();
teacher.setId((long)random.nextInt(10000));
teacher.setName(“随意的名称”);
student.setTid(teacher.getId());
student.setId((long)random.nextInt(10000));
teacherInsertFeign.insert(teacher);
return studentMapper.insert(student);
}
}

4.6 新建控制器

新建com.bjsxt.controller.StudentController

@Controller
public class StudentController {
@Autowired
private StudentService studentService;
@RequestMapping(“/insert”)
@ResponseBody
public int insert(Student student){
return studentService.insert(student);
}
}

4.7 新建启动类

新建com.bjsxt.StudentInsertApplication

@SpringBootApplication
@EnableDistributedTransaction
@EnableFeignClients
public class StudentInsertApplication {
public static void main(String[] args) {
SpringApplication.run(StudentInsertApplication.class,args);
}
}

5 测试结果

在浏览器中输入http://localhost:8081/insert?name=bjsxt后,如果页面显示1并且数据库teacher表和student表各增加一条数据表示新增成功。
为了测试分布式事务效果,在student_insert项目实现类方法return上面添加int i =5/0; 的算术异常,再次访问url页面会报500,并且数据库中没有新增数据,说明分布式事务成功了。

六、 TCC事务模式(多模式混合使用)

在上面Lcn事务模式代码基础上进行修改

1 新建项目redis_insert

1.1 修改pom.xml

在当前项目中引入Redis的依赖。如果在父项目中进行引入,则所有的子项目都需要配置Redis的相关属性。

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>

1.2 新建配置文件

新建application.yml。
虽然当前项目是连接Redis但是也需要配置MySQL数据源,因为LCN需要在MySQL中记录异常信息等。
配置文件中比别的项目多了Redis的配置。

| spring:
datasource:
url: jdbc:mysql://localhost:3306/microservice
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
application:
name: redis-insert
redis:
host: 192.168.8.128

server:
port: 8082

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/

tx-lcn:
client:
manager-address: 127.0.0.1:8070 | | —- |

1.3 新建实体类

新建com.bjsxt.pojo.People

@Data
public class People {
private String id;
private String name;
}

1.4 新建service及实现类

新建com.bjsxt.service.PeopleService及实现类。
具有@TccTransaction注解的方法有以下特性
1. 可以没有@Transactional
2. 如果整个分布式事务所有方法执行都没有执行会回调名称为:confirm+方法名首字母大写的方法。insert方法的回调方法叫做confirmInsert()。同时方法参数也可以传递给回调方法。
3. 只要整个分布式事务中有一个方法出现异常就会回调cancel+方法名首字母大写的回调方法。需要在这个方法中编写事务回滚的业务。
@TccTransaction注解属性说明:
cancelMethod:明确指定失败的回调方法名
confirmMethod:明确指定成功的回调方法名

public interface PeopleService {
void insert(People people);
}
@Service
public class PeopleServiceImpl implements PeopleService {
@Autowired
private RedisTemplate redisTemplate;
@Override
@TccTransaction
public void insert(People people) {
redisTemplate.opsForValue.set(“key::”+people.getId(),people);
}
**public void **cancelInsert(People people){<br />        System.**_out_**.println(**"执行了cancel方法"**+people);<br />        **redisTemplate**.delete("key::" + people.getId());<br />        System.**_out_**.println(**"所谓的事务回滚就是删除新增的数据"**);<br />    }

**public void **confirmInsert(People people){<br />        System.**_out_**.println(**"执行了confirm方法"**+people);<br />    }<br />}<br /> |

1.5 新建控制器

新建com.bjsxt.controller.PeopleController

@Controller
public class PeopleController {
@Autowired
private PeopleService peopleService;
@RequestMapping(“/insert”)
@ResponseBody
public int insert(People people){
return peopleService.insert(people);
}
}

5.1 测试

在浏览器输入http://localhost:8082/insert?name=sxt 观察Redis中是否出现People的键值对,键值对中name属性值为sxt

2 修改student_insert

5.2 新建feign接口

新建com.bjsxt.feign.RedisInsertFeign。
为了传递普通表单数据,insert方法参数由@RequestParam。redis_insert控制器方法参数就可以使用String name或People进行接收。

@FeignClient(“redis-insert”)
public interface RedisInsertFeign {
@RequestMapping(“/insert”)
void insert(@RequestParam String id, @RequestParam String name);
}

2.1 修改service实现类

修改com.bjsxt.service.impl.StudentServiceImpl。
在实现类中调用feign接口的方法。
当前方法依然使用LCN事务模式。方法上面加什么事务模式注解只考虑当前方法本地事务,不考虑调用远程方法的事务。如果当前方法中没有本地事务,全是调用远程方法,那么当前方法使用LCN或TCC事务模式都可以,但是必须要有事务模式,因为如果没有注解就不会想TxManager中创建事务组。

@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Autowired
private TeacherInsertFeign teacherInsertFeign;
@Autowired
private RedisInsertFeign redisInsertFeign;
@Override
@LcnTransaction
@Transactional
public int insert(Student student) {
Teacher teacher = new Teacher();
Random random = new Random();
teacher.setId((long)random.nextInt(10000));
teacher.setName(“随意的名称”);
student.setTid(teacher.getId());
student.setId((long)random.nextInt(10000));
teacherInsertFeign.insert(teacher);
redisInsertFeign.insert(“”+random.nextInt(10000), “随意的名称”);
// int i = 5/0; _return studentMapper.insert(student);
}
}