通过Dapr实现一个简单的基于.net的微服务电商系统(十九)——分布式事务之Saga模式...

目录:

一、通过Dapr实现一个简单的基于.net的微服务电商系统

二、通过Dapr实现一个简单的基于.net的微服务电商系统(二)——通讯框架讲解

三、通过Dapr实现一个简单的基于.net的微服务电商系统(三)——一步一步教你如何撸Dapr

四、通过Dapr实现一个简单的基于.net的微服务电商系统(四)——一步一步教你如何撸Dapr之订阅发布

通过Dapr实现一个简单的基于.net的微服务电商系统(五)——一步一步教你如何撸Dapr之状态管理

通过Dapr实现一个简单的基于.net的微服务电商系统(六)——一步一步教你如何撸Dapr之Actor服务

通过Dapr实现一个简单的基于.net的微服务电商系统(七)——一步一步教你如何撸Dapr之服务限流

通过Dapr实现一个简单的基于.net的微服务电商系统(八)——一步一步教你如何撸Dapr之链路追踪

通过Dapr实现一个简单的基于.net的微服务电商系统(九)——一步一步教你如何撸Dapr之OAuth2授权

通过Dapr实现一个简单的基于.net的微服务电商系统(九)——一步一步教你如何撸Dapr之OAuth2授权-百度版

通过Dapr实现一个简单的基于.net的微服务电商系统(十)——一步一步教你如何撸Dapr之绑定

通过Dapr实现一个简单的基于.net的微服务电商系统(十一)——一步一步教你如何撸Dapr之自动扩/缩容

通过Dapr实现一个简单的基于.net的微服务电商系统(十二)——istio+dapr构建多运行时服务网格

通过Dapr实现一个简单的基于.net的微服务电商系统(十三)——istio+dapr构建多运行时服务网格之生产环境部署

通过Dapr实现一个简单的基于.net的微服务电商系统(十四)——开发环境容器调试小技巧

通过Dapr实现一个简单的基于.net的微服务电商系统(十五)——集中式接口文档实现

通过Dapr实现一个简单的基于.net的微服务电商系统(十六)——dapr+sentinel中间件实现服务保护

通过Dapr实现一个简单的基于.net的微服务电商系统(十七)——服务保护之动态配置与热重载

通过Dapr实现一个简单的基于.net的微服务电商系统(十八)——服务保护之多级缓存

附录:(如果你觉得对你有用,请给个star)
一、电商Demo地址:https://github.com/sd797994/Oxygen-Dapr.EshopSample

二、通讯框架地址:https://github.com/sd797994/Oxygen-Dapr

三、Saga框架地址:https://github.com/sd797994/Oxygen-Saga

一、什么是Saga

        在领域驱动设计中,由于领域边界的存在,以往的分层设计中业务会按照其固有的领域知识被切分到不同的限界中,并且引入了领域事件这一概念来降低单个业务的复杂度,通过非耦合的事件驱动来完成复杂的业务。但是事件驱动带来了一些新的问题,由于以往一个原子性极强的逻辑被拆散到了一个一个小的领域中,原子性事务数据的强一致性无法被保证。为了解决这个问题,一般会采用事务补偿的方式来确保最终一致。

        当我们采用多个本地事务组合去进行业务处理时,由于业务其本身的复杂性,往往需要在多个事务中协调。而事件协调器(saga)就是一个专门降低其复杂度的设计,开发人员原则上只需要将事件和补偿按照一定顺序注册到协调处理器中,原则上协调器会按照注册的事件依次执行,若出现事件执行失败时,也会按照补偿列表进行相应的回滚。

        去年曾经在一篇文章里聊过Saga,链接在此。相比去年的那个小DEMO。我在此基础上基于Dapr的状态管理完成了另外一个开源项目Oxygen-Saga,地址:https://github.com/sd797994/Oxygen-Saga。当然这个项目既可以引入到dapr的环境中完成Saga事务,本身也可以独立的基于rabbitmq+redis来完成非Dapr环境下的Saga事务。今天主要讲解一下如何在Dapr环境下引入Saga来实现一个分布式事务。

  首先我们还是看看目前的一个订单下单逻辑,可以看到基于Actor服务,我们的请求是在订单服务里直连商品服务Actor完成库存减扣的,并通过Actor的周期性持久化机制完成事务落库,所以实际上是把分布式事务的复杂性通过Actor屏蔽掉了,并没有涉及到真正的分布式事务。

