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

本文详细介绍了如何利用Dapr的Saga组件在.NET环境中构建一个微服务电商系统,通过事件驱动和补偿机制实现分布式事务。首先解释了Saga在领域驱动设计中的作用,然后展示了在Dapr中使用Saga进行事务管理的流程,包括事件订阅、发布和异常处理。文章提供了具体的代码示例,展示了订单服务和商品服务如何注册事件、处理委托,并在遇到故障时进行补偿操作,确保事务的一致性。
摘要由CSDN通过智能技术生成

目录:

一、通过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的演示就到这里,可尝试拉取最新的商城源码执行即可看到效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值