六、net6分布式事务(本地消息表+TCC+Saga)

什么是分布式事务?

  分布式事务我们可以理解为多个服务同时访问多个数据源的事务处理机制,严谨地说,它更应该被称为“在分布式服务环境下的事务处理机制,我们首先来了解cap是什么

  c(数据一致性):代表任何时刻,任何节点中看到的数据都是对的,没有矛盾的;

  a(可用性):代表系统能不断提供服务;

  p(分区容忍性):指分布式系统如果某个服务不可用,也不影响整个系统正确的提供服务;

cap用例场景:

  1.(账户服务)客户扣款100,去购买产品;

  2.(商家服务)商家增加100的款;

  3.(库存服务)库存减一;

 

  假设,客户扣款后我们应该考虑的是如何通知其他账户服务1-N的数据是一致的呢?那么我们可能面临几个问题

  1.如果账户服务金额,无法及时同步到其他账户服务节点;而此时用户又刚好在没有同步的节点上下单,从而导致了错误的数据;这就是一致性问题;

  2.如果因为同步金额到1-N的账户服务节点过程中,中断整个账户服务导致无法下单;这就是可用性问题;

  3.如果账户服务因为网络或者节点问题,导致的无法同步到其中一个或多个节点,那我们要考虑能否接受由于某个节点中断服务,而影响集群中的数据正确性;这就是分区容忍性;

在实际场景中,我们来看看应该怎么来权衡CAP

  如果放弃分区容错性P

  那么我们的服务永远都是可用的,但这基本是不可能的事情;

  如果放弃可用性A

  那么我们有分区或者节点不可用时,就必须等待到节点可用为止;这个过程会非常的漫长;

  如果放弃了一致性C

  那么一旦发生分区,节点之间的数据可能不一致。

  那我们应该如何抉择呢?

  目前我们会有哪些解决方案呢?

  1.强一致性

  目前使用强一致性的有2pc和3pc,他们是XA 协议,X/Open XA(XA 是 eXtended Architecture 的缩写)事务处理框架;通过准备阶段通知节点,然后再进行提交的方式进行数据持久化。但他相应的会有很多问题,例如单点问题,协调者宕机,会让整个事务卡住,性能问题,当网络不稳定时还会出现数据一致性问题。

  2.最终一致性

  指的是数据在一段时间内没有被另外的操作所更改,那它最终将会达到与强一致性过程相同的结果。

一、可靠事件队列(本地消息表)

  意思就是系统会把最有可能出错的业务,以本地事务的方式完成后,通过不断重试的方式(不限于消息系统)来促使同个事务的其他关联业务完成;

  优点:高效率

  缺点:没隔离性

  本章内容我们介绍net6+cap组件来实现可靠事件队列

  原理

  1.首先通过入口服务通过Publish发布消息绑定队列name,发布消息成功后,会存在2张表一张是cap.published(发布表)一张是接受表(cap.received)

  2.转达到相应的服务,通过CapSubscribe("order.ctreatUser")的aop方法来接受传递,并同时建立cap.published(发布表)一张是接受表(cap.received)

  3.如果网络抖动等原因,cap会自行重试,也可自定义设置重试规则

  4.如果重试无效可接入人工操作,引入DotNetCore.CAP.Dashboard包,然后通过访问域名+/cap即可访问

  依赖,我用的是rabbitmq和mysql,而cap.mysql目前只支持ef,dapper两种orm

DotNetCore.CAP
DotNetCore.CAP.RabbitMQ
DotNetCore.CAP.MySql
Dapper

  配置