082ba53a29dbca6dd4990649054ba116.png

  从代码层面也可以看到,整个事务其实是在同一个方法里以同步调用Actor的方式完成的。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

#region 私有远程服务包装器方法

async Task<List<OrderGoodsSnapshot>> GetGoodsListByIds(IEnumerable<Guid> input)

{

    return (await goodsQueryService.GetGoodsListByIds(new GetGoodsListByIdsDto(input))).GetData<List<OrderGoodsSnapshot>>();

}

async Task<bool> DeductionGoodsStock(CreateOrderDeductionGoodsStockDto input)

{

    var data = input.CopyTo<CreateOrderDeductionGoodsStockDto, DeductionStockDto>();

    data.ActorId = input.GoodsId.ToString();

    return (await goodsActorService.DeductionGoodsStock(data)).GetData<bool>();

}

async Task<bool> UnDeductionGoodsStock(CreateOrderDeductionGoodsStockDto input)

{

    var data = input.CopyTo<CreateOrderDeductionGoodsStockDto, DeductionStockDto>();

    data.ActorId = input.GoodsId.ToString();

    return (await goodsActorService.UnDeductionGoodsStock(data)).GetData<bool>();

}

#endregion

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public async Task<ApiResult> CreateOrder(OrderCreateDto input)

{

    var mockUser = (await accountQueryService.GetMockAccount()).GetData<CurrentUser>();

    //申明一个创建订单领域服务实例,将远程rpc调用作为匿名函数传递进去

    var createOrderService = new CreateOrderService(GetGoodsListByIds, DeductionGoodsStock, UnDeductionGoodsStock);

    return await ApiResult.Ok("订单创建成功!").RunAsync(async () =>

    {

        var order = await createOrderService.CreateOrder(mockUser.Id, mockUser.UserName, mockUser.Address, mockUser.Tel, input.Items.CopyTo<OrderCreateDto.OrderCreateItemDto, OrderItem>().ToList());//通过订单服务创建订单

        repository.Add(order);

        if (await new CheckOrderCanCreateSpecification(repository).IsSatisfiedBy(order))

            await unitofWork.CommitAsync();

        await eventBus.SendEvent(EventTopicDictionary.Order.CreateOrderSucc, new OperateOrderSuccessEvent(order, mockUser.UserName));//发送订单创建成功事件

    },

    //失败回滚

    createOrderService.UnCreateOrder);

}

  而在Saga方案里就变得不一样了,我们需要借助Saga的流程管理器在多个实例中流转我们的事务,通过事件订阅和发布来最终完成一个分布式事务。具体的调用流程如下:

26c67285d7182a62fc35e8b3e0510361.png

可以看到整个流程完全依赖于SagaManger来提供对应的调用策略,而作为开发人员只需要为每个策略提供对应的委托函数,对应的具体流程如下:

  一、首先是订单服务和商品服务在系统初始化时需要注册对应的配置文件到本地Saga管理器,并且需要提供对应的流程处理委托(包括业务事件委托+补偿事件委托+异常处理委托)。

  二、在客户发起一个订单时,只需要创建一个Saga流程实例,剩下的就交给Saga,它会自动帮我们流转整个业务逻辑而无需人工插手。

二、talk is cheap, show me the code

首先我们需要为整个订单创建流程设计一组Topic:a095074359ce87e0b7a3feb3c0b58fca.png

接着我们创建一个Saga配置注册这组topic进去,流程比较简单,第一步是扣除库存,下一步是创建订单,补偿事件只有一个就是库存回滚:

0e8ae3fe7b1058d5be599f09c2e719fe.png

再然后我们需要创建这些Topic对应的事件处理器:

订单服务:

6bed07019966a9dd5280ea6cc9bcd58f.png

商品服务: 

2d3105668a00c71e9da63924b68575ec.png

接着我们在各自的服务里去实现它们:

商品服务:

8d2dd34e67522d88afcdeaba5f56d01e.png

订单服务:

cc6bbca255e0e06107575cf9c176a09e.png

然后我们在订单用例里创建一个Action用于启动saga流程(注意是第二个方法):

b45c8274bfdade97c4f97f2860b02d2e.png

 最后我们在商品和订单服务中引入saga组件并注册事件和异常回调委托(注册代码相似,此处仅展示其中一个服务的):

d81291efffd42b35a0e93f2672eeaaff.png

接着我们修改m站的创建订单接口,修改为新的Saga流程接口,然后编译整个项目,启动并测试一下:

300728e835835e1e1b81196d8e0b1226.png

