TX-LCN分布式事务(2)

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 MongoDB,通用型 2核4GB
简介: TX-LCN分布式事务(2)

6 TxManager 搭建

6.1 创建项目,添加依赖

  • 新建项目 TxManager,并添加依赖
  • 依赖包含了 Spring-boot 的依赖,版本是 2.0.5,如果希望把版本改变成 2.2.2 或其他版本只需要添加 spring-boot-starter-parent 继承即可
<dependencies> 
  <dependency> 
    <groupId>com.codingapi.txlcn</groupId> 
    <artifactId>txlcn-tm</artifactId> 
    <version>5.0.2.RELEASE</version> 
  </dependency> 
</dependencies>

6.2 执行 SQL 文件

  • 执行 tx-manager.sql 文件(在任意的数据库下执行即可)
  • tx-manager.sql 在 txlcn-tm-5.0.2.RELEASE.zip 压缩包中。
  • 在 MySQL 生成 tx-manager 的数据库,在数据库中新建 t_tx_exception 的表,此表用作存储事务组信息
  • 注意:
  • 默认情况下 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://192.168.8.131:3306/tx-manager?characterEncoding=UTF-8 
tx-lcn.logger.username=root 
tx-lcn.logger.password=root

6.3 配置配置文件

  • 在 TxManager 项目的 resource 下新建 application.properties。tx-lcn 在当前版本有个 bug 只能使用 properties 文件,使用 yml 文件会导致配置文件无法被加载的问题
  • 配置文件中内容上半部分是 tx-manager 数据库的连接信息。中间包含 redis 连接信息(此处连接的是 redis 单机版,端口默认,没有密码),下面是关闭日志记录功能
  • 小提示:
  • 依赖 Redis,所以需要安装 Redis。
  • 7970 是客户端访问端口,是 Txmanager 可视化界面访问端口,此端口任意。
spring.application.name=TransactionManager
server.port=7970
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/tx-manager?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
spring.redis.host=192.168.8.129
tx-lcn.logger.enabled=false

6.4 新建启动类

  • 注意注解@EnableTrasactionManagerServer 必须有
@SpringBootApplication 
@EnableTransactionManagerServer
public class MyApplication { 
  public static void main(String[] args) { 
    SpringApplication.run(MyApplication.class,args); 
  } 
}

6.5 访问管理界面

  • 在浏览器输入:http://localhost:7970 访问。
  • 密码默认是 codingapi
  • 可以在配置文件中修改登录密码
tx-lcn.manager.admin-key=dqcgm

20210120104416688.jpg

7 LCN 事务模式

7.1 创建数据库表

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

20201024194220691.png

7.2 创建项目

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

7.2.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> 
    <groupId>mysql</groupId> 
    <artifactId>mysql-connector-java</artifactId> 
    <version>5.1.48</version> 
    <scope>runtime</scope> 
  </dependency>
  <dependency> 
    <groupId>org.projectlombok</groupId> 
    <artifactId>lombok</artifactId> 
    <optional>true</optional> 
  </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>
</dependencies>
<dependencyManagement>
  <dependencies> 
    <dependency> 
      <groupId>org.springframework.cloud</groupId> 
      <artifactId>spring-cloud-dependencies</artifactId> 
      <version>Hoxton.SR4</version> 
      <type>pom</type> 
      <scope>import</scope> 
    </dependency> 
  </dependencies> 
</dependencyManagement>

7.3 新建 pojo 项目

  • 把实体类提出来
  • 新建两个实体类。
  • 新建 com.dqcgm.pojo.Teacher
@Data 
public class Teacher { 
  private Long id; 
  private String name; 
}
12345
  • 新建 com.dqcgm.pojo.Student
@Data 
public class Student { 
  private Long id; 
  private String name; 
  private Long tid; 
}

7.4 创建项目 teacher_insert

  • 新建 teacher_insert 项目

7.4.1 配置 pom.xml

  • 依赖 pojo
<dependencies> 
  <dependency> 
    <artifactId>pojo</artifactId> 
    <groupId>com.dqcgm</groupId> 
    <version>0.0.1-SNAPSHOT</version> 
  </dependency> 
</dependencies>

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

7.4.3 新建 mapper

  • 新建 com.dqcgm.mapper.TeacherMapper
@Mapper 
public interface TeacherMapper { 
  @Insert("insert into teacher values(#{id},#{name})") 
  int insert(Teacher teacher); 
}
12345

7.4.4 新建 service 及实现类

  • 新建 com.dqcgm.service.TeacherService 及实现类。
  • 方法上@Transactional 一定要有。本地事务控制。
  • @LcnTransaction 表示当前方法加入到分布式事务控制。

@LcnTransaction 属性 propagation 可取值

1.DTXPropagation.REQUIRED:默认值,表示如果当前没有事务组创建事务组,如果有事务组,加入事务组。多用在事务发起方。

DTXPropagation.SUPPORTS:如果当前没有事务组以本地事务运行,如果当前有事务组加入事务组。多用在事务参与方法。

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