复制代码
 1 var appDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? String.Empty;
 2 var cfgPath = Path.Combine(appDir, "appsettings.json");
 3 IConfigurationRoot? config = new ConfigurationBuilder()
 4     .AddJsonFile(cfgPath)
 5     .Build();
 6 builder.Services.AddCap(x =>
 7 {
 8     x.UseMySql(config.GetSection("Database:ConnectionString").Value);
 9     x.UseRabbitMQ(mq => {
10         mq.HostName = "119.29.92.184";//地址
11         mq.Port = 5672;//端口
12         mq.UserName = "admin";//账号
13         mq.Password = "xianrunwei";//密码
14         mq.VirtualHost = "/";//虚拟主机
15     });
16     x.FailedRetryCount = 10;//重试次数
17     x.FailedRetryInterval = 20;//多久重试一次,以秒为单位
18 
19     x.FailedThresholdCallback = failed => {
20         //写入失败发起通知
21     };
22 });
Program.cs注入cap
复制代码
复制代码
 1 public IEnumerable<string> Get()
 2         {
 3             
 4             System.Random r = new Random();
 5             PayTime pt = new PayTime();
 6             pt.Id = r.Next(1, 10000);
 7             pt.Title = "test"+ pt.Id;
 8             pt.ctreattime=DateTime.Now;
 9             
10             UserModel um=new UserModel();
11             um.Id = r.Next(1, 10000);
12             um.Name = "username"+ um.Id;
13             um.ctreattime = DateTime.Now;
14             um.des = "des";
15             DapperHelper dh = new DapperHelper();
16             dh.BeginTransaction(() => {
17                 IDictionary<string, string> dicHeader = new Dictionary<string, string>();
18                 dicHeader.Add("token", "123");
19                 capPublisher.Publish<PayTime>("order.payment", pt, dicHeader);
20                 IDictionary<string, string> dicHeaders = new Dictionary<string, string>();
21                 dicHeaders.Add("token", "123");
22                 capPublisher.Publish<UserModel>("order.ctreatUser", um, dicHeaders);
23             }, capPublisher);
24             return new string[] { "ok" };
25         }
发布
复制代码
复制代码
 1 [NonAction]
 2         [CapSubscribe("order.payment")]
 3         public ActionResult<PayTime> payment(PayTime pt,[FromCap] CapHeader header)
 4         {
 5             var token=header["token"];
 6             DapperHelper dh=new DapperHelper();
 7             dh.BeginTransaction(() => {
 8                 dh.Execute($"insert into payTime values({pt.Id},'{pt.Title}','{pt.ctreattime}','{token}')");
 9             }, capPublisher);
10             return Content("ok");
11         }
接收
复制代码

 二、TCC分布式事务

  TCC分布式事务是业务入侵性和隔离性较强的分布式事务,要求业务处理过程必须拆分“预留业务资源”和“确认/释放资源”。

  Try:尝试执行阶段,完成所需检查的业务,并保留(冻结)相应业务资源(保障隔离性)。

  Confirm:确认执行阶段,不进行任何业务检查,直接使用 Try 阶段准备的资源来完成业务处理。注意,Confirm 阶段可能会重复执行,因此需要满足幂等性。

  Cancel:取消执行阶段,释放 Try 阶段预留的业务资源。注意,Cancel 阶段也可能会重复执行,因此也需要满足幂等性。

  场景:系统A的用户,转账给系统B的用户;

  TCC的优点:

  隔离性强

  缺点:

  通常只适合短事务(服务分支少)

  目前市面上兼容net的tcc分布式事务有:dtmcli和servicecomb-pack等,本章选型是dtmcli,没为什么安装和使用简单,而且腾讯也在使用,而servicecomb-pack安装比较麻烦,相对效率来讲dtmcli用go语言开发效率更高

  dtmcli的tcc分布式事务工作流程:这里我理解为有生产者对dtm事务管理器发布一条任务,dtm事务管理器把按顺序记录保存到dtm里的数据库,并且每条记录都是有唯一的id,根据id在提交和取消阶段不断的执行,实现幂等性。

