Golang基于DTM的分布式事务TCC实战

Golang基于DTM的分布式事务SAGA实战-CSDN博客

源代码:https://github.com/Ssummer520/dtm-gin

代码在宿主机运行 docker network:bridge

docker安装,安装成功后可以访问http://localhost:36789/ 打开dtm事务web-ui

复制代码
docker run -itd  --name dtm -p 36789:36789 -p 36790:36790  yedf/dtm:latest

部署mysql

基于docker部署

复制代码
  docker run -d \
  --name mysql-latest \
  -e MYSQL_ROOT_PASSWORD=sa123456 \
  -e MYSQL_USER=sa \
  -e MYSQL_PASSWORD=sa123456 \
  -e MYSQL_DATABASE=test\
  -p 3306:3306 \
  -v db_data:/var/lib/mysql \
  mysql:latest

dtm子事务屏障

异常与子事务屏障 | DTM开源项目文档

准备 RM 数据表

子事务依赖屏障表

子事务屏障技术依赖本地数据库中创建子事务屏障相关的表(barrier),在源代码的示例中默认的数据库和表分别为(部署依赖的数据库

库:dtm_barrier

表:barrier(子事务屏障依赖表)

我们创建上面的库表,如果需要自定义库表名称

sql 复制代码
create database if not exists dtm_barrier
/*!40100 DEFAULT CHARACTER SET utf8mb4 */
;
drop table if exists dtm_barrier.barrier;
create table if not exists dtm_barrier.barrier(
  id bigint(22) PRIMARY KEY AUTO_INCREMENT,
  trans_type varchar(45) default '',
  gid varchar(128) default '',
  branch_id varchar(128) default '',
  op varchar(45) default '',
  barrier_id varchar(45) default '',
  reason varchar(45) default '' comment 'the branch type who insert this record',
  create_time datetime DEFAULT now(),
  update_time datetime DEFAULT now(),
  key(create_time),
  key(update_time),
  UNIQUE key(gid, branch_id, op, barrier_id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
转账微服务依赖库表

库:busi

表:user_account

sql 复制代码
CREATE DATABASE if not exists dtm_busi
/*!40100 DEFAULT CHARACTER SET utf8mb4 */
;
drop table if exists dtm_busi.user_account;
create table if not exists dtm_busi.user_account(
  id int(11) PRIMARY KEY AUTO_INCREMENT,
  user_id int(11) UNIQUE,
  balance DECIMAL(10, 2) not null default '0',
  trading_balance DECIMAL(10, 2) not null default '0',
  create_time datetime DEFAULT now(),
  update_time datetime DEFAULT now(),
  key(create_time),
  key(update_time)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
insert into dtm_busi.user_account (user_id, balance)
values (1, 10000),
  (2, 10000) on DUPLICATE KEY
UPDATE balance =
values (balance);

ddl和数据

sql 复制代码
/*
 Navicat Premium Dump SQL

 Source Server         : localhost_3306
 Source Server Type    : MySQL
 Source Server Version : 80039 (8.0.39)
 Source Host           : localhost:3306
 Source Schema         : test

 Target Server Type    : MySQL
 Target Server Version : 80039 (8.0.39)
 File Encoding         : 65001

 Date: 17/08/2024 10:44:02
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user_account
-- ----------------------------
DROP TABLE IF EXISTS `user_account`;
CREATE TABLE `user_account` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL,
  `balance` decimal(10,2) NOT NULL DEFAULT '0.00',
  `trading_balance` decimal(10,2) NOT NULL DEFAULT '0.00',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of user_account
-- ----------------------------
BEGIN;
INSERT INTO `user_account` (`id`, `user_id`, `balance`, `trading_balance`, `create_time`, `update_time`) VALUES (1, 1000, 11051.00, 0.00, '2024-08-16 09:19:44', '2024-08-16 13:52:11');
INSERT INTO `user_account` (`id`, `user_id`, `balance`, `trading_balance`, `create_time`, `update_time`) VALUES (2, 1001, 11051.00, 0.00, '2024-08-16 09:20:13', '2024-08-16 13:52:11');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
允许空回滚

如图我们模拟丢包的问题,让try阶段没有进行依赖于 gin框架引入timeout中间件进行控制

复制代码
func timeoutMiddleware(duration time.Duration) gin.HandlerFunc {
	return func(c *gin.Context) {
		ctx, cancel := context.WithTimeout(c.Request.Context(), duration)
		defer cancel()

		c.Request = c.Request.WithContext(ctx)

		done := make(chan struct{})
		go func() {
			c.Next()
			close(done)
		}()

		select {
		case <-ctx.Done():
			c.JSON(http.StatusGatewayTimeout, gin.H{"error": "request timed out"})
			c.Abort()
		case <-done:
		}
	}
}

我们在代码里面sleep3秒中http自定义配置会引起超时timeout

结果:

dtm一阶段timeout

rm1 因我们sleep之后直接进行了return

rm2 try之后触发cancel

数据库金额未发生变化 只看到第二条数据update_time发生的变更

防悬挂控制

我们在上面基础上取掉return 通过timeout中间件触发timeout

dtm触发timeout

rm1触发timeout之后紧接着触发cancel

rm2正常进行tcc流程rm1失败立即回滚

结果

幂等

幂等我们在dtm多次传入相同的全局事务id 操作只有一次会成功

相关推荐
初次攀爬者4 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
花酒锄作田5 天前
Gin 框架中的规范响应格式设计与实现
golang·gin
断手当码农6 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
qwfys2006 天前
How to install golang 1.26.0 to Ubuntu 24.04
ubuntu·golang·install
初次攀爬者6 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀6 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式
Asher05096 天前
Hadoop核心技术与实战指南
大数据·hadoop·分布式
codeejun6 天前
每日一Go-25、Go语言进阶:深入并发模式1
开发语言·后端·golang
凉凉的知识库6 天前
Go中的零值与空值,你搞懂了么?
分布式·面试·go
石牌桥网管6 天前
Go 泛型(Generics)
服务器·开发语言·golang