Bank transfer based on Hmily TCC in Dubbo

1. Background knowledge

1.1 BASE flexibility

BASE is the abbreviation of basic availability, flexible state and final consistency.

  • Basically Available (BA): ensure that distributed transaction participants are not necessarily online at the same time.
  • Soft State (S): the system state update is allowed to have a certain delay, which may not be perceived by the customer.
  • Eventual consistency: the final consistency of the system is usually guaranteed through message passing.

Different from traditional ACID transactions, traditional ACID requires high isolation. All resources must be locked during transaction execution. Concept of flexible transaction
The mutex operation is moved from the resource level to the business level through the business logic. By relaxing the requirements for strong consistency, the system can be exchanged
Improvement of system throughput.

1.2 common modes of base flexible things

  • TCC

It is handled by manual compensation.
TCC mode is a classic flexible transaction solution. The user needs to provide three methods: try, confirm and cancel. Try, confirm and cancel will be executed in real cases and try and cancel will be executed in abnormal cases. The confirm method is not necessary and depends entirely on how the user's try method is written. The confirm and cancel methods also require the user to ensure idempotency, which will add a certain amount of work. Since the data has been submitted after the try method is completed, it does not ensure data isolation. But in this way, its performance is relatively high. A good system design is very suitable for TCC mode.

  • AT

It is processed by automatic compensation.

1.3 hmily

Hmily is a high-performance, zero intrusion, financial level distributed transaction solution. At present, it mainly provides support for flexible transactions, including TCC, TAC (automatic generation and rollback SQL) solutions, and XA and other solutions will be supported in the future.
Reference address:
https://dromara.org/zh/projects/hmily/overview/

1.4 case description

Experience the use of hmily through a case of transfer.
User A needs to transfer money between bank A and bank B.

2. Database design

2.1 account related forms

Two tables are designed:
T_BANK_ACCOUNT table
T_BANK_FREEZE table

  • T_BANK_ACCOUNT
SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for t_bank_account
-- ----------------------------
DROP TABLE IF EXISTS `t_bank_account`;
CREATE TABLE `t_bank_account` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key auto increment column',
  `customer_id` int(11) NOT NULL COMMENT 'User number',
  `account_type` tinyint(4) NOT NULL DEFAULT '1' COMMENT 'Account type: 1 RMB account, 2 USD account',
  `balance` bigint(20) NOT NULL COMMENT 'Customer balance unit points',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'User registration time',
  `is_validate` tinyint(4) NOT NULL DEFAULT '1' COMMENT 'Whether the data is valid: 1 valid data, 2 invalid data',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last modification time',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
  • T_BANK_FREEZE
SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for t_bank_freeze
-- ----------------------------
DROP TABLE IF EXISTS `t_bank_freeze`;
CREATE TABLE `t_bank_freeze` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key auto increment column',
  `customer_id` int(11) NOT NULL COMMENT 'User number',
  `account_type` tinyint(4) NOT NULL DEFAULT '1' COMMENT 'Account type: 1 RMB account, 2 USD account',
  `amount` bigint(20) NOT NULL COMMENT 'Customer balance unit points',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation time',
  `is_validate` tinyint(4) NOT NULL DEFAULT '1' COMMENT 'Whether the data is valid: 1 valid data, 2 invalid data',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last modification time',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

Create the above two tables in the databases gts01 and gts02 corresponding to bank1 and bank2 respectively.

2.2 TCC related table

The following three TCC related tables need to be created in both application databases.