10a92aced8ca158c5849ff4e014ab119.png

 715c68195d90f6126fb4cd35244042cf.png

 可以看到整个流程被顺利的通过Saga管理器流转完毕。接着我们尝试注入一个故障,看看能否正常的被异常订阅器捕获到:

7aa7fab2c8186796f62f048063c0919e.png

 编译后再测试一下,我们可以看到事件异常订阅器里已经成功捕获到了这个异常:

f8ef745d7bf0a5838c55f836441aa563.png

 接着我们在创建订单注入一个异常,让商品库存进行回滚操作,这里由于下单后异常商品补偿所以在界面上是体现不出来的(当然在真实的业务场景中一般是下单5秒等待后查询订单创建情况,或发送一条站内信告知下单失败),这里我们通过打印控制台来演示:

908dc5e677c898183aa912d3756299b5.png

a2715d24707c50a1d9c15bad6d1be899.png

接着我们运行并下单,追踪商品服务的日志:

9f0ca3dcba75f0741317349abdb59765.png

03184fe44c2b1532205c515bfbff4b16.png

可以看到由于订单创建失败,saga触发了补偿事件并成功执行了补偿。好了,关于saga的演示就到这里,可尝试拉取最新的商城源码执行即可看到效果。

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

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

相关文章

简单直接排序

#!/usr/bin/python #encoding:utf-8 #基本思想:#将一个记录插入到已排序好的有序表中&#xff0c;从而得到一个新&#xff0c;记录数增1的有序表。即&#xff1a;先将序列的第1个记录看成是一个有序的子序列&#xff0c;然后从第2个记录逐个进行>插入&#xff0c;直至整个序…

php怎么关闭oracle连接,PHP 连接 Oracle

起因由于项目的数据库需要用客户购买的Oracle数据库&#xff0c;所以需要php安装oci扩展。运行环境php : 7.2系统: windows10oracle: 11gR2安装相关环境由于php的oci8扩展还是需要使用到oracle的一些包&#xff0c;所以先下载这一些。下载完成后解压缩这个压缩包&#xff0c;并…

MySQL查询,按拼音首字母排序

MySQL按照汉字的拼音排序按照汉字的拼音排序&#xff0c;用的比较多是在人名的排序中&#xff0c;按照姓氏的拼音字母&#xff0c;从A到Z排序&#xff1b;如果存储姓名的字段采用的是GBK字符集&#xff0c;那就好办了&#xff0c;因为GBK内码编码时本身就采用了拼音排序的方法&…

Android Studio之debug调试卡在waiting for debugger界面的解决办法

1、问题 Android Studio debug调试项目卡在waiting for debugger界面2、解决办法 一开始从启adb服务adb stop-server adb start-server 发现没什么用&#xff0c;然后呢&#xff0c;估计是很多app再运行&#xff0c;然后我就重启了Android studio,问题就解决了如果要是万一还不…

.NET 深度指南:Colors

作者 &#xff5c; Peter Huber译者 &#xff5c; 王强策划 &#xff5c; 丁晓昀我不知道你们是什么情况&#xff0c;但我自己在过去多年中都因为.NET 色彩&#xff08;Colors&#xff09;类中可用的色彩数量有限而头痛不已&#xff0c;为此我试图用 ColorPickers 获得匹配的色…

php 怎么打出来的,word书名号怎么打出来

书名号怎么打出来&#xff1f;书名号相信大家都不会陌生了&#xff0c;正常情况下&#xff0c;我们会将书名、歌曲名、作品名等用书名号框起来&#xff0c;这样就可以让读者一目了然。然而很多用户在编辑Word和Excel文档时&#xff0c;想输入书名号却不知从何下手&#xff0c;这…

Android之用UncaughtExceptionHandler实现保存崩溃日志到sdcard目录下的文件夹

1、异常和UncaughtExceptionHandler的介绍 1)、Java异常处理机制中: 如果抛出的是Exception异常的话,需要有try catch进行处理,属于可以捕获exception。 如果抛出的是RuntimeException异常的话,发生异常之后将由JVM进行处理,属于不可捕获exception。 2)、Java 异常的分类…

http://www.appinn.com/bookmark-manager-chrome/

是有多手贱才装了这个。。弄得我的书签全都木有了。。我去。。终于回来了。。感觉太好了。。转载于:https://www.cnblogs.com/aniy/p/4547709.html

springMVC带文件的表单数据无法绑定到参数中