dtm架构图

  整个DTM架构中,一共有三个角色,分别承担了不同的功能 RM-资源管理器:

  RM是一个应用服务,负责管理全局事务中的本地事务,他通常会连接到一个数据库,负责相关数据的修改、提交、回滚、补偿等操作。例如在前面的这个SAGA事务中,步骤2、3中被调用的TransIn,TransOut服务都是RM,业务上负责A、B账户余额的修改

  AP-应用程序:AP是一个应用服务,负责全局事务的编排,他会注册全局事务,注册子事务,调用RM接口。例如在前面的这个SAGA事务中,发起步骤1的是AP,它编排了一个包含TransOut、TransIn的全局事务,然后提交给TM TM-事务管理器:

  TM就是DTM服务,负责全局事务的管理,每个全局事务都注册到TM,每个事务分支也注册到TM。TM会协调所有的RM,将同一个全局事务的不同分支,全部提交或全部回滚。例如在前面的SAGA事务中,TM在步骤2、3中调用了各个RM,在步骤4中,完成这个全局事务

 

   linux下安装dtm,由于dtm使用到系统时间,所以docker安装时需要挂靠linux的时间

1 docker run --name dtm -v /etc/timezone:/etc/timezone:ro -v /etc/localtime:/etc/localtime:ro -p 36789:36789 -p 36790:36790 -e STORE_DRIVER=mysql -e STORE_HOST=175.178.81.27 -e STORE_USER=root -e STORE_PASSWORD=12345 -e STORE_PORT=3306 -e STORE_DB=dtm yedf/dtm:latest   //这是安装
2 docker exec -it dtm sh//进入容器
3 docker logs -f dtm //查看日志
dtm安装说明

  由dtm的架构图可见,我们还需要安装dtm的数据库,我是用mysql,而官网支持的是mango,mysql还有redis;相关安装数据库步骤自行百度吧。

  接下来我们要执行脚本,一共分2个一共是dtm的数据库的脚本,一个是我们服务中执行的脚本

