dotnet add package Dtmcli --version 0.4.0注:相比 0.3.0,0.4.0 支持了 4 个新的特性,详见 https://github.com/dtm-labs/dtmcli-csharp/releases/tag/v0.4.0
app.MapPost("/api/TransOutTry", async (IBranchBarrierFactory bbFactory, HttpContext context, TransRequest req) => { var bb = bbFactory.CreateBranchBarrier(context.Request.Query); using var db = Db.GeConn(); await bb.Call(db, async (tx) => { Console.WriteLine($"用户【{req.UserId}】转出【{req.Amount}】Try 操作,bb={bb}"); // tx 参数是事务,可和本地事务一起提交回滚 // 堆代码 duidaima.com await Task.CompletedTask; }); return Results.Ok(TransResponse.BuildSucceedResponse()); }); app.MapPost("/api/TransOutConfirm", async (IBranchBarrierFactory bbFactory, HttpContext context, TransRequest req) => { var bb = bbFactory.CreateBranchBarrier(context.Request.Query); using var db = Db.GeConn(); await bb.Call(db, async (tx) => { Console.WriteLine($"用户【{req.UserId}】转出【{req.Amount}】Confirm操作,bb={bb}"); await Task.CompletedTask; }); return Results.Ok(TransResponse.BuildSucceedResponse()); }); app.MapPost("/api/TransOutCancel", async (IBranchBarrierFactory bbFactory, HttpContext context, TransRequest req) => { var bb = bbFactory.CreateBranchBarrier(context.Request.Query); using var db = Db.GeConn(); await bb.Call(db, async (tx) => { Console.WriteLine($"用户【{req.UserId}】转出【{req.Amount}】Cancel操作,bb={bb}"); await Task.CompletedTask; }); return Results.Ok(TransResponse.BuildSucceedResponse()); });InApi
app.MapPost("/api/TransInTry", async (IBranchBarrierFactory bbFactory, HttpContext context, TransRequest req) => { var bb = bbFactory.CreateBranchBarrier(context.Request.Query); using var db = Db.GeConn(); await bb.Call(db, async (tx) => { Console.WriteLine($"用户【{req.UserId}】转入【{req.Amount}】Try操作,bb={bb}"); await Task.CompletedTask; }); return Results.Ok(TransResponse.BuildSucceedResponse()); }); app.MapPost("/api/TransInConfirm", async (IBranchBarrierFactory bbFactory, HttpContext context, TransRequest req) => { var bb = bbFactory.CreateBranchBarrier(context.Request.Query); using var db = Db.GeConn(); await bb.Call(db, async (tx) => { Console.WriteLine($"用户【{req.UserId}】转入【{req.Amount}】Confirm操作,bb={bb}"); await Task.CompletedTask; }); return Results.Ok(TransResponse.BuildSucceedResponse()); }); app.MapPost("/api/TransInCancel", async (IBranchBarrierFactory bbFactory, HttpContext context, TransRequest req) => { var bb = bbFactory.CreateBranchBarrier(context.Request.Query); using var db = Db.GeConn(); await bb.Call(db, async (tx) => { Console.WriteLine($"用户【{req.UserId}】转入【{req.Amount}】Cancel操作,bb={bb}"); await Task.CompletedTask; }); return Results.Ok(TransResponse.BuildSucceedResponse()); });到此各个子事务的处理已经OK了,在上面的代码中,下面这几行是子事务屏障相关代码,只要按照这个方式来调用您的业务逻辑,子事务屏障保证重复请求、悬挂、空补偿情况出现时,您的业务逻辑不会被调用,保证了正常业务的正确进行
var bb = bbFactory.CreateBranchBarrier(context.Request.Query); await bb.Call(db, async (tx) => { // 业务操作... });然后准备开启 TCC 事务,进行分支调用
var cts = new CancellationTokenSource(); var gid = await dtmClient.GenGid(cts.Token); var res = await tccGlobalTransaction.Excecute(gid, async (tcc) => { // 用户1 转出30元 var res1 = await tcc.CallBranch(userOutReq, outApi + "/TransOutTry", outApi + "/TransOutConfirm", outApi + "/TransOutCancel", cts.Token); // 用户2 转入30元 var res2 = await tcc.CallBranch(userInReq, inApi + "/TransInTry", inApi + "/TransInConfirm", inApi + "/TransInCancel", cts.Token); Console.WriteLine($"case1, branch-out-res= {res1} branch-in-res= {res2}"); }, cts.Token); Console.WriteLine($"case1, {gid} tcc 提交结果 = {res}");到这里,一个完整的 TCC 分布式事务就编写完成了。
app.MapPost("/api/TransInTryError", (IBranchBarrierFactory bbFactory, HttpContext context, TransRequest req) => { var bb = bbFactory.CreateBranchBarrier(context.Request.Query); Console.WriteLine($"用户【{req.UserId}】转入【{req.Amount}】Try--失败,bb={bb}"); return Results.Ok(TransResponse.BuildFailureResponse()); });再来看一下事务失败交互的时序图
var cts = new CancellationTokenSource(); var gid = await dtmClient.GenGid(cts.Token); var res = await tccGlobalTransaction.Excecute(gid, async (tcc) => { var res1 = await tcc.CallBranch(userOutReq, outApi + "/TransOutTry", outApi + "/TransOutConfirm", outApi + "/TransOutCancel", cts.Token); var res2 = await tcc.CallBranch(userInReq, inApi + "/TransInTryError", inApi + "/TransInConfirm", inApi + "/TransInCancel", cts.Token); Console.WriteLine($"case2, branch-out-res= {res1} branch-in-res= {res2}"); }, cts.Token); Console.WriteLine($"case2, {gid} tcc 提交结果 = {res}");需要注意的是 CallBranch 方法在对应的微服务返回失败后会抛出异常,进而触发全局事务的回滚操作,这个时候 dtm 才会触发 Cancel 的操作。
3.输出的提交结果为空,表明这个事务是失败的,成功的话会返回这个事务的 gid