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

  事务补偿机制有多种实现方式,有基于数据库自带的基于2PC的XA协议、也有在逻辑层通过TCC实现,抑或采用多个本地事务组合的方式来实现。

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

  今天我们就通过一些简单的代码设计,来还原一个简易的基于事件总线的协调器。

  废话不多说,先上代码:https://github.com/sd797994/EventCoordinator

  解决方案包含两个项目(TargetFramework为.net5,如果你没有安装.net,可以改成netcoreapp3.1),一个是演示用的webapi demo。包含基本的控制器和一组事件及事件订阅处理服务。演示项目流程如下:

  客户端访问接口下订单->发布订单创建事件->订单预创建订阅处理器创建订单编号->发布订单预创建成功事件->商品扣除订阅处理器订阅订单预创建成功事件并进行商品库存扣除->发布库存扣除成功事件->用户余额扣扣除订阅处理器订阅库存扣除成功事件并执行用户余额扣除->发布用户余额扣除成功事件->订单创建订阅处理器订阅用户余额扣除成功事件创建订单。若其中每一步处理失败,则依次进行回滚。

  若一切正常,则流程结果执行如下:

  模拟最后一步订单创建失败时,全部回滚的结果如下:

  模拟订单创建失败用户金额回滚成功商品库存回滚异常时,结果如下(在真实的业务场景中出现此类情况应该进行系统预警人工处理,我这里采用logger.error模拟警戒级别):

  下面我们来看看实现思路和代码,打开EventCoordinator项目,我们可以看到事件总线/事件协调器/通用三个文件夹。其中通用类是一些帮助方法不再赘述,事件也是基于System.Threading.Channels的简易异步事件总线实现,也不再赘述。主要说说协调器。

协调器的核心主要是EventProcessManager.cs以及EventProcessManagerPipline.cs以及ProcessConfigure.cs三个文件来实现的。我的设计逻辑如下:EventProcessManager管理所有的流程性事务。所以其包含长事务的注册和启动逻辑。而事务注册实际上就是一个构造委托代理的过程,我把它命名为ProcessConfigure,通过创建一个流程配置实例,将向EventProcessManager注册的委托作为“配置”的一部分创建其对应的方法委托,再在具体执行流程时从队列中取出配置并执行其的excute方法来发布事件,并且通过托管委托的方式在代理订阅器里执行真正的委托。而所有的入队、入栈我创建了一个EventProcessManagerPipline实例来保存我们的ProcessConfigure,这个管道包含一个队列和一个栈,由于事件是按照先进先出的方式执行,所以事件委托创建的配置会以队列的方式保存。而补偿则是按照先进后出的方式执行,所以补偿委托创建的配置会以栈的方式保存。而整个管道事务流转的核心均在EventProcessManagerPipline的Start方法中。

  Start的核心逻辑如下:启动一个流程,在一个while循环中 从当前的队列中弹出一个事件配置。执行事件配置的send方法,并且通过AutoResetEvent的方式阻塞等待信号。当真正的业务委托执行完毕后会触发代理的AutoResetEvent.set(),如果业务执行callnext,则会将当前事件处理的结果作为callbackevent的一部分存储在缓存里,方便顺序回滚。如果执行callback,则直接执行回滚。如果所有的事件/补偿执行完毕,则流程执行完毕。

  结语:整个代码其实比较简单,仅仅是我对长事务治理思想的一个粗浅理解,可能有误,恳请评论区大佬指正。。。

  