复制代码
 1 CREATE DATABASE IF NOT EXISTS dtm
 2 /*!40100 DEFAULT CHARACTER SET utf8mb4 */
 3 ;
 4 drop table IF EXISTS dtm.trans_global;
 5 CREATE TABLE if not EXISTS dtm.trans_global (
 6   `id` bigint(22) NOT NULL AUTO_INCREMENT,
 7   `gid` varchar(128) NOT NULL COMMENT 'global transaction id',
 8   `trans_type` varchar(45) not null COMMENT 'transaction type: saga | xa | tcc | msg',
 9   `status` varchar(12) NOT NULL COMMENT 'tranaction status: prepared | submitted | aborting | finished | rollbacked',
10   `query_prepared` varchar(1024) NOT NULL COMMENT 'url to check for msg|workflow',
11   `protocol` varchar(45) not null comment 'protocol: http | grpc | json-rpc',
12   `create_time` datetime DEFAULT NULL,
13   `update_time` datetime DEFAULT NULL,
14   `finish_time` datetime DEFAULT NULL,
15   `rollback_time` datetime DEFAULT NULL,
16   `options` varchar(1024) DEFAULT 'options for transaction like: TimeoutToFail, RequestTimeout',
17   `custom_data` varchar(1024) DEFAULT '' COMMENT 'custom data for transaction',
18   `next_cron_interval` int(11) default null comment 'next cron interval. for use of cron job',
19   `next_cron_time` datetime default null comment 'next time to process this trans. for use of cron job',
20   `owner` varchar(128) not null default '' comment 'who is locking this trans',
21   `ext_data` TEXT comment 'extended data for this trans',
22   `rollback_reason` varchar(1024) DEFAULT '' COMMENT 'rollback reason for transaction',
23   PRIMARY KEY (`id`),
24   UNIQUE KEY `gid` (`gid`),
25   key `owner`(`owner`),
26   key `status_next_cron_time` (`status`, `next_cron_time`) comment 'cron job will use this index to query trans'
27 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
28 drop table IF EXISTS dtm.trans_branch_op;
29 CREATE TABLE IF NOT EXISTS dtm.trans_branch_op (
30   `id` bigint(22) NOT NULL AUTO_INCREMENT,
31   `gid` varchar(128) NOT NULL COMMENT 'global transaction id',
32   `url` varchar(1024) NOT NULL COMMENT 'the url of this op',
33   `data` TEXT COMMENT 'request body, depreceated',
34   `bin_data` BLOB COMMENT 'request body',
35   `branch_id` VARCHAR(128) NOT NULL COMMENT 'transaction branch ID',
36   `op` varchar(45) NOT NULL COMMENT 'transaction operation type like: action | compensate | try | confirm | cancel',
37   `status` varchar(45) NOT NULL COMMENT 'transaction op status: prepared | succeed | failed',
38   `finish_time` datetime DEFAULT NULL,
39   `rollback_time` datetime DEFAULT NULL,
40   `create_time` datetime DEFAULT NULL,
41   `update_time` datetime DEFAULT NULL,
42   PRIMARY KEY (`id`),
43   UNIQUE KEY `gid_uniq` (`gid`, `branch_id`, `op`)
44 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
45 drop table IF EXISTS dtm.kv;
46 CREATE TABLE IF NOT EXISTS dtm.kv (
47   `id` bigint(22) NOT NULL AUTO_INCREMENT,
48   `cat` varchar(45) NOT NULL COMMENT 'the category of this data',
49   `k` varchar(128) NOT NULL,
50   `v` TEXT,
51   `version` bigint(22) default 1 COMMENT 'version of the value',
52   create_time datetime default NULL,
53   update_time datetime DEFAULT NULL,
54   PRIMARY KEY (`id`),
55   UNIQUE key `uniq_k`(`cat`, `k`)
56 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
dtm的mysql脚本
复制代码
复制代码
 1 create database if not exists dtm_barrier
 2 /*!40100 DEFAULT CHARACTER SET utf8mb4 */
 3 ;
 4 drop table if exists dtm_barrier.barrier;
 5 create table if not exists dtm_barrier.barrier(
 6   id bigint(22) PRIMARY KEY AUTO_INCREMENT,
 7   trans_type varchar(45) default '',
 8   gid varchar(128) default '',
 9   branch_id varchar(128) default '',
10   op varchar(45) default '',
11   barrier_id varchar(45) default '',
12   reason varchar(45) default '' comment 'the branch type who insert this record',
13   create_time datetime DEFAULT now(),
14   update_time datetime DEFAULT now(),
15   key(create_time),
16   key(update_time),
17   UNIQUE key(gid, branch_id, op, barrier_id)
18 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
RM数据脚本
复制代码

  dtm运行后,会监听两个端口 http:36789 grpc:36790;当然安装完毕后ip+端口即可出现可视化界面

那么接下来我们net6如何使用呢?

  我们先看我们的服务设计框架

  所以首先我们需要对每个服务进行,插件引入

dotnet add package Dtmcli 

  然后对每个服务进行注入

复制代码
builder.Services.AddDtmcli(x =>
{
    x.DtmUrl = "http://175.178.81.27:36789";

});
Program文件注入
复制代码

  接着我们来实现聚合api服务的

复制代码
 1 using Dtmcli;
 2 using Microsoft.AspNetCore.Mvc;
 3 using MQCommon.SagaModel;
 4 
 5 // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
 6 
 7 namespace SagaAggregateServices.Controllers
 8 {
 9     [Route("/api")]
10     [ApiController]
11     public class TccCtreatController : ControllerBase
12     {
13 
14         private readonly TccGlobalTransaction _globalTransaction;
15 
16 
17         public TccCtreatController( TccGlobalTransaction transaction)
18         {
19 
20             _globalTransaction = transaction;
21         }
22         /// <summary>
23         /// TCC 成功提交
24         /// </summary>
25         /// <param name="cancellationToken"></param>
26         /// <returns></returns>
27         [HttpPost("tcc")]
28         public async Task<IActionResult> Tcc()
29         {
30             try
31             {
32                 OrderModel body = new OrderModel();
33                 body.Id = 1;
34                 body.UserId = 1;
35                 body.BusinessID = 1;
36                 body.Money = 1;
37                 body.UserName = "tcc22";
38                 body.BusinessName = "tcc33";
39                 CancellationToken cancellationToken = new CancellationToken();
40                 await _globalTransaction.Excecute(async (tcc) =>
41                 {
42                     // 用户1 转出1元 第一个参数是try检测及冻结阶段,第二个是提交,第三个是回滚
43                     var res1 = await tcc.CallBranch(body,
44                         "http://175.178.81.27:5010/api/TccUserTry",
45                          "http://175.178.81.27:5010/api/TccUserConfirm",
46                          "http://175.178.81.27:5010/api/TccUserCancel", cancellationToken);
47 
48                     // 用户2 转入1元
49                     var res2 = await tcc.CallBranch(body,
50                         "http://119.29.92.184:5010/api/TccBusinessTry",
51                          "http://119.29.92.184:5010/api/TccBusinessConfirm",
52                          "http://119.29.92.184:5010/api/TccBusinessCancel", cancellationToken);
53 
54                 }, cancellationToken);
55 
56                 return Ok(TransResponse.BuildSucceedResponse());
57             }
58             catch (Exception ex)
59             {
60                 return Ok(TransResponse.BuildFailureResponse());
61             }
62         }
63 
64 
65     }
66 }
聚合api的代码
复制代码

  最后是实现每个服务的相关代码

复制代码
  1 using Dtmcli;
  2 using Microsoft.AspNetCore.Http;
  3 using Microsoft.AspNetCore.Mvc;
  4 using MQCommon;
  5 using MQCommon.SagaModel;
  6 using MySqlConnector;
  7 using SagaUser.Model;
  8 
  9 
 10 // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
 11 
 12 namespace SagaUser.Controllers
 13 {
 14     [Route("/api")]
 15     [ApiController]
 16     public class TccUserController : ControllerBase
 17     {
 18         private readonly IBranchBarrierFactory _barrierFactory;
 19 
 20         public TccUserController(IBranchBarrierFactory barrierFactory)
 21         {
 22             _barrierFactory = barrierFactory;
 23         }
 24         [HttpPost("TccUserTry")]
 25         public async Task<IActionResult> TccUserTry([FromQuery] string gid, [FromQuery] string trans_type,
 26             [FromQuery] string branch_id, [FromQuery] string op, [FromBody] OrderModel body)
 27         {
 28 
 29 
 30             var branchBarrier = _barrierFactory.CreateBranchBarrier(Request.Query);
 31             DapperHelper dh = new DapperHelper();
 32             var obj = TransResponse.BuildSucceedResponse();
 33             using (MySqlConnection conn = new MySqlConnection(dh.connectstr()))
 34             {
 35                 await branchBarrier.Call(conn, async (tx) =>
 36                 {
 37                     var UserMoneys = dh.Query<UserMoney>($"select *  from User_Money where id={body.UserId} and balance=0");
 38                     if (UserMoneys.Count > 0)
 39                     {
 40                         dh.Execute($"update User_Money set  balance=1,trading_balance=1 where id={body.UserId}");
 41 
 42                     }
 43                     else
 44                     {
 45                         obj = TransResponse.BuildFailureResponse();
 46                     }
 47                     await Task.CompletedTask;
 48                 });
 49             }
 50 
 51             return Ok(obj);
 52         }
 53 
 54         [HttpPost("TccUserConfirm")]
 55         public async Task<IActionResult> TccUserConfirm([FromQuery] string gid, [FromQuery] string trans_type,
 56            [FromQuery] string branch_id, [FromQuery] string op, [FromBody] OrderModel body)
 57         {
 58 
 59             var branchBarrier = _barrierFactory.CreateBranchBarrier(Request.Query);
 60             DapperHelper dh = new DapperHelper();
 61             var obj = TransResponse.BuildFailureResponse();
 62             using (MySqlConnection conn = new MySqlConnection(dh.connectstr()))
 63             {
 64                 await branchBarrier.Call(conn, async (tx) =>
 65                 {
 66                     var UserMoneys = dh.Query<UserMoney>($"select *  from User_Money where id={body.UserId}");
 67                     var money = UserMoneys[0].money + body.Money;
 68 
 69                     dh.Execute($"insert into User_Accounts values(null,{body.BusinessID},{body.UserId},{body.Money},'koukuan',{UserMoneys[0].money},'{DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss")}')");
 70                     dh.Execute($"update User_Money set money={money},updatetime='{DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss")}',balance=0,trading_balance=0 where id={body.UserId}");
 71                     obj = TransResponse.BuildSucceedResponse();
 72                     await Task.CompletedTask;
 73                 });
 74             }
 75 
 76             return Ok(obj);
 77         }
 78         [HttpPost("TccUserCancel")]
 79         public async Task<IActionResult> TccUserCancel([FromQuery] string gid, [FromQuery] string trans_type,
 80            [FromQuery] string branch_id, [FromQuery] string op, [FromBody] OrderModel body)
 81         {
 82 
 83             var branchBarrier = _barrierFactory.CreateBranchBarrier(Request.Query);
 84 
 85             DapperHelper dh = new DapperHelper();
 86             var obj = TransResponse.BuildFailureResponse();
 87             using (MySqlConnection conn = new MySqlConnection(dh.connectstr()))
 88             {
 89                 await branchBarrier.Call(conn, async (tx) =>
 90                 {
 91                     //这里操作回滚并解锁
 92                     var UserMoneys = dh.Query<UserMoney>($"select *  from User_Money where id={body.UserId}");
 93                     var money = UserMoneys[0].money - body.Money;
 94                     dh.Execute($"insert into User_Accounts values(null,{body.BusinessID},{body.UserId},{body.Money},'huigun',{UserMoneys[0].money},'{DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss")}')");
 95                     dh.Execute($"update User_Money set money={money},updatetime='{DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss")}',balance=0,trading_balance=0 where id={body.UserId}");
 96                     await Task.CompletedTask;
 97                 });
 98             }
 99 
100             return Ok(TransResponse.BuildSucceedResponse());
101         }
102     }
103 }
服务代码
复制代码

  我们直接执行看看效果,如果顺利执行,则状态会是成功的

 

