【Shashlik.EventBus】.NET 事件总线,分布式事务最终一致性
【Shashlik.EventBus】.NET 事件总线,分布式事务最终一致性
简介
github https://github.com/dotnet-shashlik/shashlik.eventbus
各位爷高兴了给个star呗。
分布式事务、CAP定理、事件总线,在当前微服务、分布式、集群大行其道的架构前提下,是不可逃避的几个关键字,在此不会过多阐述相关的理论知识。Shashlik.EventBus就是一个基于.NET6的开源事件总线解决方案,同时也是分布式事务最终一致性、延迟事件解决方案。Shashlik.EventBus采用的是异步确保的思路(本地消息表),将消息数据与业务数据在同一事务中进行提交或回滚,以此来保证消息数据的可靠性。其设计目标是高性能、简单、易用、易扩展,为抛弃历史包袱,仅支持NET6,采用最宽松的 MIT 开源协议。
原理如下图:

如图所示,消息数据需要和业务数据在同一的事务中进行提交或者回滚,最后Shashlik.EventBus会检查消息数据是否已提交,如果已提交才会执行真正的消息发送。所以要求事务的隔离级别最低为读已提交(RC)。
关于消息幂等
Shashlik.EventBus不能保证业务消息的幂等性,为了保证消息的可靠传输,EventBus以及消息中间件对消息QOS处理等级必须为at least once (至少到达一次),一般消息中间件都需要开启消息持久化避免消息丢失。简而言之就是一个事件处理类可能处理多次同一个事件,事件消息的幂等性应该由业务方进行处理。比如用户订单付款完成为一个事件,付款完成后需要修改订单状态为待发货,也就是在付款完成事件处理类中可能收到多次这个订单的付款完成事件,那么业务的幂等性处理就可以使用锁,判断订单状态,如果订单状态已经为待发货,则直接返回并忽略本次事件响应。
延迟事件
Shashlik.EventBus支持基于本地的延迟事件机制,考虑到不是所有的消息中间件都支持延迟功能,且为了最大程度保证消息的可靠性,最后采用了System.Timers.Timer来执行延迟功能。
延迟事件同样适用于分布式事务最终一致性,但如果延迟事件处理类处理异常由重试器介入处理后,那么最终的延迟执行时间和期望的延迟时间就会产生较大的差异,是否忽略这里的时间差需要由具体的业务来决定。比如订单30分钟未付款需要关闭订单,30分钟后关闭订单出现了异常,最后由重试器到了40分钟后才关闭,也不影响订单,那么认为这个时间差可以容忍。又比如双11啦,发布一个延迟事件,晚上12点叫醒我起来买买买,只有1分钟时间,过了就买不到了,那么这种情况可以在事件处理类中,自行根据当前时间、事件发送时间、延迟执行时间等要素,自行决定业务如何处理。
延迟事件和普通事件在事件定义和事件处理类声明和处理时没有任何区别,仅仅是在发布事件时需要指定延迟时间。
上代码
需求:一个新用户注册以后有以下需求:1. 发送欢迎注册短信;2. 发放新用户优惠券;3. 30分钟后推送新用户优惠活动信息。
- 服务配置,这里以
MySql+RabbitMQ为例:
services.AddEventBus(r =>
{
// 这些都是缺省配置,可以直接services.AddEventBus()
// 运行环境,注册到MQ的事件名称和事件处理名称会带上此后缀
r.Environment = "Production";
// 最大失败重试次数,默认60次
r.RetryFailedMax = 60;
// 消息重试间隔,默认2分钟
r.RetryInterval = 60 * 2;
// 单次重试消息数量限制,默认100
r.RetryLimitCount = 100;
// 成功的消息过期时间,默认3天,失败的消息永不过期,必须处理
r.SucceedExpireHour = 24 * 3;
// 消息处理失败后,重试器介入时间,默认5分钟后
r.StartRetryAfter = 60 * 5;
// 事务提交超时时间,单位秒,默认60秒
r.TransactionCommitTimeout = 60;
// 重试器执行时消息锁定时长
r.LockTime = 110;
})
// 使用ef DbContext mysql
.AddMySql<DemoDbContext>()
// 配置RabbitMQ
.AddRabbitMQ(r =>
{
r.Host = "localhost";
r.UserName = "rabbit";
r.Password = "123123";
});
- 定义事件
// 新用户注册完成事件,实现接口IEvent
public class NewUserEvent : IEvent
{
public string Id { get;set; }
public string Name { get; set; }
}
// 定义新用户注册延迟活动推送事件
public class NewUserPromotionEvent : IEvent
{
public string Id { get;set; }
public string Name { get; set; }
public string PromotionId { get; set; }
}
- 发布事件
public class UserManager
{
public UserManager(IEventPublisher eventPublisher, DemoDbContext dbContext)
{
EventPublisher = eventPublisher;
DbContext = dbContext;
}
private IEventPublisher EventPublisher { get; }
private DemoDbContext DbContext { get; }
public async Task CreateUserAsync(UserInput input)
{
// 开启本地事务
using var tran = await DbContext.DataBase.BeginTransactionAsync();
try
{
// 创建用户逻辑处理...
// 发布新用户事件
// 通过注入IEventPublisher发布事件,需要传入事务上下文数据
await EventPublisher.PublishAsync(new NewUserEvent{
Id = user.Id,
Name = input.Name
}, DbContext.GetTransactionContext());
// 发布延迟事件
// 通过ef扩展,直接使用DbContext发布事件,自动使用当前上下文事务
await DbContext.PublishEventAsync(new NewUserPromotionEvent{
Id = user.Id,
Name = input.Name,
PromotionId = "1"
}, DatetimeOffset.Now.AddMinutes(30));
// 提交本地事务
await tran.CommitAsync();
}catch(Exception ex)
{
// 回滚事务,消息数据也将回滚不会发布
await tran.RollbackAsync();
}
}
}
- 定义事件处理类
// 一个事件可以有多个处理类,可以分布在不同的微服务中
// 用于发送短信的事件处理类
public class NewUserEventForSmsHandler : IEventHandler<NewUserEvent>
{
public async Task Execute(NewUserEvent @event, IDictionary<string, string> items)
{
// 发送短信...
}
}
// 用于发放消费券的事件处理类
public class NewUserEventForCouponsHandler : IEventHandler<NewUserEvent>
{
public async Task Execute(NewUserEvent @event, IDictionary<string, string> items)
{
// 业务处理...
}
}
// 用于新用户延迟活动的事件处理类,将在指定时间执行
public class NewUserPromotionEventHandler : IEventHandler<NewUserPromotionEvent>
{
public async Task Execute(NewUserPromotionEvent @event, IDictionary<string, string> items)
{
// 业务处理...
}
}
默认的,发布、声明到消息中间件的事件、事件处理器名称生产规则为{Type.Name}.{Options.Environment},在分布式架构下需要,您需要了解这个默认规则,这点不同于CAP框架必须显示声明,当然Shashlik.EventBus也可以使用EventBusNameAttribute特性来显示声明,详细说明请上github查看wiki文档。
XA事务支持(TransactionScope)
虽然尽可能的不要使用TransactionScope,但在某些场景仍然是需要的,Shashlik.EventBus对其提供了事务支持,可以通过XaTransactionContext.Current获取当前环境的事务上下文,发布事件如下:
public class UserManager
{
public UserManager(IEventPublisher eventPublisher, DemoDbContext dbContext)
{
EventPublisher = eventPublisher;
DbContext = dbContext;
}
private IEventPublisher EventPublisher { get; }
private DemoDbContext DbContext { get; }
public async Task CreateUserAsync(UserInput input)
{
// 开启事务
using var scope = new TransactionScope();
try
{
// 创建用户逻辑处理...
// 发布新用户事件
// 通过注入IEventPublisher发布事件,需要传入事务上下文数据
await EventPublisher.PublishAsync(new NewUserEvent{
Id = user.Id,
Name = input.Name
// 使用 XaTransactionContext.Current
}, XaTransactionContext.Current);
// 提交事务
await scope.Complete();
}catch(Exception ex)
{
// 回滚事务,消息数据也将回滚不会发布
await tran.RollbackAsync();
}
}
}
扩展
如果默认实现不能满足你的需求,可以自行实现可扩展接口,并注册即可。
IMsgIdGenerator:消息Id生成器,是指传输的全局唯一id,不是指存储的id。默认guidIEventPublisher:事件发布处理器。IMessageSerializer:消息序列化、反序列化处理类。默认Newtonsoft.Json。IReceivedMessageRetryProvider:已接收消息重试器。IPublishedMessageRetryProvider:已发布消息重试器。IEventHandlerInvoker: 事件处理执行器IEventNameRuler:事件名称规则生成(对应消息队列topic/route)。IEventHandlerNameRuler:事件处理名称规则生成(对应消息队列queue/group)。IEventHandlerFindProvider:事件处理类查找器IExpiredMessageProvider:已过期消息删除处理器。IMessageListener:消息监听处理器。IRetryProvider:重试执行器。IPublishHandler:消息发布处理器。IReceivedHandler:消息接收处理器。IMessageStorageInitializer:存储介质初始化。IMessageStorage:消息存储、读取等操作。
例:
// 替换默认的IMsgIdGenerator
service.AddSingleton<IMsgIdGenerator, CustomMsgIdGenerator>();
service.AddEventBus()
.AddMemoryQueue()
.AddMemoryStorage();
后续计划
- 功能
- Dashboard
- 消息中间件支持
- RabbitMQ
- Kafka
- RocketMQ
- ActiveMQ
- Pulsar
- Redis
- 存储支持
- MySql
- PostgreSql
- SqlServer
- Oracle
- MongoDb
【Shashlik.EventBus】.NET 事件总线,分布式事务最终一致性的更多相关文章
- 【转】.NET Core 事件总线,分布式事务解决方案:CAP
[转].NET Core 事件总线,分布式事务解决方案:CAP 背景 相信前面几篇关于微服务的文章也介绍了那么多了,在构建微服务的过程中确实需要这么一个东西,即便不是在构建微服务,那么在构建分布式应用 ...
- .NET Core 事件总线,分布式事务解决方案:CAP
背景 相信前面几篇关于微服务的文章也介绍了那么多了,在构建微服务的过程中确实需要这么一个东西,即便不是在构建微服务,那么在构建分布式应用的过程中也会遇到分布式事务的问题,那么 CAP 就是在这样的背景 ...
- .NET Core 事件总线,分布式事务解决方案:CAP 基于Kafka
背景 相信前面几篇关于微服务的文章也介绍了那么多了,在构建微服务的过程中确实需要这么一个东西,即便不是在构建微服务,那么在构建分布式应用的过程中也会遇到分布式事务的问题,那么 CAP 就是在这样的背景 ...
- 分布式事务最终一致性-CAP框架轻松搞定
前言 对于分布式事务,常用的解决方案根据一致性的程度可以进行如下划分: 强一致性(2PC.3PC):数据库层面的实现,通过锁定资源,牺牲可用性,保证数据的强一致性,效率相对比较低. 弱一致性(TCC) ...
- ABP EventBus(事件总线)
事件总线就是订阅/发布模式的一种实现 事件总线就是为了降低耦合 1.比如在winform中 到处都是事件 触发事件的对象 sender 事件的数据 e 事件的处理逻辑 方法体 通过E ...
- 一文教你迅速解决分布式事务 XA 一致性问题
欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:腾讯云数据库团队 近日,腾讯云发布了分布式数据库解决方案(DCDB),其最明显的特性之一就是提供了高于开源分布式事务XA的性能.大型 ...
- asp.net core集成CAP(分布式事务总线)
一.前言 感谢杨晓东大佬为社区贡献的CAP开源项目,传送门在此:.NET Core 事件总线,分布式事务解决方案:CAP 以及 如何在你的项目中集成 CAP[手把手视频教程],之前也在工作中遇到分布式 ...
- 分布式事务之最终一致性BASE理论
一.事务 事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败,都将导致整个事务的回滚.简单地说 ...
- MQ关于实现最终一致性分布式事务原理解析
本文讲述阿里云官方文档中关于通过MQ实现分布式事务最终一致性原理 概念介绍 事务消息:消息队列 MQ 提供类似 X/Open XA 的分布式事务功能,通过消息队列 MQ 事务消息能达到分布式事务的最终 ...
随机推荐
- 洛谷 P1714 切蛋糕 单调队列
这个题比较显然,要用前缀和来做.但只用前缀和是过不去的,会TLE,所以需要进行优化. 对于每个前缀和数组 b 中的元素,都可以找到以 b[i] 结尾的子段最大值 p[i],显然,最终的 ans 就是 ...
- Harbor-私有镜像仓库的安装部署
Harbor 安装条件 官网给出了安装需要的最低硬件和软件的条件:https://goharbor.io/docs/2.0.0/install-config/installation-prereqs/ ...
- Unity3D学习笔记9——加载纹理
目录 1. 概述 2. 详论 2.1. Resources方式 2.2. API方式 2.3. Web方式 1. 概述 理论上,Unity中加载纹理并没有什么难度,只需要将图片放置在Assets文件夹 ...
- 思维导图学《On Java》基础卷
说明 原来读过 <Java 编程思想(第 4 版)>,但是这个版本还是基于 Java 5 讲解.由于 Java 8 做出了非常大的改进(是 Java 变化最大的版本),且截止到 2022- ...
- 运维实践-最新Nginx二进制构建编译lua-nginx-module动态链接Lua脚本访问Redis数据库读取静态资源隐式展现
关注「WeiyiGeek」公众号 设为「特别关注」每天带你玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 本章目录 目录 0x0n 前言 ...
- 参考MySQL Internals手册,使用Golang写一个简单解析binlog的程序
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. MySQL作为最流行的开源关系型数据库,有大量的拥趸.其生态已经相当完善,各项特性在圈内都有大量研究.每次新特性发布,都会 ...
- 万答#9,MySQL 中有哪些常用的日志
欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 前 ...
- Apache DolphinScheduler 1.3.6 功能发布说明
参与人员 @chengshiwen.@hailin0.@wanghong1314.@ruanwenjun.@xxjingcd.@zhangguohao.@zhuangchong.@syb8535531 ...
- Luogu1038 神经网络 (拓扑排序)
拓扑排序,裸的,水的. 第一发:题读错,输出错,输入错,到处错 \(\longrightarrow\) 40pts (excuse me ?) 第二发:漏了输入层特判 \(\longrightarro ...
- Luogu2543[AHOI2004]奇怪的字符串 (动态规划 LCS)
04年的省选这么water吗,开个滚动数组算了 #include <iostream> #include <cstdio> #include <cstring> # ...