AspNetCoreMassTransit Courier实现分布式事务

在之前的一篇博文中,CAP框架可以方便我们实现非实时、异步场景下的最终一致性,而有些用例总是无法避免的需要在实时、同步场景下进行,可以借助Saga事务来解决这一困扰。在一些博文和仓库中也搜寻到了.Net下实现Saga模式的解决方案MassTransit,这就省得自己再造轮子了。

分布式事务

分布式系统中,分布式事务是一个不能避免的问题,如何保证不同节点间的数据一致性。举个常见的例子,下订单、减库存、扣余额,三者在单个节点时,可以借助本地事务,实现要么成功要么失败。而当三者处于不同节点时,又参杂了如网络环境、节点自身环境、服务环境等各种因素,使得三个节点想要实现要么成功、要么失败就增加了许多困难。


CAP理论和BASE理论很好的诠释了这一问题,也有了许多的解决分布式事务的方案,如2PC、3PC、TCC、本地消息表、Saga等一系列解决方案,面对不同场景、不同要求等可选择不同的解决方案。


数据一致性容错性复杂性性能维护成本
2PC
3PC
TCC
本地消息表
MQ事务
Saga事务

在之前提到过一个基于本地消息表的CAP框架,借助最终一致性很方便的解决了异步非实时请求下的分布式事务,而对于大部分场景虽然可以直接或者妥协方式使用着异步非实时,如同步实时场景的下订单且减库存变更到异步非实时场景的下订单后发事件减库存,但是总有那么一些场景,不得不去考虑同步实时请求下的分布式事务。

Saga模式

Saga模式又叫做长时间运行事务(Long-running-transaction), 由普林斯顿大学的 Hector Garcia-Molina和Kenneth Salem 1987年发表的论文《Sagas》。核心思想是将长事务拆分为多个本地短事务,通过保证所有短事务的成功或失败来决定整体的成功或失败,由Saga事务协调器协调管理,所有节点执行成功,则成功,如有节点失败,则反向执行前置节点的补偿操作。

  • 每个Saga事务由一系列幂等的有序子事务(sub-transaction) Ti 组成。

  • 每个Ti 都有对应的幂等补偿动作Ci,补偿动作用于撤销Ti造成的结果。

执行过程

当正常执行时,依照T1、T2、T3三个短事务正常执行下去,直到最后一个Tn事务执行完毕,宣告整个事务的成功。


而当执行到某个Tj出现故障时,则反向补偿之前的Tj-1..T1,每个对应的补偿操作Cj-1...C1,其中Tj事务由于在执行阶段就已失败,所以Tj对应的补偿动作Cj不需要执行,即也确定了最后一个Tn事务可以不设置补偿动作Cn。

恢复策略

  • 向前恢复(forward recovery):对于Ti事务的执行,部分场景下可能因为数据库的连接、网络的波动等导致短暂的失败,对Ti事务重试执行,以确保整个事务的执行,如执行T1, T2, T3,当执行T3失败时,不直接宣告失败,对T3执行重试以排除部分不稳定因素,如在若干次重试无效后,再考虑向后恢复。

  • 向后恢复(backward recovery):按照执行顺序方式作为向前的指向,则向后为反向补偿,对已执行过的节点顺序倒退执行各Ti的补偿动作Ci,也就是把走过的路往回走,对执行过的操作执行业务上的反操作,如正向流程执行减库存则补偿操作时执行加库存。

协作方式

对于服务与服务间的协作,我们通常有两种模式:Orchestration(编排式) 和 Choreography(协同式),在Saga模式中也有着这两种的实现。

  • 编排式(Orchestrator):把 Saga 的决策和执行顺序逻辑集中在一个 Saga 编排器类中。Saga 编排器发出命令式消息给各个 Saga 参与方,指示这些参与方服务完成具体操作(本地事务)。

  • 协同式(Choreography):把 Saga 的决策和执行顺序逻辑分布在 Saga 的每个参与方中,它们通过交换事件的方式来进行沟通。

编排式与协同式的差异仅在于服务之间的协作方式,每个参与服务的接口定义却没有任何区别。

编排式(Orchestrator)