三、SAGA分布式事务

  它是“长时间事务”(Long Lived Transaction)运作效率的方法

  原理

  SAGA 由两部分操作组成

  一部分是由大事务拆分成N个子事务(Ti),另一部分是把每个子事务对应一个事务补偿(Ci);而Ti和Ci具备的条件:

  1.Ti和Ci具有幂等性

  2.Ti 与 Ci 满足交换律(Commutative),即不管是先执行 Ti 还是先执行 Ci,效果都是一样的;

  3.Ci 必须能成功提交,即不考虑 Ci 本身提交失败被回滚的情况,如果出现就必须持续重试直至成功,或者要人工介入。

  如果T1到Tn由于某些原因失败,我们会进行2种恢复策略

  1.正向恢复:如果Ti失败,则一直重试

  2.反向恢复:如果Ti失败,则一直执行 Ci 对 Ti 进行补偿,直至成功为止(最大努力交付)。

  saga事务我们可以用什么事务管理器呢?其实一般支持tcc事务的同样也是支持saga事务

  场景:用户转账给商家,但用户是通过银行转账的,那么就无法冻结资金。这时候就适合saga来做事务处理。

  在net6的实现,我们同样用tcc的架构图,并且安装dtm,接下来我们直接上代码,而对于dtm更细的saga实现方式请自行上官网查阅

