潮流印花图案设计网站,php是专门做网站的,网站文章排版,网站开发开题报告范文目录
引言#xff1a;
1. TCC事务模式
2. TCC组成
3. TCC执行流程
3.1 TCC正常执行流程
3.2 TCC失败回滚
4. Confirm/Cancel操作异常
5. TCC 设计原则
5.1 TCC如何做到更好的一致性
5.2 为什么只适合短事务
6. 嵌套的TCC
7. .NET CORE结合DTM实现TCC分布式事务 …目录
引言
1. TCC事务模式
2. TCC组成
3. TCC执行流程
3.1 TCC正常执行流程
3.2 TCC失败回滚
4. Confirm/Cancel操作异常
5. TCC 设计原则
5.1 TCC如何做到更好的一致性
5.2 为什么只适合短事务
6. 嵌套的TCC
7. .NET CORE结合DTM实现TCC分布式事务
7.1 轮子小卖部(Nuget)引入Dtmcli
7.2 生成转账数据库(EF_CORE)
7.3 数据库持久化
7.4 appsettings.json
7.5 Program.cs
7.6 主程序事务API控制器
7.7 用户1转账事务API控制器
7.8 用户2转账事务API控制器
小结 引言
紧接上一期.NET CORE 分布式事务(一) DTM实现二阶段提交(.NET CORE 分布式事务(一) DTM实现二阶段提交-CSDN博客)
1. TCC事务模式
什么是TCCTCC是Try、Confirm、Cancel三个词语的缩写最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。
2. TCC组成
TCC分为3个阶段
Try 阶段尝试执行完成所有业务检查一致性, 预留必须业务资源准隔离性Confirm 阶段如果所有分支的Try都成功了则走到Confirm阶段。Confirm真正执行业务不作任何业务检查只使用 Try 阶段预留的业务资源Cancel 阶段如果所有分支的Try有一个失败了则走到Cancel阶段。Cancel释放 Try 阶段预留的业务资源。
3. TCC执行流程
3.1 TCC正常执行流程 一般情况下时序图中的9个步骤会正常完成整个业务按照预期进行。主程序注册全局事务以及Try尝试事务Api地址、Confirm提交事务Api地址、Cancel回滚事务Api地址。并开始执行Try尝试事务Try中的事务要对资源进行预算以及锁定也就是尝试执行判断资源是否支持提交执行事务。然后进行提交事务。最终完成全局事务。
3.2 TCC失败回滚 当Try尝试事务异常时与上面的正常流程的区别是现在不会调用Confirm提交事务而是调用Cancel回滚事务对Try尝试事务进行的资源锁定进行解锁释放等操作。回退到全局事务开始前。
4. Confirm/Cancel操作异常
假如Confirm/Cancel操作遇见失败会怎么样按照Tcc模式的协议Confirm/Cancel操作是要求最终成功的遇见失败的情况都是由于临时故障或者程序bug。dtm在Confirm/Cancel操作遇见失败时会不断进行重试直到成功。
为了避免程序bug导致补偿操作一直无法成功建议开发者对全局事务表进行监控发现重试超过3次的事务发出报警由运维人员找开发手动处理。进行人工干预。
5. TCC 设计原则 在设计上TCC主要用于处理一致性要求较高、需要较多灵活性的短事务。
5.1 TCC如何做到更好的一致性
对于我们的 A 跨行转账给 B 的场景如果采用SAGA在正向操作中调余额在补偿操作中反向调整余额那么会出现这种情况如果A扣款成功金额转入B失败最后回滚把A的余额调整为初始值。整个过程中如果A发现自己的余额被扣减了但是收款方B迟迟没有收到资金那么会对A造成非常大的困扰。
上述需求在SAGA中无法解决但是可以通过TCC来解决设计技巧如下
在账户中的 balance 字段之外再引入一个 trading_balance 字段Try 阶段检查账户是否被冻结检查账户余额是否充足没问题后调整 trading_balance 即业务上的冻结资金Confirm 阶段调整 balance 调整 trading_balance 即业务上的解冻资金Cancel 阶段调整 trading_balance 即业务上的解冻资金
这种情况下终端用户 A 就不会看到自己的余额扣减了但是 B 又迟迟收不到资金的情况。
5.2 为什么只适合短事务
TCC 的事务编排放在了应用端上就是事务一共包含多少个分支每个分支的顺序什么样这些信息不会像 SAGA 那样都发送给dtm服务器之后再去调用实际的事务分支。当应用出现 crash 或退出编排信息丢失那么整个全局事务就没有办法往前重试只能够进行回滚。如果全局事务持续时间很长例如一分钟以上那么当应用进行正常的发布升级时也会导致全局事务回滚影响业务。因此 TCC 会更适合短事务。
那么是否可以把TCC的事务编排都保存到服务器保证应用重启也不受到影响呢理论上这种做法是可以解决这个问题的但是存储到服务器会比在应用端更不灵活无法获取到每个分支的中间结果无法做嵌套等等。
考虑到一致性要求较高和短事务是高度相关的一个中间不一致状态持续很长时间的事务自然不能算一致性较好这两者跟“应用灵活编排”也是有较高相关度所以将 TCC 实现为应用端编排而 SAGA 实现为服务端编排。
6. 嵌套的TCC
dtm的Tcc事务模式支持子事务嵌套流程图如下 在这个流程图中Order这个微服务管理了订单相关的数据修改同时还管理了一个嵌套的子事务因此他即扮演了RM的角色也扮演了AP的角色。
7. .NET CORE结合DTM实现TCC分布式事务
还是以跨行转账作为例子给大家详解这种架构。业务场景介绍如下
我们需要跨行从A转给B 30元我们先进行可能失败的转出操作TccUserTry即进行A扣减30元。如果A因余额不足扣减失败那么转账直接失败返回错误如果扣减成功那么进行下一步转入操作因为转入操作没有余额不足的问题可以假定转入操作一定会成功。
7.1 轮子小卖部(Nuget)引入Dtmcli ItemGroupPackageReference IncludeDtmcli Version1.4.0 //ItemGroup
7.2 生成转账数据库(EF_CORE)
数据库模型
//模型
public partial class UserMoney
{public int id { get; set; }public int money { get; set; }public int trading_balance { get; set; }public int balance { get; set; }public int trymoney { get; set; }public string guid { get; set; }
}
DbContext public class DtmDbContext : DbContext{public DtmDbContext() { }public DtmDbContext(DbContextOptionsDtmDbContext options) : base(options) { }public virtual DbSetUserMoney UserMoney { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){optionsBuilder.UseMySql(serverlocalhost;port3307;user idroot;password123;databaseDTM_Test, ServerVersion.Parse(8.0.23-mysql)).UseLoggerFactory(LoggerFactory.Create(option {option.AddConsole();}));}protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.UseCollation(utf8_general_ci).HasCharSet(utf8);modelBuilder.EntityUserMoney(entity {entity.ToTable(UserMoney);});}}
7.3 数据库持久化
CREATE TABLE
IFNOT EXISTS DTM_Test.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;
数据库最终生成 7.4 appsettings.json
{AllowedHosts: *,ConnectionString: serverlocalhost;port3307;user idroot;password123;databasetest,DtmUrl: http://localhost:36789,TransactionUrl: http://localhost:5016,Logging: {LogLevel: {Default: Information,Microsoft.AspNetCore: Warning}}
}7.5 Program.cs // 注册DbContextbuilder.Services.AddDbContextDtmDbContext(options {options.UseMySql(builder.Configuration.GetValuestring(ConnectionString), ServerVersion.Parse(8.0.23-mysql));});// 注册dtmbuilder.Services.AddDtmcli(dtm {dtm.DtmUrl builder.Configuration.GetValuestring(DtmUrl);dtm.DBType mysql;dtm.BarrierTableName dtm_test.barrier;});7.6 主程序事务API控制器
using DTM_EF.Model;
using Dtmcli;
using Microsoft.AspNetCore.Mvc;
using System.Threading;namespace Dtm_TCC.Controllers
{[ApiController][Route([controller])]public class DtmTccController : ControllerBase{private readonly ILoggerDtmTccController _logger;private readonly TccGlobalTransaction _globalTransaction;private readonly IDtmClient _dtmClient;private readonly IConfiguration _configuration;public DtmTccController(ILoggerDtmTccController logger,TccGlobalTransaction globalTransaction,IDtmClient dtmClient,IConfiguration configuration){_logger logger;_globalTransaction globalTransaction;_dtmClient dtmClient;_configuration configuration;}[HttpPost(Name DtmTcc)]public async TaskIActionResult DtmTcc(){var transactionurl _configuration.GetValuestring(TransactionUrl);// 创建CancellationToken用于取消事务CancellationToken cancellationToken new CancellationToken();// 生成全局事务IDvar gid await _dtmClient.GenGid(cancellationToken);UserMoney body new UserMoney() { id 1, trymoney -30, guid string.Empty };UserMoney body2 new UserMoney() { id 2, trymoney 30, guid string.Empty };await _globalTransaction.Excecute(/*gid,*/ async (tcc) {// 用户1 转出30元 第一个参数是try检测及冻结阶段第二个是提交第三个是回滚var res1 await tcc.CallBranch(body,transactionurl /TccUserTry,transactionurl /TccUserConfirm,transactionurl /TccUserCancel, cancellationToken);// 用户2 转入30元var res2 await tcc.CallBranch(body2,transactionurl /TccUser2Try,transactionurl /TccUser2Confirm,transactionurl /TccUser2Cancel, cancellationToken);}, cancellationToken);return Ok(TransResponse.BuildSucceedResponse());}}
}7.7 用户1转账事务API控制器
using DTM_EF;
using DTM_EF.Model;
using Dtmcli;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MySqlConnector;namespace Dtm_TCC.Controllers
{[Route(api/[controller])][ApiController]public class UserController : ControllerBase{private readonly IBranchBarrierFactory _barrierFactory;private readonly ILoggerUserController _Logger;private readonly DtmDbContext _dtmDbContext;public UserController(IBranchBarrierFactory barrierFactory,ILoggerUserController Logger,DtmDbContext dtmDbContext){_barrierFactory barrierFactory;_Logger Logger;_dtmDbContext dtmDbContext;}[HttpPost][Route(/TccUserTry)]public async TaskIActionResult TccUserTry([FromQuery] string gid, [FromQuery] string trans_type, [FromQuery] string branch_id, [FromQuery] string op, [FromBody] UserMoney body){var branchBarrier _barrierFactory.CreateBranchBarrier(Request.Query);var obj TransResponse.BuildFailureResponse();using (MySqlConnection conn new MySqlConnection(serverlocalhost;port3307;user idroot;password123;databaseDTM_Test)){await branchBarrier.Call(conn, async (tx) {//获取用户账户信息var UserMoney _dtmDbContext.SetUserMoney().Where(c c.id body.id ).FirstOrDefault();//判断预算--不够回滚if (UserMoney null || UserMoney!.money body.trymoney 0) obj TransResponse.BuildFailureResponse();else{//修改信息准备提交UserMoney!.balance 1;UserMoney.trading_balance 1;UserMoney.trymoney body.trymoney;UserMoney.guid gid;_dtmDbContext.SaveChanges();obj TransResponse.BuildSucceedResponse();}await Task.CompletedTask;});}_Logger.LogInformation(${gid}--Try成功);return Ok(obj);}[HttpPost][Route(/TccUserConfirm)]public async TaskIActionResult TccUserConfirm([FromQuery] string gid, [FromQuery] string trans_type,[FromQuery] string branch_id, [FromQuery] string op, [FromBody] UserMoney body){var branchBarrier _barrierFactory.CreateBranchBarrier(Request.Query);var obj TransResponse.BuildFailureResponse();using (MySqlConnection conn new MySqlConnection(serverlocalhost;port3307;user idroot;password123;databaseDTM_Test)){await branchBarrier.Call(conn, async (tx) {//获取用户账户信息var UserMoney _dtmDbContext.SetUserMoney().Where(c c.id body.id).FirstOrDefault();if (UserMoney ! null){UserMoney!.balance 0;UserMoney!.trading_balance 0;UserMoney!.money UserMoney!.trymoney;UserMoney!.trymoney 0;UserMoney!.guid string.Empty;_dtmDbContext.SaveChanges();obj TransResponse.BuildSucceedResponse();}//修改信息准备提交 await Task.CompletedTask;});}_Logger.LogInformation(${gid}--Confirm成功);return Ok(obj);}[HttpPost][Route(/TccUserCancel)]public async TaskIActionResult TccUserCancel([FromQuery] string gid, [FromQuery] string trans_type,[FromQuery] string branch_id, [FromQuery] string op, [FromBody] UserMoney body){var branchBarrier _barrierFactory.CreateBranchBarrier(Request.Query);var obj TransResponse.BuildFailureResponse();using (MySqlConnection conn new MySqlConnection(serverlocalhost;port3307;user idroot;password123;databaseDTM_Test)){//操作回滚并解锁await branchBarrier.Call(conn, async (tx) {//获取用户账户信息var UserMoney _dtmDbContext.SetUserMoney().Where(c c.id body.id ).FirstOrDefault();if (UserMoney ! null){UserMoney!.balance 0;UserMoney!.trading_balance 0;UserMoney!.trymoney 0;UserMoney!.guid string.Empty;_dtmDbContext.SaveChanges();obj TransResponse.BuildSucceedResponse();}await Task.CompletedTask;});}_Logger.LogInformation(${gid}--Cancel成功);return Ok(obj);}}
}7.8 用户2转账事务API控制器
using DTM_EF;
using DTM_EF.Model;
using Dtmcli;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MySqlConnector;namespace Dtm_TCC.Controllers
{[Route(api/[controller])][ApiController]public class User2Controller : ControllerBase{private readonly IBranchBarrierFactory _barrierFactory;private readonly ILoggerUser2Controller _Logger;private readonly DtmDbContext _dtmDbContext;public User2Controller(IBranchBarrierFactory barrierFactory,ILoggerUser2Controller Logger,DtmDbContext dtmDbContext){_barrierFactory barrierFactory;_Logger Logger;_dtmDbContext dtmDbContext;}[HttpPost][Route(/TccUser2Try)]public async TaskIActionResult TccUserTry([FromQuery] string gid, [FromQuery] string trans_type, [FromQuery] string branch_id, [FromQuery] string op, [FromBody] UserMoney body){var branchBarrier _barrierFactory.CreateBranchBarrier(Request.Query);var obj TransResponse.BuildFailureResponse();using (MySqlConnection conn new MySqlConnection(serverlocalhost;port3307;user idroot;password123;databaseDTM_Test)){await branchBarrier.Call(conn, async (tx) {//获取用户账户信息var UserMoney _dtmDbContext.SetUserMoney().Where(c c.id body.id).FirstOrDefault();//判断预算--不够回滚if (UserMoney null || UserMoney!.money body.trymoney 0) obj TransResponse.BuildFailureResponse();else{//修改信息准备提交UserMoney!.balance 1;UserMoney.trading_balance 1;UserMoney.trymoney body.trymoney;UserMoney.guid gid;_dtmDbContext.SaveChanges();obj TransResponse.BuildSucceedResponse();}await Task.CompletedTask;});}obj TransResponse.BuildFailureResponse();_Logger.LogInformation(${gid}--Try成功);return Ok(obj);}[HttpPost][Route(/TccUser2Confirm)]public async TaskIActionResult TccUserConfirm([FromQuery] string gid, [FromQuery] string trans_type,[FromQuery] string branch_id, [FromQuery] string op, [FromBody] UserMoney body){var branchBarrier _barrierFactory.CreateBranchBarrier(Request.Query);var obj TransResponse.BuildFailureResponse();using (MySqlConnection conn new MySqlConnection(serverlocalhost;port3307;user idroot;password123;databaseDTM_Test)){await branchBarrier.Call(conn, async (tx) {//获取用户账户信息var UserMoney _dtmDbContext.SetUserMoney().Where(c c.id body.id).FirstOrDefault();if (UserMoney ! null){UserMoney!.balance 0;UserMoney!.trading_balance 0;UserMoney!.money UserMoney!.trymoney;UserMoney!.trymoney 0;UserMoney!.guid string.Empty;_dtmDbContext.SaveChanges();obj TransResponse.BuildSucceedResponse();}//修改信息准备提交 await Task.CompletedTask;});}_Logger.LogInformation(${gid}--Confirm成功);return Ok(obj);}[HttpPost][Route(/TccUser2Cancel)]public async TaskIActionResult TccUserCancel([FromQuery] string gid, [FromQuery] string trans_type,[FromQuery] string branch_id, [FromQuery] string op, [FromBody] UserMoney body){var branchBarrier _barrierFactory.CreateBranchBarrier(Request.Query);var obj TransResponse.BuildFailureResponse();using (MySqlConnection conn new MySqlConnection(serverlocalhost;port3307;user idroot;password123;databaseDTM_Test)){//操作回滚并解锁await branchBarrier.Call(conn, async (tx) {//获取用户账户信息var UserMoney _dtmDbContext.SetUserMoney().Where(c c.id body.id ).FirstOrDefault();if (UserMoney ! null){UserMoney!.balance 0;UserMoney!.trading_balance 0;UserMoney!.trymoney 0;UserMoney!.guid string.Empty;_dtmDbContext.SaveChanges();obj TransResponse.BuildSucceedResponse();}await Task.CompletedTask;});}_Logger.LogInformation(${gid}--Cancel成功);return Ok(obj);}}
}小结
本文给出了一个完整的 TCC 事务方案是一个可以实际运行的 TCC您只需要在这个示例的基础上进行简单修改就能够用于解决您的真实问题