Hmily实现TCC事务
业务说明
本实例通过Hmily实现ICC分布式事务,模拟两个账户的转账交易过程。
两个账户分别在不同的银行(张三在bank1、李四在bank2) , bank1. bank2是两个微服务。 交易过程
是,张三给李四转账指定金额。
上述交易步骤,要么一起成功,要么一起失败,必须是一个整体性的事务。
程序组成部分
数据库:MySQL-8.X (包括bank1和bank2 两个数据库)
JDK:1.8.X
微服务框架:SpringBoot2.3、SpringCloudHoxton
Hmily:hmily-springcloud.2.0.4
微服务及数据库的关系:
dtx/hmily-bank-tcc/bank1-server 银行1 操作张三用户,连接bank1
dtx/hmily-bank-tcc/bank1-server 银行2 操作李四用户,连接bank2
服务注册中心:nacos
创建数据库
创建Hmily数据库,用于存储hmily框记录的数据,运行时自动添加表
1 | CREATE DATABASE `hmily` CHARACTER SET `utf8mb4` ; |
bank1库,包含张三的账户
1 2 | DROP DATABASE IF EXISTS `bank1`; CREATE DATABASE `bank1` CHARACTER SET 'utf8mb4'; |
1 2 3 4 5 6 7 8 9 10 11 12 13 | USE `bank1`; DROP TABLE IF EXISTS `account_info`; CREATE TABLE `account_info` ( id bigint ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键', account_name VARCHAR ( 100 ) COMMENT '账户姓名', account_no VARCHAR ( 100 ) COMMENT '账户卡号', account_password VARCHAR ( 100 ) COMMENT '账户密码', account_balance DECIMAL ( 10,2) COMMENT '账户余额', PRIMARY KEY ( id ) ) COMMENT = '账户表'; INSERT INTO `account_info` VALUES ('1' , '张三的账户','1', '', 10000 ); |
bank2库,包含李四的账户
1 2 | DROP DATABASE IF EXISTS `bank2`; CREATE DATABASE `bank2` CHARACTER SET 'utf8mb4'; |
1 2 3 4 5 6 7 8 9 10 11 12 13 | USE `bank2`; DROP TABLE IF EXISTS `account_info`; CREATE TABLE `account_info` ( id bigint ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主键', account_name VARCHAR ( 100 ) COMMENT '账户姓名', account_no VARCHAR ( 100 ) COMMENT '账户卡号', account_password VARCHAR ( 100 ) COMMENT '账户密码', account_balance DECIMAL ( 10,2) COMMENT '账户余额', PRIMARY KEY ( id ) ) COMMENT = '账户表'; INSERT INTO `account_info` VALUES ('1' , '李四的账户','2', '', 0); |
每个数据库都创建try、confirm、cancelsa
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | CREATE TABLE `local_try_log` ( `tx_no` varchar(64) not NUll comment '事务Id', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`tx_no`) ); CREATE TABLE `local_confirm_log` ( `tx_no` varchar(64) not NUll comment '事务Id', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`tx_no`) ); CREATE TABLE `local_cancel_log` ( `tx_no` varchar(64) not NUll comment '事务Id', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`tx_no`) ); |
搭建项目
- pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | <?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.3.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.wry.dtx</groupId> <artifactId>bank1-server-tcc</artifactId> <version>0.0.1-SNAPSHOT</version> <name>bank1-server-tcc</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR5</spring-cloud.version> <spring-cloud-alibaba.version>2.2.0.RELEASE</spring-cloud-alibaba.version> <mybatis-plus.version>3.3.2</mybatis-plus.version> <mysql.version>8.0.19</mysql.version> <hmily.version>2.0.0-RELEASE</hmily.version> </properties> <dependencies> <!--hmily --> <dependency> <groupId>org.dromara</groupId> <artifactId>hmily-spring-boot-starter-springcloud</artifactId> <version>${hmily.version}</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId></exclusion> <exclusion> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </exclusion> <exclusion> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-eureka-client</artifactId> </exclusion> </exclusions> </dependency> <!-- openfeign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--注册中心客户端--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</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> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.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> |
- application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | server: port: 8003 spring: application: name: bank1-server-tcc datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://39.96.3.100:3306/bank1?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT+8&nullCatalogMeansCurrent=true username: root password: 123456 cloud: nacos: discovery: server-addr: 39.96.3.100:8848 logging: level: root: info mybatis-plus: mapper-locations: classpath:/mapper/*.xml typeAliasesPackage: com.wry.dtx.bank1.entity global-config: db-config: field-strategy: not-empty id-type: auto db-type: mysql ribbon: ReadTimeout: 3000 ConnectTimeout: 3000 org: dromara: hmily : serializer : kryo recoverDelayTime : 128 retryMax : 30 scheduledDelay : 128 scheduledThreadMax : 10 repositorySupport : db started: true hmilyDbConfig : driverClassName : com.mysql.cj.jdbc.Driver url : jdbc:mysql://39.96.3.100:3306/hmily?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT+8&nullCatalogMeansCurrent=true username : root password : 123456 |
- 启动类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package com.wry.dtx.bank1; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.EnableAspectJAutoProxy; @SpringBootApplication @EnableDiscoveryClient @EnableAspectJAutoProxy @EnableFeignClients(basePackages = {"com.wry.dtx.bank1.feign"}) @ComponentScan({"org.dromara.hmily","com.wry.dtx.bank1"}) public class Bank1ServerTccApplication { public static void main(String[] args) { SpringApplication.run(Bank1ServerTccApplication.class, args); } } |
具体代码可参考Github:https://github.com/hobbyWang/DistributedTransaction/tree/master/hmily-bank-tcc
小结
如果拿TCC事务的处理流程与2PC两阶段提交做比较,2PC通常都是在跨库的DB层面,而TCC则在应用层面的处 理,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自己定义数据操作的粒度,使得降低锁冲突、提高吞吐量成为可能。 而不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、cancel三个操作。此 外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。