编排式的 Saga 需要开发人员定义一个编排器类,用于编排一个Saga中多个参与服务执行的流程。如果整个业务流程正常结束,业务就成功完成,一旦这个过程的任何环节出现失败,Saga编排器类就会以相反的顺序调用补偿操作,重新进行业务回滚。

对于每个参与的服务而言,需要做的事情是

  • 订阅并处理命令消息

  • 执行命令后返回响应消息

  • 设计执行逻辑和补偿逻辑

以提交订单为例,假设场景是分布式系统下,进程间以消息传递进行通信:

1、事务发起方的主业务逻辑请求预先定义好的Saga编排器类(内部编排了执行顺序)。

2、Saga编排器类向MQ发送减库存事件,库存服务订阅事件、执行处理并返回MQ处理结果。

3、Saga编排器类向MQ发送减余额事件,支付服务订阅事件、执行处理并返回MQ处理结果。

4、Saga编排器类向MQ发送创建订单命令,订单服务订阅事件并按照命令创建订单。

5、主业务逻辑接收并处理Saga编排器类处理结果。

6、整个过程由Saga 编排器类对接收到的回复进行判决,来决定是继续执行还是悬崖勒马。

协同式(Choreography)

没有集中式的编排类,而是各参与方间相互订阅,一个服务订阅另一个服务的事件。

先由事务发起方执行逻辑并发布一个事件,该事件被一个或多个服务进行订阅,这些服务执行本地数据库操作并发布(或不发布)新的事件,该部分需要保证本地数据库的操作成功且写入MQ的消息也成功,可考虑使用本地消息表或是基于MQ事务。当最后一个服务执行本地事务并且不发布任何事件或者发布的事件没有被任何Saga参与者订阅意味着事务结束,则整个业务流程的分布式事务完成。如果某一服务出现故障,那么则反向发布事件,执行补偿操作,以此回滚。

以提交订单为例,假设场景是分布式系统下,进程间以消息传递进行通信:

1、事务发起方执行主业务逻辑发送提交订单命令。

2、库存服务订阅事件、扣减库存并发布已扣减事件。

3、订单服务订阅库存已扣减事件,创建订单并发布订单已创建事件。

4、支付服务订阅订单已创建事件,执行支付并发布订单已支付事件。

5、主业务逻辑订阅订单已支付事件并处理。

当某服务内执行时如存在异常,则反向发布事件,如订单创建失败,则发布OrderCreatedFailed事件,库存服务订阅该事件并执行补偿操作。

相比而言,编排式中参与服务无需向协同式中订阅上游服务的事件,减少了服务间对事件协议的依赖,而只需要关心集权的编排器类发送的消息。

MassTransit Courier

MassTransit Courier是一种用于创建和执行带有故障补偿的分布式事务的机制,它可以用于满足本地事务的需求,也可以在分布式系统中实现分布式事务。

Courier实现了Routing Slip模式,通过有序组合一系列的Activity,得到一个Routing slip。每个Activity都有 Execute 和 Compensate 两个方法(最后一个可以只有一个Execute方法)。Compensate即为补偿操作。

补偿服务

当开启一个事务前,需要做一些准备,准备一个事务Id,记录整个事务执行情况,各Tj事务执行情况,当前请求上下文参数,入参参数记录等,以方便执行补偿操作时需要用到。如当Tj事务执行失败时,需要对Cj-1到C1执行补偿操作,此时各补偿操作需要一些正向执行T1,Tj-1的请求参数或执行结果,因此都需要记录下来。


在Courier中,通过Routing Slip来完成这些记录,创建一个Guid,记录请求上下文参数信息,可以绑定几个内置事件,在各阶段到来时会发送事件,如有需要可以订阅。

var builder = new RoutingSlipBuilder(NewId.NextGuid());
builder.AddSubscription(context.ReceiveContext.InputAddress, RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted | RoutingSlipEvents.CompensationFailed);
builder.AddVariable("RequestId", context.RequestId);
builder.AddVariable("ResponseAddress", context.ResponseAddress);
builder.AddVariable("FaultAddress", context.FaultAddress);
builder.AddVariable("Request", context.Message);
//组合一系列Activity
var routingSlip = builder.Build();
await context.Execute(routingSlip).ConfigureAwait(false);

服务建立

