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

  事务补偿机制有多种实现方式,有基于数据库自带的基于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. vue2.0中实现echarts图片下载-----书写中

    由于各个版本浏览器兼容性不一,所以,我们需要一个判断浏览器类型的函数来对不同的浏览器做不同的处理. 获取浏览器版本的函数 // 判断浏览器类型 IEVersion () { let userAgent ...

  2. Amazon EKS 中 EFS 持久性存储

    作者:SRE运维博客 博客地址:https://www.cnsre.cn/ 文章地址:https://www.cnsre.cn/posts/220110850573/ 相关话题:https://www ...

  3. elasticsearch在linux上的安装,Centos7.X elasticsearch 7.6.2安装

    本文环境:Elasticsearch7.6.2目前最先版本   centos7.X     JDK1.8 elasticsearch介绍 官网:https://www.elastic.co/cn/pr ...

  4. 403 Invalid CORS request 跨域问题 invalid+cors+request什么意思

    5.跨域问题 跨域:浏览器对于javascript的同源策略的限制 . 以下情况都属于跨域: 跨域原因说明 示例 域名不同 www.jd.com 与 www.taobao.com 域名相同,端口不同 ...

  5. 使用 SourceGenerator 简化 Options 绑定

    目录 摘要 Options 绑定 使用 SourceGenerator 简化 如何 Debug SourceGenerator 如何 Format 生成的代码 使用方法 SourceCode & ...

  6. 【机器学习】VAE

    机器学习算法-VAE 目录 机器学习算法-VAE 1. VAE模型推导 1.1 算法引入 1.2 模型推导 1.3 损失函数 1.4 重参数技巧 2. 实现 2.1 模型定义 2.2 实验 1. VA ...

  7. unity3d发布安卓出错plese set the package name

    发布时报错 参考https://forum.unity.com/threads/where-is-package-name-setting.318839/ 参考https://answers.unit ...

  8. golang中time包日期时间常用用法

    package main import ( "fmt" "reflect" "time" ) var week time.Duration ...

  9. IDEA中导入Maven工程(module)

    导入其它Maven工程时可能会出现依赖代码变红等等可以重新导入 右键pom.xml文件 --->Maven---->Reimport  ,idea强制刷新内容,一般能解决依赖没有识别的问题 ...

  10. 通过kubeadm工具部署k8s集群

    1.概述 kubeadm是一工具箱,通过kubeadm工具,可以快速的创建一个最小的.可用的,并且符合最佳实践的k8s集群. 本文档介绍如何通过kubeadm工具快速部署一个k8s集群. 2.主机规划 ...