.NET微服务系列之Saga分布式事务案例实践

net,服务,系列,saga,分布式,事务,案例,实践 · 浏览次数 : 46

小编点评

## 代码生成内容排版指南 以下代码需要带简单的排版,以便生成内容时带简单的排版。 **代码示例:** ```csharp using Microsoft.AspNetCore.Mvc; using Saga.Bank.ABC.Models; using System; using Wing.Persistence.Saga; namespace Saga.Bank.ABC.Controllers { public class TransferAccountsController : ControllerBase13 { private static bool _result = false; public TransferReceiveController() { } [HttpGet] public string Get() { return $"我是中国工商银行账户,当前账户余额为:{MyAccount.Balance}¥"; } [HttpPost] public bool Post(ReceivedModel model) { if (model.BankNo != MyAccount.BankNo) { throw new Exception("账号不存在!"); } if (!_result) { throw new Exception("跨行转账业务失败!"); } MyAccount.Balance += model.Amount; return true; } } } ``` **代码解析:** * `TransferAccountsController`类定义了一个`Get`和`Post`方法,用于处理跨行转账业务。 * `Get`方法展示了当前账户余额,并使用`MyAccount.Balance``获取账户余额。 * `Post`方法接收了一个`ReceivedModel`对象,并检查`BankNo`是否与`MyAccount.BankNo`相同。如果不同,则抛出异常。 * `Post`方法检查`_result`是否等于`false`,如果不等于`false`,则抛出异常。 * `Post`方法添加了`amount`的金额到`MyAccount`的账户余额,并返回成功。 * `Post`方法使用`ReceivedModel`对象接收参数并设置`BankNo`和`amount`的金额。 **排版指南:** * 在使用任何模板或代码时,应使用简单的排版格式,例如行间距、缩进等。 * 使用缩进和格式化可以使代码更易读。 * 使用模板可以将代码格式化成更易读的格式。 * 可以根据实际情况进行格式化,例如根据模板的格式化要求进行格式化。

正文

        自从Wing正式发布以后,很多童鞋反馈对Saga分布式事务比较感兴趣,今天就跟大家分享一下“跨行转账”的分布式事务实践案例,入门使用教程请自行前往Wing官方文档

假设自己名下有“中国农业银行(ABC)”和“中国工商银行(ICBC)”的账户余额各1万元,现在从“ABC”跨行转账1000元到“ICBC”。对于“ABC”我们创建一个项目名称为

“Saga.Bank.ABC”,“跨行转账”这个动作我们分为两个事务单元来处理:

1、当前账户扣减1000元,定义一个事务单元的数据传输模型(MyAccountUnitModel),一个事务单元的实现类(MyAccountSagaUnit),如果我们定义的事务策略是“向前恢复”,那就只需要实现“Commit”

方法,否则还需要实现 “Cancel”方法,代码如下:

事务单元的数据传输模型(MyAccountUnitModel)

 1 using System;
 2 using Wing.Saga.Client;
 3 
 4 namespace Saga.Bank.ABC.TransferSagaUnits
 5 {
 6     [Serializable]
 7     public class MyAccountUnitModel : UnitModel
 8     {
 9         /// <summary>
10         /// 账号
11         /// </summary>
12         public string BankNo { get; set; }
13 
14         /// <summary>
15         /// 转出金额
16         /// </summary>
17         public double Amount { get; set; }
18     }
19 }

事务单元的实现类(MyAccountSagaUnit)

 1 using Microsoft.AspNetCore.Mvc;
 2 using System.Threading.Tasks;
 3 using Wing.Saga.Client;
 4 
 5 namespace Saga.Bank.ABC.TransferSagaUnits
 6 {
 7     /// <summary>
 8     /// 当前账户操作
 9     /// </summary>
10     public class MyAccountSagaUnit : SagaUnit<MyAccountUnitModel>
11     {
12         public override Task<SagaResult> Cancel(MyAccountUnitModel model, SagaResult previousResult)
13         {
14             MyAccount.Balance += model.Amount;
15             return Task.FromResult(new SagaResult());
16         }
17 
18         public override Task<SagaResult> Commit(MyAccountUnitModel model, SagaResult previousResult)
19         {
20             var result = new SagaResult();
21             if (MyAccount.Balance < model.Amount)
22             {
23                 result.Success = false;
24                 result.Msg = "转账失败,当前账户余额不足!";
25             }
26             MyAccount.Balance -= model.Amount;
27             return Task.FromResult(result);
28         }
29     }
30 }

