TX-LCN分布式事务(2)

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL,集群系列 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
目录
相关文章
|
SQL NoSQL 关系型数据库
TX-LCN分布式事务(1)
TX-LCN分布式事务(1)
137 1
|
25天前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
3月前
|
NoSQL Redis
基于Redis的高可用分布式锁——RedLock
这篇文章介绍了基于Redis的高可用分布式锁RedLock的概念、工作流程、获取和释放锁的方法,以及RedLock相比单机锁在高可用性上的优势,同时指出了其在某些特殊场景下的不足,并提到了ZooKeeper作为另一种实现分布式锁的方案。
105 2
基于Redis的高可用分布式锁——RedLock
|
3月前
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
这篇文章是关于如何在SpringBoot应用中整合Redis并处理分布式场景下的缓存问题,包括缓存穿透、缓存雪崩和缓存击穿。文章详细讨论了在分布式情况下如何添加分布式锁来解决缓存击穿问题,提供了加锁和解锁的实现过程,并展示了使用JMeter进行压力测试来验证锁机制有效性的方法。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
|
1天前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
30 16
|
1月前
|
缓存 NoSQL Java
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
55 3
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
|
26天前
|
NoSQL Redis 数据库
计数器 分布式锁 redis实现
【10月更文挑战第5天】
46 1
|
30天前
|
NoSQL 算法 关系型数据库
Redis分布式锁
【10月更文挑战第1天】分布式锁用于在多进程环境中保护共享资源,防止并发冲突。通常借助外部系统如Redis或Zookeeper实现。通过`SETNX`命令加锁,并设置过期时间防止死锁。为避免误删他人锁,加锁时附带唯一标识,解锁前验证。面对锁提前过期的问题,可使用守护线程自动续期。在Redis集群中,需考虑主从同步延迟导致的锁丢失问题,Redlock算法可提高锁的可靠性。
69 4
|
1月前
|
存储 缓存 NoSQL
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
54 4
|
1月前
|
缓存 NoSQL Ubuntu
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
53 3

热门文章

最新文章

下一篇
无影云桌面