弄了个Demo,建立了三个服务,此处我使用编排式来完成,但无论是选用编排式还是协同式,都借助RabbitMQ实现消息传递。

每个服务都安装了MassTransit相关的包

MassTransit.AspNetCore
MassTransit.RabbitMQ

将Saga编排器类放置在OrderService中了,对于编排器类的放置,个人认为是应该看用例的主服务是谁而放置,想过放在BFF去协调三个服务,但是总是感觉不是BFF的职责范围。

服务配置

在各服务中对MassTransit配置,如下在OrderService中对MassTransit需要使用到的RabbitMQ配置,对需要进行多个服务协作的用例配置Routing Slip,对消息队列侦听订阅需要的事件并配置相应的Activity处理。

services.AddMassTransit(x =>
{var currentAssembly = Assembly.GetExecutingAssembly();x.AddActivities(currentAssembly);x.AddConsumers(currentAssembly);x.AddRequestClient<createordercommand>();x.UsingRabbitMq((context, cfg) =>{// 配置RabbitMQcfg.Host(Configuration["RabbitmqConfig:HostIP"], ushort.Parse(Configuration["RabbitmqConfig:HostPort"]), Configuration["RabbitmqConfig:VirtualHost"], h =>{h.Username(Configuration["RabbitmqConfig:Username"]);h.Password(Configuration["RabbitmqConfig:Password"]);});//配置Routing Slipcfg.ReceiveEndpoint("CreateOrderCommand", ep =>{ep.ConfigureConsumer<createorderrequestproxy>(context);ep.ConfigureConsumer<createorderresponseproxy>(context);});// 配置订阅队列及Handler处理cfg.ReceiveEndpoint("CreateOrder_execute", ep =>{ep.ExecuteActivityHost<createorderactivity, createordermodel="">(context);});});
});
services.AddMassTransitHostedService();

服务编排

构建Routing Slip,此处依据用例的需求,对需要协作的服务编排,组合一系列的Activity。

Task BuildRoutingSlip(RoutingSlipBuilder builder, ConsumeContext<createordercommand> request)
{builder.AddActivity("ReduceStock", new Uri("..."), new {});builder.AddActivity("DeductBalance", new Uri("..."), new {});builder.AddActivity("CreateOrder", new Uri("..."), new { });return Task.CompletedTask;
}

执行请求

当请求进入后,通过RequestClient发送CreateOrderCommand,同步等待执行结果,再由编排器类负责协调预设好的Activity,发送事件到消息队列,经各Activity订阅处理最终返回结果。

[Route("[controller]")]
public class OrderController : ControllerBase
{private readonly IRequestClient<createordercommand> _createOrderClient;public OrderController(IRequestClient<createordercommand> createOrderClient){_createOrderClient = createOrderClient;}[HttpGet("CreateOrder")]public async Task<commoncommandresponse<createorderresult>> CreateOrder(){var result = await _createOrderClient.GetResponse
<commoncommandresponse<createorderresult>>(new CreateOrderCommand(){// ...});return result.Message;}
}

各服务中对于Activity设置侦听队列以及请求信息,调用Execute执行逻辑,当出现异常时返回到MQ通知编排器类,在对之前执行的Activity执行Compensate。如在CreateOrderActivity中执行异常,由编排器类执行补偿,ReduceStockActivity调用Compensate,执行增加库存逻辑

public class ReduceStockActivity : IActivity<ReduceStockModel, ReduceStockLog>
{public async Task<ExecutionResult> Execute(ExecuteContext<ReduceStockModel> context){var argument = context.Arguments;// 扣减库存await Task.Delay(100);return context.Completed(new ReduceStockLog() { ProductId = argument.ProductId, Amount = 1 });}public async Task<CompensationResult> Compensate(CompensateContext<ReduceStockLog> context){// 增加库存await Task.Delay(100);return context.Compensated();}
}

执行成功

用例请求执行后,先由Controller发送请求,再由库存服务扣减库存,支付服务扣减余额,最后由订单服务创建订单,当创建失败时,执行补偿操作,库存服务增加库存,支付服务增加余额。

执行补偿

用例请求执行后,先由Controller发送请求,再由库存服务扣减库存,支付服务扣减余额,最后由订单服务创建订单,当创建失败时,执行补偿操作,库存服务增加库存,支付服务增加余额。

在整个事务失败后,先会返回异常,再由编排器执行补偿操作,实现最终的数据一致性。MassTransit也提供了重试机制以实现向前恢复,避免因数据库连接超时、网络波动等问题造成的失败。

Demo

参考

Masstransit中的 Request/Response 与 Courier 功能实现最终一致性 - 丁松松松

理解分布式事务 (juejin.cn)-陈彩华

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/300450.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设置列表字段为主键

转贴:Sample event handler to set a field as a pr imary key (enforce no duplicates) Got this as a request from a reader- how to prevent users from adding items with same titles as ones that already exist in the list. Codeusing System;using System.Collectio…

被学校辞退、拒绝FB后:语音识别大牛Povey确认兼职北京初创公司,称主业还选中国...

全世界只有3.14 % 的人关注了青少年数学之旅由于5月的学生抗议事件&#xff0c;语音识别领域著名学者、原约翰霍普金斯大学教授Daniel Povey被学校辞退。随后&#xff0c;Daniel Povey准备进入Facebook从事语音识别系统的开发&#xff0c;但是由于Facebook要对其进行长达6周的审…

dotnet中的counters说明(二)

上篇说了System.Runtime&#xff0c;它负责应用运行的环境资源的收集&#xff0c;这篇要继续说AspNetCore的Hosting,Http.Connections和Server.Kestrel三个计数器。同时&#xff0c;下面指标各项()里的项目是--counters 参数[]里的项&#xff0c;用逗号分隔多项指标。Microsoft…

有这些好习惯,可以让你悄悄变优秀

全世界只有3.14 %的人关注了青少年数学之旅这是一个普遍无趣的时代&#xff0c;很多人看似忙到起飞内在却空虚迷茫。今天我们为你诚意推荐几个公众号它们会成为你生活的一剂调味料&#xff0c;让你做一个学识丰富、灵魂有趣的人。快来关注&#xff0c;开启精彩的生活吧&#xf…

java设置access-allow_Java Web如何设置多个Access-Control-Allow-Origin

有没有办法让Access-Control-Allow-Origin header允许设置multiple cross-domains呢&#xff1f;如果设置response.addHeader("Access-Control-Allow-Origin","*");感觉这个接口太开放了&#xff0c;不太安全。 我想只设置自己指定的若干个域名或者端口可以…

微软 MS Learn 上线 Blazor 入门教程

微软官方学习网站 MS Learn 上线了 Blazor 入门教程模块&#xff0c;希望通过这个课程&#xff0c;让开发人员了解如何设置开发环境&#xff0c;以及如何使用 Blazor、Visual Studio Code 和 C# 生成你的首个 Web 应用。Build a web app with Blazor - Learndocs.microsoft.com…

Type Casting

Type Casting C : Documents : C Language Tutorial : Type Casting Search: userpass[register] javascript and cookies required C Language TutorialIntroduction?Instructions for useBasics of C?Structure of a program?Variables. Data Types.?Constants?Oper…

世界最牛实验室,堪称诺贝尔奖孵化器!到底是个怎样神奇的存在?!

▲ 点击查看随着诺贝尔各个奖项陆陆续续的公布&#xff0c;卡文迪许实验室&#xff0c;又开始重回大众视野。在这个世界最牛实验室之一的实验室里&#xff0c;仅仅过去了一百多年&#xff0c;就不断涌现出一批又一批世界一流的科学家&#xff1a;把电与磁进行有机统一的麦克斯…

开源的负载测试/压力测试工具 NBomber

负载测试和压力测试对于确保 web 应用的性能和可缩放性非常重要。尽管它们的某些测试是相同的&#xff0c;但目标不同。负载测试&#xff1a;测试应用是否可以在特定情况下处理指定的用户负载&#xff0c;同时仍满足响应目标。应用在正常状态下运行。压力测试&#xff1a;在极端…

男人都应该懂的一张图。。 | 今日趣图

全世界只有3.14 % 的人关注了青少年数学之旅美国人为了教民众如何辨别韩国人制作的韩国女性标准照左右军事成为一个有钱人的概率有多高&#xff1f;最新版男人都该懂的汽车品牌从属关系图twi:NOCO_1002肥胖和骨架没有必然联系800斤胖子的X射线照 科普君XueShu 雪树来猜一猜这是…

学做菜咯

以前在QQ空间发的贴&#xff0c;现在转到这边来&#xff0c;嘿嘿。青蛙达 - 07月28日- 14时28分今天中午做我个人最喜欢的菜之一《咖喱鸡饭》 早上到超市买了&#xff1a; 大蒜头、洋葱、南瓜、鸡翅膀、咖喱粉、胡椒粉、白糖、桂皮 回来发现原来把辣椒粉当成胡椒粉买回来了....…

Facebook 中国程序员之死

全世界只有3.14 % 的人关注了青少年数学之旅9 月 19 日&#xff0c;一位 Facebook 软件工程师从加州门洛帕克&#xff08;Menlo Park&#xff09;总部四楼纵身跳下&#xff0c;结束年轻的生命。Facebook 新闻发言人证实确有其事&#xff0c;并说公司将会联系员工家人。门洛帕克…

[导入]数据库物理模型设计的其他模式之继承模式

连载之7原创&#xff1a;胖子刘&#xff08;转载请注明作者和出处&#xff0c;谢谢&#xff09;数据库物理模型设计的其他模式除了上面提到的四种主要设计模式&#xff0c;还有一些其他模式&#xff0c;在某些项目中可能会用到&#xff0c;在这里先简单做个说明&#xff0c;暂不…

“我数学太烂,但高考136分!”刷完上万道题后,我找到2个月多考58分的捷径…...

全世界只有3.14 %的人关注了青少年数学之旅01难上天的高考试卷&#xff0c;我逆袭考到136分&#xff01;我叫刘辉&#xff0c;来自湖北省的某个县城&#xff0c;今年我数学考到了136分的好成绩&#xff0c;成功被一所985高校录取。↓我的高考成绩↓但回想一年之前&#xff0c;我…

[Forward] 因为火炬,所以迟到,工资照扣

今天跟往常一样,到香蜜湖等230 看到深南大道主道那边站了一名JC叔叔 在前面的主辅岔道口看到有交J叔叔...对面又大堵车...心想大事不妙..又要交通管制了......两hui期间因为交通管制让我第一个月上班就来了一次迟到 这时候是8点钟多一点开始管制.....很后悔没有上到最后一…

三分钟总览微软任务并行库TPL

点击上方蓝字进行关注有小伙伴问我每天忽悠的TPL是什么&#xff1f;☹️ 这次站位高一点&#xff0c;严肃讲一讲。引言俗话说&#xff0c;不想开飞机的程序员不是一名好爸爸&#xff1b;作为微软技术栈的老鸟&#xff0c;一直将代码整洁之道奉为经典&#xff0c; 优秀的程序员将…

第五章 MyEclipse配置hadoop开发环境

1.首先要下载相应的hadoop版本的插件&#xff0c;我这里就给2个例子&#xff1a; hadoop-1.2.1插件&#xff1a;http://download.csdn.net/download/hanyongan300/6238153 hadoop2.2.0插件&#xff1a;http://blog.csdn.net/twlkyao/article/details/17334693 上一章我也讲了怎…

这才是真正的,坐上来,自己动!| 今日趣图

全世界只有3.14 % 的人关注了青少年数学之旅你有见过加辣的奶茶吗&#xff1f;什么叫做科技改变生活其实你的猫一直都看不起你坐上来&#xff0c;自己动&#xff01;安全带使用体验当iPhone遇上数学在B站UP主的剪刀下诞生了各种神奇的CP组合其中最受欢迎的居然是伏地魔和林黛玉…

九项路考(2)

<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />二、侧方位停车<?xml:namespace prefix v ns "urn:schemas-microsoft-com:vml" />要点&#xff1a;1、倒车入停车位&#xff08;1&#xff09;挂倒档&#xf…

360导航源码php,51zxw 仿360网址导航源码

1.上传网站安装程序到空间&#xff0c;空间需支持PHP&#xff0c;MYSQL数据库20M 即可。2.访问网网址执行安装&#xff0c;按照提示填入mysql数据库信息。3.安装时默认设置后台管理 用户&#xff1a;admin 密码&#xff1a;123456 (以防安装出错建议默认安装&#xff0c;然后登…