复制代码
 1 [HttpPost("SAGA")]
 2         public async Task<IActionResult> SAGA()
 3         {
 4             var cancellationToken = new CancellationToken();
 5             var gid = await _dtmClient.GenGid(cancellationToken);
 6             OrderModel body=new OrderModel();
 7             body.Id = 1;
 8             body.UserId = 1;
 9             body.BusinessID = 1;
10             body.Money = 1;
11             body.UserName = "22";
12             body.BusinessName = "33";
13             //下面add是按照顺序执行,当然也可以设置并行详细请看官网
14             var saga = _transFactory.NewSaga(gid)
15                 .Add("http://175.178.81.27:5010/api/ctreatUser", "http://175.178.81.27:5010/api/ctreatUsererror", body)
16                 .Add("http://119.29.92.184:5010/api/ctreatBusinessAccounts", "http://119.29.92.184:5010/api/ctreatBusinessAccountserror", body)
17                 ;
18 
19             await saga.Submit(cancellationToken);
20             return Ok(TransResponse.BuildSucceedResponse());
21 
22         }
聚合服务api
复制代码
复制代码
 1 using Dtmcli;
 2 using Microsoft.AspNetCore.Mvc;
 3 using MQCommon;
 4 using MQCommon.SagaModel;
 5 using MySqlConnector;
 6 using SagaBusiness.Model;
 7 
 8 // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
 9 