2、调用收款行“ICBC”的接口,同样,也是定义一个事务单元的数据传输模型(TransferOutUnitModel),一个事务单元的实现类(TransferOutSagaUnit),代码如下:

事务单元的数据传输模型(TransferOutUnitModel)

 1 using System;
 2 using Wing.Saga.Client;
 3 
 4 namespace Saga.Bank.ABC.TransferSagaUnits
 5 {
 6     [Serializable]
 7     public class TransferOutUnitModel : UnitModel
 8     {
 9         /// <summary>
10         /// 收款账号
11         /// </summary>
12         public string BankNo { get; set; }
13 
14         /// <summary>
15         /// 收款行
16         /// </summary>
17         public string BankName { get; set; }
18 
19         /// <summary>
20         /// 接收金额
21         /// </summary>
22         public double Amount { get; set; }
23     }
24 }

事务单元的实现类(TransferOutSagaUnit)

 1 using System.Net.Http;
 2 using System;
 3 using System.Threading.Tasks;
 4 using Wing;
 5 using Wing.Saga.Client;
 6 using Wing.ServiceProvider;
 7 using Newtonsoft.Json;
 8 using Wing.Result;
 9 using System.Text;
10 
11 namespace Saga.Bank.ABC.TransferSagaUnits
12 {
13     /// <summary>
14     /// 账户转出操作
15     /// </summary>
16     public class TransferOutSagaUnit : SagaUnit<TransferOutUnitModel>
17     {
18         private readonly IServiceFactory _serviceFactory = App.GetService<IServiceFactory>();
19         private readonly IHttpClientFactory _httpClientFactory = App.GetService<IHttpClientFactory>();
20 
21         public override Task<SagaResult> Cancel(TransferOutUnitModel model, SagaResult previousResult)
22         {
23             throw new NotImplementedException();
24         }
25 
26         public override Task<SagaResult> Commit(TransferOutUnitModel model, SagaResult previousResult)
27         {
28             return _serviceFactory.InvokeAsync("Saga.Bank.ICBC", async serviceAddr =>
29             {
30                 var client = _httpClientFactory.CreateClient();
31                 client.BaseAddress = new Uri(serviceAddr.ToString());
32                 var response = await client.PostAsync("/TransferReceive", new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"));
33                 var sagaResult = new SagaResult();
34                 if (response.IsSuccessStatusCode)
35                 {
36                     var apiStrResult = await response.Content.ReadAsStringAsync();
37                     var apiResult = JsonConvert.DeserializeObject<ApiResult<bool>>(apiStrResult);
38                     if (apiResult.Code == ResultType.Success)
39                     {
40                         sagaResult.Success = apiResult.Data;
41                     }
42                     else
43                     {
44                         sagaResult.Success = false;
45                     }
46                     sagaResult.Msg = apiResult.Msg;
47                 }
48                 else
49                 {
50                     sagaResult.Success= false;
51                     sagaResult.Msg = $"调用工商银行接口失败,http状态码:{(int)response.StatusCode}";
52                 }
53                 return sagaResult;
54             });
55         }
56     }
57 }

以上两个事务单元将组成一个完整的“跨行转账”事务,代码如下:

 1 using Microsoft.AspNetCore.Mvc;
 2 using Microsoft.AspNetCore.Routing;
 3 using Saga.Bank.ABC.TransferSagaUnits;
 4 using System;
 5 using Wing.Persistence.Saga;
 6 using Wing.Saga.Client;
 7 
 8 namespace Saga.Bank.ABC.Controllers
 9 {
10     /// <summary>
11     /// 转账
12     /// </summary>
13     [ApiController]
14     [Route("[controller]")]
15     public class TransferAccountsController : ControllerBase
16     {
17         public TransferAccountsController()
18         {
19         }
20 
21         /// <summary>
22         /// 当前账户余额
23         /// </summary>
24         /// <returns></returns>
25         public string Get()
26         {
27             return $"我是中国农业银行账户,当前账户余额为:{MyAccount.Balance}¥";
28         }
29 
30 
31         [HttpGet("{amount}")]
32         public bool Get(double amount)
33         {
34             if (amount <= 0)
35             {
36                 throw new Exception("转账金额必须大于0");
37             }
38             var result = Wing.Saga.Client.Saga.Start("跨行转账", new SagaOptions { TranPolicy = TranPolicy.Forward })
39                    .Then(new MyAccountSagaUnit(), new MyAccountUnitModel
40                    {
41                        Name = "当前账户扣减",
42                        BankNo = MyAccount.BankNo,
43                        Amount = 1000
44                    })
45                   .Then(new TransferOutSagaUnit(), new TransferOutUnitModel
46                   {
47                       Name = "调用收款行接口",
48                       BankNo = "987654321",
49                       Amount = 1000,
50                       BankName = "中国工商银行"
51                   })
52                   .End();
53             if (!result.Success)
54             {
55                 throw new Exception(result.Msg);
56             }
57             return result.Success;
58         }
59     }
60 }

对于“ICBC”,我们创建一个项目名称为“Saga.Bank.ICBC”,它的职责很简单,就是增加收款账号的转账金额,代码如下:

 1 using Microsoft.AspNetCore.Mvc;
 2 using Saga.Bank.ICBC.Models;
 3 using System;
 4 
 5 namespace Saga.Bank.ICBC.Controllers
 6 {
 7     /// <summary>
 8     /// 转账
 9     /// </summary>
10     [ApiController]
11     [Route("[controller]")]
12     public class TransferReceiveController : ControllerBase
13     {
14         private static bool _result = false;
15         public TransferReceiveController()
16         {
17         }
18 
19         /// <summary>
20         /// 当前账户余额
21         /// </summary>
22         /// <returns></returns>
23         public string Get()
24         {
25             return $"我是中国工商银行账户,当前账户余额为:{MyAccount.Balance}¥";
26         }
27 
28         /// <summary>
29         /// 手动控制跨行转账收款是否成功,测试需要
30         /// </summary>
31         /// <param name="result"></param>
32         /// <returns></returns>\
33         [HttpGet("{result}")]
34         public bool Get(int result)
35         {
36             _result = result == 1; 
37             return _result;
38         }
39 
40         /// <summary>
41         /// 跨行转账收款动作
42         /// </summary>
43         /// <param name="model"></param>
44         /// <returns></returns>
45         /// <exception cref="Exception"></exception>
46         [HttpPost]
47         public bool Post(ReceivedModel model)
48         {
49             if (model.BankNo != MyAccount.BankNo)
50             {
51                 throw new Exception("账号不存在!");
52             }
53             if (!_result)
54             {
55                 throw new Exception("跨行转账业务失败!");
56             }
57             MyAccount.Balance += model.Amount;
58             return true;
59         }
60     }
61 }

 启动“Saga.Bank.ICBC”项目,可以看到当前账户余额为10000元,如下图:

启动“Saga.Bank.ABC”项目,可以看到当前账户余额也是为10000元,如下图:

 启动Saga协调服务“Saga.Bank.Server”,启动“Wing.UI”示例1.3,  我们调用农业银行跨行转账接口 http://localhost:9110/TransferAccounts/1000,这时我们可以看到“ABC”的余额为

9000元,“ICBC”的余额还是10000元,因为“ICBC”自身业务操作处理失败,如下图所示:

 

 

我们把“Saga.Bank.ICBC”的收款接口处理结果改为“成功”(调用接口 http://localhost:9111/TransferReceive/1),1分钟左右,我们重新查看“ICBC”的账户余额为11000元,“跨行转账”事务也处理完成了,如下图:

 

 

 代码完整示例下载地址:https://gitee.com/linguicheng/wing-demo

与.NET微服务系列之Saga分布式事务案例实践相似的内容:

.NET微服务系列之Saga分布式事务案例实践

自从Wing正式发布以后,很多童鞋反馈对Saga分布式事务比较感兴趣,今天就跟大家分享一下“跨行转账”的分布式事务实践案例,入门使用教程请自行前往Wing官方文档。 假设自己名下有“中国农业银行(ABC)”和“中国工商银行(ICBC)”的账户余额各1万元,现在从“ABC”跨行转账1000元到“ICB

Asp .Net Core 系列:集成 CAP + RabbitMQ + MySQL(含幂等性)

简介 官网:https://cap.dotnetcore.xyz/ CAP 是什么? 是一个 EventBus,同时也是一个在微服务或者 SOA 系统中解决分布式事务问题的一个框架。它有助于创建可扩展,可靠并且易于更改的微服务系统。 什么是 EventBus? 事件总线是一种机制,它允许不同的组件彼

OData WebAPI实践-与ABP vNext集成

本文属于 OData 系列文章 ABP 是一个流行的 ASP. NET 开发框架,旧版的的 ABP 已经能够非常好的支持了 OData ,并提供了对应的 OData 包。 ABP vNext 是一个重新设计的,面向微服务的框架,提供了一些非常有用的特性,包括分页查询等但是它并不能原生支持 OData

记一次 .NET某酒店后台服务 卡死分析

一:背景 1. 讲故事 停了一个月没有更新文章了,主要是忙于写 C#内功修炼系列的PPT,现在基本上接近尾声,可以回头继续更新这段时间分析dump的一些事故报告,有朋友微信上找到我,说他们的系统出现了大量的http超时,程序不响应处理了,让我帮忙看下怎么回事,dump也抓到了。 二:WinDbg分析

.NET微服务系统迁移至.NET6.0的故事

本次迁移涉及的是公司内部一个业务子系统,该系统是一个多样化的应用,支撑着公司的多个业务方向。目前,该系统由40多个基于.NET的微服务应用构成,使用数千个CPU核心和数TB内存,在数百个Linux容器中运行。每天,该系统需要处理数十亿次请求。 该系统其中大部分服务是在2018-2019年左右由老旧.

.NET周报 【2月第4期 2023-02-25】

国内文章 .NET微服务系统迁移至.NET6.0的故事 https://www.cnblogs.com/InCerry/p/microservice-migration-net-6.html 本次迁移涉及的是公司内部一个业务子系统,该系统是一个多样化的应用,支撑着公司的多个业务方向。目前,该系统由4

.NET无侵入自动化探针原理和主流实现

## 前言 最近,我在微信公众号和博客园分享了一篇关于[.NET微服务系统迁移至.NET 6.0的故事](https://www.cnblogs.com/InCerry/p/microservice-migration-net-6.html)的文章,引起了许多读者的关注。其中,许多人对基于 Open

[转帖]10 张图搞懂服务注册发现机制

http://blog.itpub.net/70024420/viewspace-2926779/ 在微服务架构或分布式环境下,服务注册与发现技术不可或缺,这也是程序员进阶之路必须要掌握的核心技术之一,本文通过图解的方式带领大家轻轻松松掌握。 引入服务注册与发现组件的原因 先来看一个问题,假如现在我

[转帖]微服务并不是银弹

https://www.oschina.net/translate/microservices-not-a-silver-bullet?print 让我们看一个公司/客户与前端框架技术团队之间的典型对话。 这是你从公司/客户那里听到的,技术团队因此而疯狂。当整个世界都从微服务中获益时,为什么不应该采

[转帖]基于腾讯云微服务引擎(TSE) ,轻松实现云上全链路灰度发布

https://my.oschina.net/u/4587289/blog/8570699 1. 概述 软件开发过程中,应用发布非常频繁,通常情况下,开发或运维人员会将系统里所有服务同时上线,使得所有用户都使用上新版本。这样的操作时常会导致发布失败,或因发布前修改代码,线上出现 Bug。 假设一个在