2019独角兽企业重金招聘Python工程师标准>>> 在一个带enctype"multipart/form-data"属性的表单提交时发现&#xff0c;该表单中包含的其他input无法设置到对应方法参数中。 如下&#xff1a; JSP&#xff1a;带enctype"multipart/form-data"属性…

关于Retinex图像增强算法的一些新学习。

最近再次看了一下IPOL网站&#xff0c;有一篇最近发表的文章&#xff0c;名字就是Multiscale Retinex&#xff0c;感觉自己对这个已经基本了解了&#xff0c;但还是进去看了看&#xff0c;也有一些收获&#xff0c;于是抽空把他们稍微整理了下&#xff0c;原始文章及其配套代码…

如何判断 .NET Core 应用程序是以管理员身份运行

有时候&#xff0c;我们需要知道当前程序是否以管理员身份运行&#xff0c;以便执行一些需要特殊权限的操作。在github(https://github.com/dotnet/runtime/issues/25118#issuecomment-367407469)上找到了一个解决方案&#xff1a;//需要引用nuget包Mono.Posix.NETStandard pub…

php如何避免时间重复,如何避免/停止php和html的重复插入?

这是我写的一个小班(未经测试)class FormID {private $lastFormID "";private $newFormID "";function __construct(){$this->lastFormID $_SESSION[__frmid__];$_SESSION[__frmid__] uniqid(sm);$this->newFormID $_SESSION[__frmid__];}publi…

Android之Content和activity、service、Application关系和attachBaseContext函数调用的时候

1、Content和activity、service、Application关系 2、Application里面attachBaseContext和onCreate函数调用顺序 Application-> attachBaseContext ();ContentProvider:onCreate()Application:onCreate()人还是容易忘记&#xff0c;先记录下来。

批量创建域账号

创建5列的csv文档&#xff08;注意文件编码&#xff09; 执行如下脚本 for /f "tokens1,2,3,4,5 delims," %a in (c:\test.csv) do dsadd user "cn%c,oumk,ouicgroup,dcicdomain,dccom" -samid %d -upn %dicdomain.com -ln %a -fn %b -pwd %e -disabled no…

如何快速编写并运行Tiny模板语言?

2019独角兽企业重金招聘Python工程师标准>>> 说到模板开发&#xff0c;当然就离不开要调试&#xff0c;要运行。 由于一般情况下模板语言都是由Java程序驱动跑的&#xff0c;因此&#xff0c;每次都需要搞一个Java类来驱动它&#xff0c;才能运行出结果。这个对于悠…

Windows 11 上大招!正式支持安卓!

面向 Windows 11 正式版用户&#xff0c;微软现已发布累积更新 KB5010414&#xff0c;更新后版本号升级至 Build 22000.527。KB5010414 是一个可选更新&#xff0c;因此除非您主动点击“获取更新”按钮&#xff0c;否则它不会下载或安装。该更新将于 2022 年 3 月向所有 Window…

linux之使用md5sum命令比较两个文件是否一样

1、md5sum命令介绍 md5sum命令用于生成和校验文件的md5值。它会逐位对文件的内容进行校验。是文件的内容&#xff0c;与文件名无关&#xff0c;也就是文件内容相同&#xff0c;其md5值相同2、比较文件 1&#xff09;、生产文件的md5md5sum file1md5sum file2 会有结果显示2&am…

php 友盟推送角标,桌面角标设置以及消息推送服务

更新记录0.01(2020-05-09)1、添加角标设置2、添加清除角标3、添加品牌获取4、添加通知栏推送5、添加点击通知栏进入APP平台兼容性AndroidiOS原生插件通用使用流程&#xff1a;购买插件&#xff0c;选择该插件绑定的项目。在HBuilderX里找到项目&#xff0c;在manifest的app原生…

博客流量分析

接连两篇Spark内核分析的文章都被推荐到CSDN首页&#xff0c;带来的流量还是很客观的&#xff0c;基本上一天最少500个独立IP的访问。这个访问量还是很客观的&#xff0c;比推荐到博客首页和侧边栏还是效果好处不少的。88%的流量来自首页。北京不愧是码农的聚集地啊&#xff0c…

hdu 4493 Tutor (水 精度)

题意&#xff1a; 思路&#xff1a; #include <iostream> #include <cstdio> #include <cstring> #include <algorithm>using namespace std; int fa[150]; int fin(int x) {return fa[x]x?x:fin(fa[x]); } void unionn(int x,int y) {int fxfin(x);i…