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(子事务屏障依赖表)

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

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

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和数据

/*
 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 操作只有一次会成功

  • 13
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Go语言基于Redis实现的分布式限流是一种常见的解决方案,可以有效地控制系统的并发访问流量,防止系统被过多的请求压垮。 首先,分布式限流需要使用Redis的计数器功能。通过对每个请求进行计数,并设置一个时间窗口,可以统计在该窗口内的请求次数。当请求次数超过某个阈值时,可以拒绝该请求或者进行降级处理。 其次,为了保证分布式限流的准确性和高效性,需要使用Redis的原子操作,例如INCR、EXPIRE等。INCR命令可以原子地将计数器的值加1,并返回加1后的结果,而EXPIRE命令可以设置计数器的过期时间。通过这些原子操作,可以在多个节点之间共享计数状态,并且保证计数器的同步和高效性。 此外,为了保证系统的稳定性和可靠性,需要考虑设置适当的限流阈值和时间窗口大小。根据系统的负载情况和性能需求,可以调整这些参数,实现对系统流量的合理控制。 在实际应用中,可以使用Go语言的Redis客户端连接Redis服务器,并通过相关命令操作计数器。同时,还可以结合其他的组件和技术,如分布式锁、消息队列等,增强系统的稳定性和可扩展性。 总之,Go语言基于Redis实现的分布式限流是一种可行且有效的解决方案,可以帮助我们应对大流量的并发请求,保证系统的稳定运行。通过合理设定限流参数和灵活运用Redis的功能,我们可以实现流量控制、降级和保护系统免受恶意请求的攻击。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值