7.4.5 新建控制器

  • 新建 com.dqcgm.controller.TeacherController。
  • 由于在 student_insert 中通过 OpenFeign 进行条件,参数使用请求体数据,所以控制器方法的参数需要添加@RequestBody
@Controller 
public class TeacherController {
  @Autowired 
  private TeacherService teacherService;
  @RequestMapping("/insert") 
  @ResponseBody
  public int insert(@RequestBody Teacher teacher){ 
    System.out.println("taecher"+teacher); 
    return teacherService.insert(teacher); 
  } 
}

7.4.6 新建启动器

  • 新建 com.dqcgm.TeacherInsertApplication。
  • 一定要有注解@EnableDistributedTransaction 表示启动分布式事务
@SpringBootApplication 
@EnableDistributedTransaction 
public class TeacherInsertApplication {
  public static void main(String[] args) {
    SpringApplication.run(TeacherInsertApplication.class,args); 
  } 
}

7.5 新建项目 student_insert

7.5.1 编写 pom.xml

  • 添加对 pojo 依赖
<dependencies> 
  <dependency> 
    <artifactId>pojo</artifactId> 
    <groupId>com.dqcgm</groupId> 
    <version>0.0.1-SNAPSHOT</version> 
  </dependency> 
</dependencies>

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

7.5.3 新建 Feign 接口

  • 新建 com.dqcgm.feign.TeacherInsertFeign
@FeignClient("teacher-insert") 
public interface TeacherInsertFeign { 
  @RequestMapping("/insert") int insert(Teacher teacher); 
}

7.5.4 新建 Mapper

  • 新建 com.dqcgm.mapper.StudentMapper
@Mapper 
public interface StudentMapper { 
  @Insert("insert into student values(#{id},#{name},#{tid})") 
  int insert(Student student); 
}

7.5.5 新建 service 及实现类

  • 新建 com.dqcgm.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);
  }
}

7.5.6 新建控制器

  • 新建 com.dqcgm.controller.StudentController
@Controller 
public class StudentController {
  @Autowired 
  private StudentService studentService;
  @RequestMapping("/insert") 
  @ResponseBody 
  public int insert(Student student){ 
    return studentService.insert(student); 
  } 
}

7.5.7 新建启动类

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

7.6 测试结果

  • 在浏览器中输入 http://localhost:8081/insert?name=dqcgm后,如果页面显示 1 并且数据库 teacher 表和 student 表各增加一条数据表示新增成功。

为了测试分布式事务效果,在 student_insert 项目实现类方法 return 上面添加 int i =5/0; 的算术异常,再次访问 url 页面会报 500,并且数据库中没有新增数据,说明分布式事务成功了。

20210120104506330.jpg

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

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

8.1 新建项目 mongodb_insert

8.1.1 修改 pom.xml

  • 在当前项目中引入 mongodb 的依赖。如果在父项目中进行引入,则所有的子项目都需要配置 mongodb 的相关属性
<dependencies> 
  <dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-data-mongodb</artifactId> 
    <version>2.2.6.RELEASE</version> 
  </dependency> 
</dependencies>

8.1.2 新建配置文件

  • 新建 application.yml。
  • 虽然当前项目是连接 Mongodb 但是也需要配置 MySQL 数据源,因为 LCN 需要在 MySQL 中记录异常信息等
  • 配置文件中比别的项目多了 MongoDB 的配置
spring: 
  datasource:
    url: jdbc:mysql://localhost:3306/microservice
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
  application:
    name: mongodb-insert
  data:
    mongodb:
      authentication-database: admin
      username: dqcgm
      password: dqcgmpwd
      database: lcn
      host: 192.168.8.139
      port: 27017
server:
  port: 8082
eureka: 
  client: 
    service-url: 
      defaultZone: http://localhost:8761/eureka/
tx-lcn: 
  client: 
    manager-address: 127.0.0.1:8070

8.1.3 新建实体类

  • 新建 com.dqcgm.pojo.People
@Data 
public class People { 
  private String id; 
  private String name; 
}

8.1.4 新建 service 及实现类

  • 新建 com.dqcgm.service.PeopleService 及实现类
  • 具有@TccTransaction 注解的方法有以下特性
    1.可以没有@Transactional

2.如果整个分布式事务所有方法执行都没有异常,会回调名称为:confirm+方法名首字母大写的方法。insert 方法的回调方法叫做 confirmInsert()。同时方法参数也可以传递给回调方法

  • 3.只要整个分布式事务中有一个方法出现异常,会回调 cancel+方法名首字母大写的回调方法。需要在这个方法中编写事务回滚的业务。
  • @TccTransaction 注解属性说明:
  • cancelMethod:明确指定失败的回调方法名
    confirmMethod:明确指定成功的回调方法名
