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:
module | describe |
---|---|
bank-transfer-api | dubbo API |
bank-transfer-orm | The orm package of the database is automatically generated by fluent |
bank-transfer-bank1 | bank1 service, a service for initiating transfer |
bank-transfer-bank2 | bank2 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/> <!– lookup parent from repository –>--> </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/> <!– lookup parent from repository –>--> </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/> <!– lookup parent from repository –>--> </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/> <!– lookup parent from repository –>--> </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/> <!– lookup parent from repository –>--> </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/> <!– lookup parent from repository –>--> </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.