.NET core实现一个简易的事件协调器(saga)的更多相关文章

  1. 使用lua实现一个简单的事件派发器

    设计一个简单的事件派发器,个人觉得最重要的一点就是如何保证事件派发过程中,添加或删除同类事件,不影响事件迭代顺序和结果,只要解决这一点,其它都好办. 为了使用pairs遍历函数,重写了pairs(lu ...

  2. 用pyqt5做一个简易的音乐播放器

    需求 要求可以读取音频文档,有播放和暂停的功能 附上代码(1)UI界面 # -*- coding: utf-8 -*- # Form implementation generated from rea ...

  3. 用条件变量实现事件等待器的正确与错误做法--转自陈硕的Blog

    用条件变量实现事件等待器的正确与错误做法 TL;DR 如果你能一眼看出 https://gist.github.com/chenshuo/6430925 中的那 8 个 Waiter classes ...

  4. Caliburn.Micro 杰的入门教程4,事件聚合器

    Caliburn.Micro 杰的入门教程1(原创翻译)Caliburn.Micro 杰的入门教程2 ,了解Data Binding 和 Events(原创翻译)Caliburn.Micro 杰的入门 ...

  5. Server 2008 R2 事件查看器实现日志分析

    在 windows server 2008 R2 中,可以通过点击 "开始" -> "管理工具" -> "事件查看器" ,来打开 ...

  6. .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”

    FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为我们构建了一个物理文件系统和程序集内嵌文 ...

  7. .NET CORE学习笔记系列(2)——依赖注入[4]: 创建一个简易版的DI框架[上篇]

    原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...

  8. 基于OpenGL编写一个简易的2D渲染框架-07 鼠标事件和键盘事件

    这次为程序添加鼠标事件和键盘事件 当检测到鼠标事件和键盘事件的信息时,捕获其信息并将信息传送到需要信息的对象处理.为此,需要一个可以分派信息的对象,这个对象能够正确的把信息交到正确的对象. 实现思路: ...

  9. ENode 2.0 - 第一个真实案例剖析-一个简易论坛(Forum)

    前言 经过不断的坚持和努力,ENode 2.0的第一个真实案例终于出来了.这个案例是一个简易的论坛,开发这个论坛的初衷是为了验证用ENode框架来开发一个真实项目的可行性.目前这个论坛在UI上是使用了 ...

随机推荐

  1. 前端 vue 等刷新清浏览器缓存的方法

    vue开发过程中发现内存一直往上飙,360时不时的提示下清理内存,测试发现每刷新一次页面内存就增加1%. 清掉浏览器的缓存等各种数据以后内存明显变小,刷新页面导致内存爆表. 这种问题着实不应该存在,记 ...

  2. FileReader()读取文件、图片上传预览

    前言 FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据. 其中File对象可以是来自用户 ...

  3. 重大升级!灵雀云发布全栈云原生开放平台ACP 3.0

    云原生技术的发展正在改变全球软件业的格局,随着云原生技术生态体系的日趋完善,灵雀云的云原生平台也进入了成熟阶段.近日,灵雀云发布重大产品升级,推出全栈云原生开放平台ACP 3.0.作为面向企业级用户的 ...

  4. T-SQL创建数据库常用方法2020年10月29日20:12:04网课笔记

    2.接口的作用 第一.方便框架的设计.利于团队的开发. 第二.方便项目拓展.高内聚.低耦合. 3.反射 [1]反射的理解:通过读取程序集的信息,找到相关的类型和类型的成员,也可以得到相关的对象.而这种 ...

  5. Sentry 开发者贡献指南 - Feature Flag

    功能 flag 在 Sentry 的代码库中声明. 对于自托管用户,这些标志然后通过 sentry.conf.py 进行配置. 对于 Sentry 的 SaaS 部署,Flagr 用于在生产中配置标志 ...

  6. Redis介绍一

    一.五中数据类型 String: 字符串 Hash: 散列 List: 列表 Set: 集合 Sorted Set: 有序集合 Redis 发布订阅 Redis 发布订阅 (pub/sub) 是一种消 ...

  7. linux 安装mysql 可能遇到的小问题

    问题一. ./mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No ...

  8. Jetpack—LiveData组件的缺陷以及应对策略 转至元数据结尾

    一.前言 为了解决Android-App开发以来一直存在的架构设计混乱的问题,谷歌推出了Jetpack-MVVM的全家桶解决方案.作为整个解决方案的核心-LiveData,以其生命周期安全,内存安全等 ...

  9. 一文读懂HarmonyOS服务卡片怎么换肤

    作者:zhenyu,华为软件开发工程师 关注HarmonyOS的小伙伴肯定对服务卡片已经很熟悉了.服务卡片(也简称为"卡片")是FA(FeatureAbility,元服务)的一种界 ...

  10. gin框架中项目的初始化

    核心知识点 json配置文件解析成结构体 将路由对应的接口抽离到单独的文件中,main函数中直接注册路由即可 项目目录图 项目代码 app.json代码 { "app_name": ...