public interface PeopleService { 
  int insert(People people); 
}
@Service 
public class PeopleServiceImpl implements PeopleService {
  @Autowired 
  private MongoTemplate mongoTemplate;
  @Override 
  @TccTransaction
  public int insert(People people) {
    People result = mongoTemplate.insert(people);
    if(result!=null){
      return 1;
    }
    return 0;
  }
  public void cancelInsert(People people){ 
    System.out.println("执行了 cancel 方法"+people); 
    mongoTemplate.remove(people); 
    System.out.println("所谓的事务回滚就是删除新增的数据"); 
  }
  public void confirmInsert(People people){ 
    System.out.println("执行了 confirm 方法"+people); 
  }
}

8.1.5 新建控制器

  • 新建 com.dqcgm.controller.PeopleController
@Controller
public class PeopleController {
  @Autowired 
  private PeopleService peopleService; 
  @RequestMapping("/insert") 
  @ResponseBody
  public int insert(People people){ 
    return peopleService.insert(people); 
  } 
}

8.1.6 测试

  • 在浏览器输入 http://localhost:8082/insert?name=dqcgm观察 mongodb 中是否出现了 lcn 的 数据库,数据库中是否出现 People 的集合,people 集合中 name 属性值为 dqcgm

8.2 修改 student_insert

8.2.1 新建 feign 接口

  • 新建 com.dqcgm.feign.MongodbInsertFeign。
  • 为了传递普通表单数据,insert 方法参数由@RequestParam。mongodb_insert 控制器方法参数就可以使用 String name 或 People 进行接收
@FeignClient("mongodb-insert") 
public interface MongodbInsertFeign { 
  @RequestMapping("/insert") 
  int insert(@RequestParam String name); 
}

8.2.2 修改 service 实现类

  • 修改 com.dqcgm.service.impl.StudentServiceImpl。
  • 在实现类中调用 feign 接口的方法
  • 当前方法依然使用 LCN 事务模式。方法上面加什么事务模式注解只考虑当前方法本地事务,不考虑调用远程方法的事务。如果当前方法中没有本地事务,全是调用远程方法,那么当前方法使用 LCN 或 TCC 事务模式都可以,但是必须要有事务模式,因为如果没有注解就不会想 TxManager 中创建事务组。
@Service 
public class StudentServiceImpl implements StudentService {
  @Autowired 
  private StudentMapper studentMapper; 
  @Autowired 
  private TeacherInsertFeign teacherInsertFeign;
  @Autowired 
  private MongodbInsertFeign mongodbInsertFeign; 
  @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);
    mongodbInsertFeign.insert("随意的名称");
    return studentMapper.insert(student);
  }
}


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
8月前
|
SQL NoSQL 关系型数据库
TX-LCN分布式事务(1)
TX-LCN分布式事务(1)
74 1
|
6天前
|
NoSQL Java 关系型数据库
【Redis系列笔记】分布式锁
分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让程序串行执行,这就是分布式锁的核心思路
27 2
|
24天前
|
NoSQL Java Redis
redis分布式锁
redis分布式锁
|
2月前
|
缓存 NoSQL Java
分布式项目中锁的应用(本地锁-_redis【setnx】-_redisson-_springcache)-fen-bu-shi-xiang-mu-zhong-suo-de-ying-yong--ben-de-suo--redissetnx-springcache-redisson(一)
分布式项目中锁的应用(本地锁-_redis【setnx】-_redisson-_springcache)-fen-bu-shi-xiang-mu-zhong-suo-de-ying-yong--ben-de-suo--redissetnx-springcache-redisson
59 0
|
2天前
|
NoSQL Java 大数据
介绍redis分布式锁
分布式锁是解决多进程在分布式环境中争夺资源的问题,与本地锁相似但适用于不同进程。以Redis为例,通过`setIfAbsent`实现占锁,加锁同时设置过期时间避免死锁。然而,获取锁与设置过期时间非原子性可能导致并发问题,解决方案是使用`setIfAbsent`的超时参数。此外,释放锁前需验证归属,防止误删他人锁,可借助Lua脚本确保原子性。实际应用中还有锁续期、重试机制等复杂问题,现成解决方案如RedisLockRegistry和Redisson。
|
3天前
|
缓存 NoSQL Java
【亮剑】如何使用注解来实现 Redis 分布式锁的功能?
【4月更文挑战第30天】分布式锁是保证多服务实例同步的关键机制,常用于互斥访问共享资源、控制访问顺序和系统保护。基于 Redis 的分布式锁利用 SETNX 或 SET 命令实现,并考虑自动过期、可重入及原子性以确保可靠性。在 Java Spring Boot 中,可通过 `@EnableCaching`、`@Cacheable` 和 `@CacheEvict` 注解轻松实现 Redis 分布式锁功能。
|
4天前
|
NoSQL Redis 微服务
分布式锁_redis实现
分布式锁_redis实现
|
6天前
|
存储 NoSQL Java
基于Redis实现分布式锁
基于Redis实现分布式锁
31 0
|
7天前
|
NoSQL Java Redis
Redis入门到通关之分布式锁Rediision
Redis入门到通关之分布式锁Rediision
11 0
|
7天前
|
NoSQL 关系型数据库 MySQL
Redis入门到通关之Redis实现分布式锁
Redis入门到通关之Redis实现分布式锁
12 1