DROP TABLE IF EXISTS `t_try_log`;
CREATE TABLE `t_try_log` (
  `tx_no` varchar(64) NOT NULL COMMENT 'affair id',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`tx_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `t_confirm_log`;
CREATE TABLE `t_confirm_log` (
                                 `tx_no` varchar(64) NOT NULL COMMENT 'affair id',
                                 `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
                                 PRIMARY KEY (`tx_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `t_cancel_log`;
CREATE TABLE `t_cancel_log` (
                                `tx_no` varchar(64) NOT NULL COMMENT 'affair id',
                                `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
                                PRIMARY KEY (`tx_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

The above table structure needs to be created in the two application databases gts01 and gts02 respectively.

3.Dubbo project configuration

Because hmily's TCC process can only run in rpc framework or spring cloud mode. In this paper, dubbo is selected to implement.

3.1 project planning

dubbo project is divided into modules. module is divided as follows:

moduledescribe
bank-transfer-apidubbo API
bank-transfer-ormThe orm package of the database is automatically generated by fluent
bank-transfer-bank1bank1 service, a service for initiating transfer
bank-transfer-bank2bank2 service, receiving transfer service

3.2 pom documents

3.2.1 pom file of parent project

<?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.0.RELEASE</version>
<!--		<relativePath/> &lt;!&ndash; lookup parent from repository &ndash;&gt;-->
	</parent>
	<groupId>com.dhb</groupId>
	<artifactId>bank-transfer</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>bank-transfer</name>
	<packaging>pom</packaging>
	<description>RPC demo project for Spring Boot</description>

	<modules>
		<module>bank-transfer-api</module>
		<module>bank-transfer-orm</module>
		<module>bank-transfer-bank1</module>
		<module>bank-transfer-bank2</module>
		<module>bank-transfer-client</module>
	</modules>

	<properties>
		<java.version>1.8</java.version>
		<spring-boot.version>2.3.0.RELEASE</spring-boot.version>
		<dubbo.version>2.7.7</dubbo.version>
	</properties>

	<dependencyManagement>
		<dependencies>
			<!-- Spring Boot -->
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-dependencies</artifactId>
				<version>${spring-boot.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>

			<!-- Apache Dubbo  -->
			<dependency>
				<groupId>org.apache.dubbo</groupId>
				<artifactId>dubbo-dependencies-bom</artifactId>
				<version>${dubbo.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>

		</dependencies>
	</dependencyManagement>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- lombok -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
		<!-- Dubbo Spring Boot Starter -->
		<dependency>
			<groupId>org.apache.dubbo</groupId>
			<artifactId>dubbo-spring-boot-starter</artifactId>
			<version>${dubbo.version}</version>
		</dependency>

		<!-- Zookeeper dependencies -->
		<dependency>
			<groupId>org.apache.dubbo</groupId>
			<artifactId>dubbo-dependencies-zookeeper</artifactId>
			<version>${dubbo.version}</version>
			<type>pom</type>
			<exclusions>
				<exclusion>
					<groupId>org.slf4j</groupId>
					<artifactId>slf4j-log4j12</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

</project>

3.2.2 pom file of API

<?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>com.dhb</groupId>
        <artifactId>bank-transfer</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <!--		<relativePath/> &lt;!&ndash; lookup parent from repository &ndash;&gt;-->
    </parent>
    <groupId>com.dhb</groupId>
    <artifactId>bank-transfer-api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>bank-transfer-api</name>
    <description>bank-transfer-api</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>hmily-annotation</artifactId>
            <version>2.1.1</version>
        </dependency>
    </dependencies>

</project>

3.2.3 pom file of ORM

<?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>com.dhb</groupId>
        <artifactId>bank-transfer</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <!--		<relativePath/> &lt;!&ndash; lookup parent from repository &ndash;&gt;-->
    </parent>
    <groupId>com.dhb</groupId>
    <artifactId>bank-transfer-orm</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>bank-transfer-orm</name>
    <description>bank-transfer-orm</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.atool</groupId>
            <artifactId>fluent-mybatis</artifactId>
            <version>1.7.2</version>
        </dependency>
        <dependency>
            <groupId>com.github.atool</groupId>
            <artifactId>fluent-mybatis-processor</artifactId>
            <version>1.7.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>
    </dependencies>
</project>

3.2.4 pom file of bank1

<?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>com.dhb</groupId>
        <artifactId>bank-transfer</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <!--		<relativePath/> &lt;!&ndash; lookup parent from repository &ndash;&gt;-->
    </parent>
    <groupId>com.dhb</groupId>
    <artifactId>bank-transfer-bank1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>bank-transfer-bank1</name>
    <description>bank-transfer-bank1</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.dhb</groupId>
            <artifactId>bank-transfer-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>com.dhb</groupId>
            <artifactId>bank-transfer-orm</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>hmily-spring-boot-starter-dubbo</artifactId>
            <version>2.1.1</version>
            <exclusions>
                <exclusion>
                    <groupId>org.dromara</groupId>
                    <artifactId>hmily-repository-mongodb</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.1.0.Final</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3.2.5 pom file of bank2

<?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>com.dhb</groupId>
        <artifactId>bank-transfer</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <!--		<relativePath/> &lt;!&ndash; lookup parent from repository &ndash;&gt;-->
    </parent>
    <groupId>com.dhb</groupId>
    <artifactId>bank-transfer-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>bank-transfer-client</name>
    <description>bank-transfer-client</description>
    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.dhb</groupId>
            <artifactId>bank-transfer-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.8</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.2.6 pom file of client

<?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>com.dhb</groupId>
        <artifactId>bank-transfer</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <!--		<relativePath/> &lt;!&ndash; lookup parent from repository &ndash;&gt;-->
    </parent>
    <groupId>com.dhb</groupId>
    <artifactId>bank-transfer-bank1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>bank-transfer-bank1</name>
    <description>bank-transfer-bank1</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.dhb</groupId>
            <artifactId>bank-transfer-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>com.dhb</groupId>
            <artifactId>bank-transfer-orm</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>hmily-spring-boot-starter-dubbo</artifactId>
            <version>2.1.1</version>
            <exclusions>
                <exclusion>
                    <groupId>org.dromara</groupId>
                    <artifactId>hmily-repository-mongodb</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.1.0.Final</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.3 yml configuration file

3.3.1 yml configuration of bank1

server:
  port: 8088

spring:
  application:
    name: bank-transfer-bank1

dubbo:
  scan:
    base-packages: com.dhb.bank.transfer.bank1
  protocol:
    name: dubbo
    port: 12345
  registry:
    address: zookeeper://localhost:2181
  metadata-report:
    address: zookeeper://localhost:2181
  application:
    qosEnable: true
    qosPort: 22222
    qosAcceptForeignIp: true
    qos-enable-compatible: true
    qos-host-compatible: localhost
    qos-port-compatible: 22222
    qos-accept-foreign-ip-compatible: true
    qos-host: localhost


#Data source configuration default Hikari
spring.datasource.url: jdbc:mysql://192.168.161.114:3306/gts01?useSSL=false&autoReconnect=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username: gts
spring.datasource.password: ******
spring.datasource.driver-class-name: com.mysql.cj.jdbc.Driver

3.3.2 yml configuration of bank2

server:
  port: 8089

spring:
  application:
    name: bank-transfer-bank2

dubbo:
  scan:
    base-packages: com.dhb.bank.transfer.bank2
  protocol:
    name: dubbo
    port: 12346
  registry:
    address: zookeeper://localhost:2181
  metadata-report:
    address: zookeeper://localhost:2181
  application:
    qosEnable: true
    qosPort: 22222
    qosAcceptForeignIp: true
    qos-enable-compatible: true
    qos-host-compatible: localhost
    qos-port-compatible: 22222
    qos-accept-foreign-ip-compatible: true
    qos-host: localhost


#Data source configuration default Hikari
spring.datasource.url: jdbc:mysql://192.168.161.114:3306/gts02?useSSL=false&autoReconnect=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username: gts
spring.datasource.password: ******
spring.datasource.driver-class-name: com.mysql.cj.jdbc.Driver

3.3.3 client yml configuration

spring:
  application:
    name: bank-transfer-client
  main:
    allow-bean-definition-overriding: true
    web-application-type: none
dubbo:
  scan:
    base-packages: com.dhb.bank.transfer.client
  registry:
    address: zookeeper://localhost:2181
  metadata-report:
    address: zookeeper://localhost:2181

4.hmily configuration

hmily needs to specify a separate configuration file.
You need to create a separate hmily configuration database. This database only needs to create db, and the database table hmily will be created automatically.

create database hmily DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
grant all privileges on gts.* to gts@'%.%.%.%' identified by '******';
flush privileges;

##4.1 hmily configuration of bank1

hmily:
  server:
    configMode: local
    appName: bank-transfer-bank1
  #  If the server.configmode is EQ local, the configuration information here will be read
  config:
    appName: bank-transfer-bank1
    serializer: kryo
    contextTransmittalMode: threadLocal
    scheduledThreadMax: 16
    scheduledRecoveryDelay: 60
    scheduledCleanDelay: 60
    scheduledPhyDeletedDelay: 600
    scheduledInitDelay: 30
    recoverDelayTime: 60
    cleanDelayTime: 180
    limit: 200
    retryMax: 10
    bufferSize: 8192
    consumerThreads: 16
    asyncRepository: true
    autoSql: true
    phyDeleted: true
    storeDays: 3
    repository: mysql

repository:
  database:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.161.114:3306/hmily?useSSL=false&autoReconnect=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: gts
    password: mysql
    maxActive: 20
    minIdle: 10
    connectionTimeout: 30000
    idleTimeout: 600000
    maxLifetime: 1800000

metrics:
  metricsName: prometheus
  host:
  port: 9070
  async: true
  threadCount : 16
  jmxConfig:

##4.2 hmily configuration of bank2

hmily:
  server:
    configMode: local
    appName: bank-transfer-bank2
  #  If the server.configmode is EQ local, the configuration information here will be read
  config:
    appName: bank-transfer-bank2
    serializer: kryo
    contextTransmittalMode: threadLocal
    scheduledThreadMax: 16
    scheduledRecoveryDelay: 60
    scheduledCleanDelay: 60
    scheduledPhyDeletedDelay: 600
    scheduledInitDelay: 30
    recoverDelayTime: 60
    cleanDelayTime: 180
    limit: 200
    retryMax: 10
    bufferSize: 8192
    consumerThreads: 16
    asyncRepository: true
    autoSql: true
    phyDeleted: true
    storeDays: 3
    repository: mysql

repository:
  database:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.161.114:3306/hmily?useSSL=false&autoReconnect=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: gts
    password: mysql
    maxActive: 20
    minIdle: 10
    connectionTimeout: 30000
    idleTimeout: 600000
    maxLifetime: 1800000

metrics:
  metricsName: prometheus
  host:
  port: 9071
  async: true
  threadCount : 16
  jmxConfig:

5. Implementation code

5.1 API

public interface Bank1Service {
	
	@Hmily
	Boolean transfer(String tid,int customerId,int amount);
}

public interface Bank2Service {

	@Hmily
	Boolean transfer(String tid,int customerId,int amount);
}

5.2 bank1 implementation

Bank1ServiceImpl

@DubboService(version = "1.0.0", tag = "red", weight = 100)
public class Bank1ServiceImpl implements Bank1Service {
	
	@Autowired
	BankAccountService bankAccountService;

	@Override
	public Boolean transfer(String tid,int customerId, int amount) {
		this.bankAccountService.subtractAccountBalance(tid,customerId,amount);
		return true;
	}
}

Bank1AccountServiceImpl

@Slf4j
@Component
public class BankAccountServiceImpl implements BankAccountService {

	private static AtomicInteger confrimCount = new AtomicInteger(0);

	@Autowired
	BankAccountDao bankAccountDao;

	@Autowired
	BankFreezeDao bankFreezeDao;

	@Autowired
	TryLogDao tryLogDao;

	@Autowired
	ConfirmLogDao confirmLogDao;

	@Autowired
	CancelLogDao cancelLogDao;

	@DubboReference(version = "1.0.0")
	Bank2Service bank2Service;

	@Override
	@HmilyTCC(confirmMethod = "confirm", cancelMethod = "cancel")
	public void subtractAccountBalance(String tid, int customerId, int amount) {
		log.info("bank1 subtractAccountBalance try begin ... tid is {} customerId is {} amount is {} !!!", tid, customerId, amount);
		//Idempotent judgment t_ try_ Is there a try log record in the log table? If so, it will not be executed again
		if (tryLogDao.isExist(tid)) {
			log.info("bank1 In, tid by {} of try The operation was performed. Exit directly..", tid);
			return;
		}
		//Suspension processing: if one of cancel and confirm has been executed, try will not be executed again
		if (confirmLogDao.isExist(tid) || cancelLogDao.isExist(tid)) {
			log.info("bank1 In, tid by {} of cancel perhaps confirm The operation was performed. Exit directly..", tid);
			return;
		}
		//Freezing deduction amount
		if (bankAccountDao.subtractAccountBalance(customerId, 1, amount)) {
			log.info("bank1 Account {} Deduction amount {} success tid is {} !!!", customerId, amount, tid);
			//Insert try operation log
			tryLogDao.addTry(tid);
			log.info("bank1 Insert in try log ...");
		} else {
			throw new HmilyRuntimeException("Account deduction exception!");
		}
		//Call bank2 to initiate transfer
		try {
			bank2Service.transfer(tid,customerId, amount);
		} catch (Exception e) {
			throw new HmilyRuntimeException("Remote call exception!");
		}
		log.info("bank1 subtractAccountBalance try end ... tid is {} customerId is {} amount is {} !!!", tid, customerId, amount);
	}

	@Override
	public void addAccountBalance(String tid, int customerId, int amount) {
		return;
	}


	@Transactional(rollbackFor = Exception.class)
	public boolean confirm(String tid, int customerId, int amount) {
		log.info("bank1 confirm begin ... tid is {} customerId is {} amount is {} !!!", tid, customerId, amount);
		//Idempotent check: if the confirm operation is not executed, the frozen amount can be removed; otherwise, nothing will be done..
		if (!confirmLogDao.isExist(tid)) {
			//confirm is allowed only after the try operation is completed and the cancel operation is not executed
			if (tryLogDao.isExist(tid) && !cancelLogDao.isExist(tid)) {
				//Unfreezing amount
				log.info("bank1 confirm In operation, unfreezing amount succeeded!");
				bankFreezeDao.subtractFreezeAmount(customerId, 1, amount);
				//Write confirm log
				log.info("bank1 confirm In operation, add confirm journal!");
				confirmLogDao.addConfirm(tid);
			}
		}
		log.info("bank1 confirm end ... tid is {} customerId is {} amount is {} !!!", tid, customerId, amount);
		return Boolean.TRUE;
	}

	@Transactional(rollbackFor = Exception.class)
	public boolean cancel(String tid, int customerId, int amount) {
		log.info("bank1 cancel begin ... tid is {} customerId is {} amount is {} !!!", tid, customerId, amount);
		//Idempotent check can be cancelled only when the cancel operation is not executed, otherwise nothing will be done.
		if (!cancelLogDao.isExist(tid)) {
			//Empty rollback operation. If the try operation is not executed, cancel will do nothing. If and only after the try operation is executed, cancel can be executed
			if (tryLogDao.isExist(tid)) {
				//To cancel, you need to determine whether confirm has been executed
				//If confirm has not been executed at this time, you need to clear the frozen amount
				if (!confirmLogDao.isExist(tid)) {
					log.info("bank1 cancel In operation, unfreezing amount succeeded!");
					bankFreezeDao.subtractFreezeAmount(customerId, 1, amount);
				}
				log.info("bank1  cancel During the operation, the account balance is increased successfully!");
				bankAccountDao.addAccountBalance(customerId, 1, amount);
				//Add cancel log
				cancelLogDao.addCancel(tid);
			}
		}
		log.info("bank1 cancel end ... tid is {} customerId is {} amount is {} !!!", tid, customerId, amount);
		return Boolean.TRUE;
	}
}

Initiator for Bank1

@SpringBootApplication(scanBasePackages = {"com.dhb.bank.transfer.orm.dao", "com.dhb.bank.transfer.bank1"})
@MapperScan(basePackages = {"com.dhb.bank.transfer.orm.mapper"})
public class DubboServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(DubboServerApplication.class, args);
	}

}

5.3 bank2 implementation

Bank2ServiceImpl

@DubboService(version = "1.0.0", tag = "red", weight = 100)
public class Bank2ServiceImpl implements Bank2Service {

	@Autowired
	BankAccountService bankAccountService;

	@Override
	public Boolean transfer(String tid,int customerId, int amount) {
		this.bankAccountService.addAccountBalance(tid,customerId,amount);
		return true;
	}
}

BankAccountServiceImpl

@Component
@Slf4j
public class BankAccountServiceImpl implements BankAccountService {


	@Autowired
	BankAccountDao bankAccountDao;
	
	@Autowired
	TryLogDao tryLogDao;

	@Autowired
	ConfirmLogDao confirmLogDao;

	@Autowired
	CancelLogDao cancelLogDao;
	
	@Override
	public void subtractAccountBalance(String tid,int customerId, int amount) {

	}

	@Override
	@HmilyTCC(confirmMethod = "confirm", cancelMethod = "cancel")
	public void addAccountBalance(String tid,int customerId, int amount) {
		log.info("bank2 addAccountBalance try begin ... tid is {} customerId is {} amount is {} !!!", tid, customerId, amount);
		//Idempotent check 
		if(!tryLogDao.isExist(tid)) {
			tryLogDao.addTry(tid);
		}
		log.info("bank2 addAccountBalance try end ... tid is {} customerId is {} amount is {} !!!", tid, customerId, amount);

	}


	@Transactional(rollbackFor = Exception.class)
	public boolean confirm(String tid,int customerId, int amount) {
		log.info("bank2 confirm begin ... tid is {} customerId is {} amount is {} !!!", tid, customerId, amount);
		//Idempotent check and try execution completed
		if(!confirmLogDao.isExist(tid)){
			if(tryLogDao.isExist(tid)) {
				bankAccountDao.addAccountBalance(customerId, 1, amount);
				log.info("account {} Amount Collected  {} success!!!", customerId, amount);
				confirmLogDao.addConfirm(tid);
			}
		}
		log.info("bank2 confirm end ... tid is {} customerId is {} amount is {} !!!", tid, customerId, amount);
		return Boolean.TRUE;
	}

	@Transactional(rollbackFor = Exception.class)
	public boolean cancel(String tid,int customerId, int amount) {
		log.info("bank2 cancel begin ... tid is {} customerId is {} amount is {} !!!", tid, customerId, amount);
		//Idempotent check and try execution completed
		if(!cancelLogDao.isExist(tid)){
			if(tryLogDao.isExist(tid)) {
				bankAccountDao.subtractAccountBalance(customerId, 1, amount);
				cancelLogDao.addCancel(tid);
			}
		}

		log.info("bank2 cancel end ... tid is {} customerId is {} amount is {} !!!", tid, customerId, amount);
		return Boolean.TRUE;
	}

}

Initiator for Bank2

@SpringBootApplication(scanBasePackages = {"com.dhb.bank.transfer.orm.dao", "com.dhb.bank.transfer.bank2"})
@MapperScan(basePackages = {"com.dhb.bank.transfer.orm.mapper"})
public class DubboServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(DubboServerApplication.class, args);
	}

}

6.client connection test

6.1 client code

@SpringBootApplication
@Slf4j
public class BankTransferClientApplication {

	@DubboReference(version = "1.0.0") //, url = "dubbo://127.0.0.1:12345")
	private Bank1Service bank1Service;

	public static void main(String[] args) {
		SpringApplication.run(BankTransferClientApplication.class, args);
	}


	@Bean
	public ApplicationRunner runner() {
		return args -> {
			int customerid = 10000;
			int amount = 500;
			String tid = UUID.randomUUID().toString();
			bank1Service.transfer(tid,customerid,amount);
			log.info("customerid {} transfer amount {} ...",customerid,amount);
		};
	}
}

6.2 test process

First, you need to start a zookeeper locally to ensure that the configuration in this article can be connected.

Start dubbo Starter of bank2 and bank1

Then start the client connection for testing.

Tags: Java Database

Posted on Thu, 14 Oct 2021 01:27:24 -0400 by rivka