10 namespace SagaBusiness.Controllers
11 {
12     [Route("/api")]
13     [ApiController]
14     public class BusinessAccountsController : ControllerBase
15     {
16         private readonly IBranchBarrierFactory _barrierFactory;
17         public BusinessAccountsController(IBranchBarrierFactory barrierFactory)
18         {
19             _barrierFactory = barrierFactory;
20         }
21         // GET: api/<UserPayController>
22         [HttpPost("ctreatBusinessAccounts")]
23         public async Task<ActionResult> ctreatBusinessAccounts([FromQuery] string gid, [FromQuery] string trans_type,
24             [FromQuery] string branch_id, [FromQuery] string op, [FromBody] OrderModel body)
25         {
26 
27             var branchBarrier = _barrierFactory.CreateBranchBarrier(Request.Query);
28             DapperHelper dh = new DapperHelper();
29             using (var connection = new MySqlConnection(dh.connectstr()))
30             {
31                 await branchBarrier.Call(connection, async (tx) =>
32                 {
33                     var BusinessMoneys = dh.Query<BusinessMoney>($"select *  from Business_money where id={body.BusinessID}");
34                     var money = BusinessMoneys[0].money + body.Money;
35                     dh.Execute($"update Business_money set money={money},updatetime='{DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss")}' where id={body.BusinessID}");
36                     dh.Execute($"insert into Business_Accounts values(null,{body.BusinessID},{body.UserId},{body.Money},'koukuan',{BusinessMoneys[0].money},'{DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss")}')");
37                 });
38             }
39 
40            
41             return Ok(TransResponse.BuildSucceedResponse());
42         }
43         [HttpPost("ctreatUsererror")]
44         public async Task<ActionResult> ctreatBusinessAccountserror([FromQuery] string gid, [FromQuery] string trans_type,
45             [FromQuery] string branch_id, [FromQuery] string op, [FromBody] OrderModel body)
46         {
47             var branchBarrier = _barrierFactory.CreateBranchBarrier(Request.Query);
48             DapperHelper dh = new DapperHelper();
49             await dh.DTMTransactionAsync(() => {
50             //这里是回滚操作,如果设置update操作需要增加字段锁行,操作完才解锁,如果inert可以删除已添加数据
51             }, branchBarrier);
52             return Ok(TransResponse.BuildSucceedResponse());
53         }
54     }
55 }
服务代码
复制代码

  那么我们来看看效果,执行成功则状态是成功的,如果不成功可查看dtm的log报错信息

  我们再来dtm的数据库记录了什么,首先是trans_global记录了我们每次提交的事务记录;而trans_branch_op则是记录子事务

 

 

 

 

总结:

        在工作中经常会听到同事说哪个分布式事务快,哪个分布式事务更好等问题?其实分布式事务的本身效率上就很难去维持高效,事务 越大执行越慢;所以我们需要考虑的是业务场景需要用到什么样的分布式事务,多种分布式事务的出现,并不是为了表现谁更好谁更快的,而是为了解决业务上需要用到的是什么样的事务场景。

posted @   冼润伟  阅读(1441)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示