.NET core实现一个简易的事件协调器(saga)
在领域驱动设计中,由于领域边界的存在,以往的分层设计中业务会按照其固有的领域知识被切分到不同的限界中,并且引入了领域事件这一概念来降低单个业务的复杂度,通过非耦合的事件驱动来完成复杂的业务。但是事件驱动带来了一些新的问题,由于以往一个原子性极强的逻辑被拆散到了一个一个小的领域中,原子性事务数据的强一致性无法被保证。为了解决这个问题,一般会采用事务补偿的方式来确保最终一致。
事务补偿机制有多种实现方式,有基于数据库自带的基于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)的更多相关文章
- 使用lua实现一个简单的事件派发器
设计一个简单的事件派发器,个人觉得最重要的一点就是如何保证事件派发过程中,添加或删除同类事件,不影响事件迭代顺序和结果,只要解决这一点,其它都好办. 为了使用pairs遍历函数,重写了pairs(lu ...
- 用pyqt5做一个简易的音乐播放器
需求 要求可以读取音频文档,有播放和暂停的功能 附上代码(1)UI界面 # -*- coding: utf-8 -*- # Form implementation generated from rea ...
- 用条件变量实现事件等待器的正确与错误做法--转自陈硕的Blog
用条件变量实现事件等待器的正确与错误做法 TL;DR 如果你能一眼看出 https://gist.github.com/chenshuo/6430925 中的那 8 个 Waiter classes ...
- Caliburn.Micro 杰的入门教程4,事件聚合器
Caliburn.Micro 杰的入门教程1(原创翻译)Caliburn.Micro 杰的入门教程2 ,了解Data Binding 和 Events(原创翻译)Caliburn.Micro 杰的入门 ...
- Server 2008 R2 事件查看器实现日志分析
在 windows server 2008 R2 中,可以通过点击 "开始" -> "管理工具" -> "事件查看器" ,来打开 ...
- .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”
FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为我们构建了一个物理文件系统和程序集内嵌文 ...
- .NET CORE学习笔记系列(2)——依赖注入[4]: 创建一个简易版的DI框架[上篇]
原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...
- 基于OpenGL编写一个简易的2D渲染框架-07 鼠标事件和键盘事件
这次为程序添加鼠标事件和键盘事件 当检测到鼠标事件和键盘事件的信息时,捕获其信息并将信息传送到需要信息的对象处理.为此,需要一个可以分派信息的对象,这个对象能够正确的把信息交到正确的对象. 实现思路: ...
- ENode 2.0 - 第一个真实案例剖析-一个简易论坛(Forum)
前言 经过不断的坚持和努力,ENode 2.0的第一个真实案例终于出来了.这个案例是一个简易的论坛,开发这个论坛的初衷是为了验证用ENode框架来开发一个真实项目的可行性.目前这个论坛在UI上是使用了 ...
随机推荐
- JAVA并发-AQS知识笔记
概述 AQS是AbstractQueuedSynchronizer的缩写,翻译成中文就是抽象队列同步器,AbstractQueuedSynchronizer这个类也是在java.util.concur ...
- java基础01-03-注释、标识符、数据类型讲解
java基础01-注释 java中的注释有三种: 单行注释 多行注释 文件注释 public class helloworld { public static void main(String[] a ...
- windos 安装 redis 启动闪退
本来想在linux上安装redis的,后来觉得也没必要,主要是了解使用方法,和原理,在什么平台上安装都是大同小异的 接下来简单描述下碰到的小问题:闪退和启动失败 究其原因就是端口被占用了,但是自己并没 ...
- JavaScript创建和获取时间的方法
一.获取时间常用方法 1.创建时间对象 var time=new Date() //创建当前的时间信息对象 var time1=new Date(2022,1,1,10,25,30) //创建2022 ...
- 使用redis+lua实现SQL中的select intersect的效果
公众号文章地址 1.需求 业务中需要实现在两个集合中搜索数据,并返回交集. 用SQL的伪代码可以描述如下: select key from set1 where sorted_key between ...
- 阿里Java规范:【强制】所有的 POJO 类属性必须使用包装数据类型
在 Java 开发手册中有这一条: 我们知道基本类型和包装类型有很多不同点: 封装类型可以调用各种方法,而基本类型没有 封装类型声明字段之后可以不设置默认值,而基本类型需要初始化默认值.比如 int ...
- python04day
回顾 int str bool str: s1='tangdaren123' 索引: s1[0] s1[-1] s1[:3] s1[:5:2] s1[-1:-4:-1] s1[-1:-6:-2] 常用 ...
- java 变量的定义 类型转换 基本的数据类型
package com.aaa.zxf.ajax.test; import org.junit.Test; /** * 六. * 如何在ideal的maven项目中导入 Test 测试类? * * * ...
- Pandas 学习手册中文第二版·翻译完成
原文:Learning pandas 协议:CC BY-NC-SA 4.0 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远. 在线阅读 ApacheCN 面试求职交流群 72418 ...
- tomcat访问所有的资源,都是用Servlet来实现的
感谢大佬:https://www.zhihu.com/question/57400909 tomcat访问所有的资源,都是用Servlet来实现的. 在Tomcat看来,资源分3种 静态资源,如css ...