将Abp默认事件总线改造为分布式事件总线
文章目录
- 原理
- 创建分布式事件总线
- 实现自动订阅和事件转发
- 使用
- 启动Redis服务
- 配置
- 传递Abp默认事件
- 传递自定义事件
- 项目地址
原理
本地事件总线是通过Ioc容器来实现的。
IEventBus接口定义了事件总线的基本功能,如注册事件、取消注册事件、触发事件等。
Abp.Events.Bus.EventBus是本地事件总线的实现类,其中私有成员ConcurrentDictionary<Type, List<IEventHandlerFactory>> _handlerFactories
是事件订阅表。通过维护事件订阅表来实现事件处理器的注册和取消注册。当对应类型的事件触发时,通过订阅表查找所有事件处理器,通过Ioc容器来获取处理器实例,然后通过反射来调用事件处理器的"HandleEvent"方法。
创建分布式事件总线
首先,我们需要一个分布式事件总线中间件,用来将事件从本地事件总线转发到分布式事件总线。常用的中间件有RabbitMQ、Kafka、Redis等。
开源社区已经有实现好的库,本项目参考了 wuyi6216/Abp.RemoteEventBus
这里已经定义好了一个分布式事件总线接口
public interface IDistributedEventBus : IDisposable
{void MessageHandle(string topic, string message);void Publish(IDistributedEventData eventData);void Subscribe(string topic);void Unsubscribe(string topic);void UnsubscribeAll();
}
为了兼容本地事件总线,我们需要定义一个分布式事件总线接口,继承自IEventBus接口。
public interface IMultipleEventBus : IDistributedEventBus, IEventBus
{}
实现自动订阅和事件转发
当注册本地事件时,将订阅分布式事件,事件Topic为类型的字符串表现形式
public IDisposable Register(Type eventType, IEventHandlerFactory factory)
{GetOrCreateHandlerFactories(eventType);List<IEventHandlerFactory> currentLists;if (_handlerFactories.TryGetValue(eventType, out currentLists)){lock (currentLists){if (currentLists.Count == 0){//Register to distributed eventthis.Subscribe(eventType.ToString());}currentLists.Add(factory);}}return new FactoryUnregistrar(this, eventType, factory);
}
创建TriggerRemote,此方法用于将本地事件参数打包成为分布式事件消息payload,并发布该消息
public void TriggerRemote(Type eventType, object eventSource, IEventData eventData)
{var exceptions = new List<Exception>();eventData.EventSource = eventSource;try{var payloadDictionary = new Dictionary<string, object>{{ PayloadKey, eventData }};var distributedeventData = new DistributedEventData(eventType.ToString(), payloadDictionary);Publish(distributedeventData);}catch (Exception ex){exceptions.Add(ex);}if (exceptions.Any()){if (exceptions.Count == 1){exceptions[0].ReThrow();}throw new AggregateException("More than one error has occurred while triggering the event: " + eventType, exceptions);}
}
当触发本地事件时,将消息转发至分布式事件总线。
在Trigger方法中调用TriggerRemote,事件状态回调和事件异常回调将不会被转发。
if (!(typeof(DistributedEventBusEvent) == eventType|| typeof(DistributedEventBusEvent).IsAssignableFrom(eventType)|| typeof(DistributedEventMessageHandleExceptionData) == eventType|| typeof(DistributedEventHandleExceptionData) == eventType))
{if (typeof(DistributedEventArgs) != eventType){TriggerRemote(eventType, eventSource, eventData);}
}
在消费端接收到分布式事件消息时,从Topic中解析类型,转发给本地事件。若此类型在本地事件注册过,则将消息反序列化为本地事件参数,然后触发本地事件。
本地事件处理器将触发最终的处理方法。
public virtual void MessageHandle(string topic, string message)
{Logger.Debug($"Receive message on topic {topic}");try{var eventData = _remoteEventSerializer.Deserialize<DistributedEventData>(message);var eventArgs = new DistributedEventArgs(eventData, topic, message);Trigger(this, new DistributedEventBusHandlingEvent(eventArgs));if (!string.IsNullOrEmpty(eventData.Type)){string pattern = @"(.*?)\[(.*?)\]";Match match = Regex.Match(eventData.Type, pattern);if (match.Success){var type = match.Groups[1].Value;var type2 = match.Groups[2].Value;var localTriggerType = typeFinder.Find(c => c.FullName == type).FirstOrDefault();var genericType = typeFinder.Find(c => c.FullName == type2).FirstOrDefault();if (localTriggerType != null && genericType != null){if (localTriggerType.GetTypeInfo().IsGenericType&& localTriggerType.GetGenericArguments().Length == 1&& !genericType.IsAbstract && !genericType.IsInterface){var localTriggerGenericType = localTriggerType.GetGenericTypeDefinition().MakeGenericType(genericType);if (eventData.Data.TryGetValue(PayloadKey, out var payload)){var payloadObject = (payload as JObject).ToObject(localTriggerGenericType);Trigger(localTriggerGenericType, this, (IEventData)payloadObject);}}}}else{var localTriggerType = typeFinder.Find(c => c.FullName == eventData.Type).FirstOrDefault();if (localTriggerType != null && !localTriggerType.IsAbstract && !localTriggerType.IsInterface){if (eventData.Data.TryGetValue(PayloadKey, out var payload)){var payloadObject = (payload as JObject).ToObject(localTriggerType);Trigger(localTriggerType, this, (IEventData)payloadObject);}}}Trigger(this, new DistributedEventBusHandledEvent(eventArgs));}}catch (Exception ex){Logger.Error("Consume remote message exception", ex);Trigger(this, new DistributedEventMessageHandleExceptionData(ex, topic, topic));}
}
使用
DistributedEventBus有不同的实现方式,这里以Redis为例
启动Redis服务
下载Redis并启动服务,使用默认端口6379
配置
生产者和消费者端都需要配置分布式事件总线
首先引用Abp.DistributedEventBus.Redis,并配置Abp模块依赖
[DependsOn(typeof(AbpDistributedEventBusRedisModule))]
在PreInitialize方法中配置Redis连接信息
Configuration.Modules.DistributedEventBus().UseRedis().Configure(setting =>{setting.Server = "127.0.0.1:6379";});
用MultipleEventBus替换Abp默认事件总线
//todo: 事件总线Configuration.ReplaceService(typeof(IEventBus),() => IocManager.IocContainer.Register(Component.For<IEventBus>().ImplementedBy<MultipleEventBus>()));
传递Abp默认事件
我们知道在使用仓储时,Abp会自动触发一些事件,如创建、更新、删除等。我们来测试这些事件是否能通过分布式事件总线来传递。
定义一个实体类,用于传递实体的增删改事件。
public class Person : FullAuditedEntity<int>
{public string Name { get; set; }public int Age { get; set; }public string PhoneNumber { get; set; }}
在消费者端,定义一个事件处理器,用于处理实体的增删改事件。
public class RemoteEntityChangedEventHandler :IEventHandler<EntityUpdatedEventData<Person>>,IEventHandler<EntityCreatedEventData<Person>>,IEventHandler<EntityDeletedEventData<Person>>,ITransientDependency
{void IEventHandler<EntityUpdatedEventData<Person>>.HandleEvent(EntityUpdatedEventData<Person> eventData){var person = eventData.Entity;Console.WriteLine($"Remote Entity Updated - Name:{person.Name}, Age:{person.Age}, PhoneNumber:{person.PhoneNumber}");}void IEventHandler<EntityCreatedEventData<Person>>.HandleEvent(EntityCreatedEventData<Person> eventData){var person = eventData.Entity;Console.WriteLine($"Remote Entity Created - Name:{person.Name}, Age:{person.Age}, PhoneNumber:{person.PhoneNumber}");}void IEventHandler<EntityDeletedEventData<Person>>.HandleEvent(EntityDeletedEventData<Person> eventData){var person = eventData.Entity;Console.WriteLine($"Remote Entity Deleted - Name:{person.Name}, Age:{person.Age}, PhoneNumber:{person.PhoneNumber}");}
}
在生产者端,用IRepository对实体进行增删改操作。
var person = new Person()
{Name = "John",Age = 36,PhoneNumber = "18588888888"};personRepository.Insert(person);var person2 = new Person()
{Name = "John2",Age = 36,PhoneNumber = "18588888889"};
personRepository.Insert(person2);var persons = personRepository.GetAllList();
foreach (var p in persons)
{p.Age += 1;personRepository.Update(p);Console.WriteLine($"Entity Updated - Name:{p.Name}, Age:{p.Age}, PhoneNumber:{p.PhoneNumber}");}
foreach (var p in persons)
{personRepository.Delete(p);Console.WriteLine($"Entity Deleted - Name:{p.Name}, Age:{p.Age}, PhoneNumber:{p.PhoneNumber}");}
运行程序(同时运行消费者端和生产者端),可以看到消费者端打印出了实体的增删改事件。
注意:
分布式事件总线在两个独立系统间传递事件,所以需要定义一个共同的类型对象,用于事件参数的传递。
因此消费者端需要引用生产者端的模块,以便获取共同的类型对象。
public override Assembly[] GetAdditionalAssemblies()
{var clientModuleAssembly = typeof(Person).GetAssembly();return [clientModuleAssembly];
}
传递自定义事件
定义NotificationEventData,用于传递自定义事件。
public class NotificationEventData : EventData
{public int Id { get; set; }public string Title { get; set; }public string Message { get; set; }public bool IsRead { get; set; }
}
在消费者端,定义一个事件处理器,用于处理自定义事件。
public class NotificationEventHandler :IEventHandler<NotificationEventData>, ITransientDependency
{void IEventHandler<NotificationEventData>.HandleEvent(NotificationEventData eventData){Console.WriteLine($"Id: {eventData.Id}");Console.WriteLine($"Title: {eventData.Title}");Console.WriteLine($"Message: {eventData.Message}");Console.WriteLine($"IsRead: {eventData.IsRead}");}
}
在生产者端,触发自定义事件。
var eventBus = IocManager.Instance.Resolve<IEventBus>();eventBus.Trigger<NotificationEventData>(new NotificationEventData()
{Title = "Hi",Message = "Customized definition event test!",Id = 100,IsRead = true,
});
运行程序(同时运行消费者端和生产者端),可以看到消费者端打印出了自定义事件。
项目地址
Github:DistributedEventBus
相关文章:
将Abp默认事件总线改造为分布式事件总线
文章目录 原理创建分布式事件总线实现自动订阅和事件转发 使用启动Redis服务配置传递Abp默认事件传递自定义事件 项目地址 原理 本地事件总线是通过Ioc容器来实现的。 IEventBus接口定义了事件总线的基本功能,如注册事件、取消注册事件、触发事件等。 Abp.Events…...
Jupyter Notebook修改默认工作目录
1、参考修改Jupyter Notebook的默认工作目录_jupyter文件路径-CSDN博客修改配置文件 2.在上述博客内容的基础上,这里不是删除【%USERPROFILE%】而是把这个地方替换为所要设置的工作目录路径, 3.【起始位置】也可以更改为所要设置的工作目录路径&#x…...
高校/企业如何去做数据挖掘呢?
随着近年来人工智能及大数据、云计算进入爆发时期,依托三者进行的数据分析、数据挖掘服务已逐渐成为各行业进行产业升级的载体,缓慢渗透进我们的工作和生活,成为新时代升级版的智能“大案牍术”。 那么对于多数企业来说,如何做数据…...
数据仓库-数据治理小厂实践
一、简介 数据治理贯穿数仓中数据的整个生命周期,从数据的产生、加载、清洗、计算,再到数据展示、应用,每个阶段都需要对数据进行治理,像有些比较大的企业都是有自己的数据治理平台或者会开发一些便捷的平台,对于没有平…...
【C++多线程编程】(五)之 线程生命周期管理join() 与 detach()
在C中,std::thread 类用于创建和管理线程。std::thread 提供了两种主要的方法来控制线程的生命周期:join 和 detach。 detach方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束。join方式&…...
金融信贷场景的风险“要素”与主要“风险点”
目录 要素一:贷款对象 风险点1:为不具备主体资格或主体资格有瑕疵的借款人发放贷款 风险表现: 防控措施: 风险点2:向国家限控行业发放贷款 风险表现: 防控措施: 风险点3:受理不符合准入条件的客户申请 风险表现: 防控措施: 要素二:金额 风险点4:过渡授…...
ubuntu下docker安装,配置python运行环境
参考自: 1.最详细ubuntu安装docker教程 2.使用docker搭建python环境 首先假设已经安装了docker,卸载原来的docker 在命令行中运行: sudo apt-get updatesudo apt-get remove docker docker-engine docker.io containerd runc 安装docker依赖 apt-get…...
在Docker中安装kafka遇到问题记录
命令含义解答: 在docker安装kafka的时候,启动kafka的时候会执行下面语句: docker run -d --log-driver json-file --log-opt max-size100m --log-opt max-file2 --name kafka -p 9092:9092 -e KAFKA_BROKER_ID0 -e KAFKA_ZOOKEEPER_CONNEC…...
aws-waf-cdn 基于规则组的永黑解决方案
1. 新建waf 规则组 2. 为规则组添加规则 根据需求创建不同的规则 3. waf中附加规则组 (此时规则组所有规则都会附加到waf中,但是不会永黑) 此刻,可以选择测试下规则是否生效,测试前确认保护资源绑定无误 4. 创建堆…...
如何实现免费无限流量云同步笔记软件Obsidian?
目录 前言 如何实现免费无限流量云同步笔记软件Obsidian? 一、简介 软件特色演示: 二、使用免费群晖虚拟机搭建群晖Synology Drive服务,实现局域网同步 1 安装并设置Synology Drive套件 2 局域网内同步文件测试 三、内网穿透群晖Synol…...
GPTs | Actions应用案例
上篇文章说道,如何使用创建的GPTs通过API接口去获取外部的一些信息,然后把获取的外部信息返回给ChatGPT让它加工出来,回答你的问题,今天我们就来做一个通俗易懂的小案例,让大家来初步了解一下它的使用法! …...
Python Opencv实践 - 手势音量控制
本文基于前面的手部跟踪功能做一个手势音量控制功能,代码用到了前面手部跟踪封装的HandDetector.这篇文章在这里: Python Opencv实践 - 手部跟踪-CSDN博客文章浏览阅读626次,点赞11次,收藏7次。使用mediapipe库做手部的实时跟踪&…...
关于Selenium的网页对象单元测试的设计模式
写在前面:经过了实践总结一下经验,心得进行一个分享。 首先driver是可以单独抽出来的,变成一个driver函数放在driver.py。 from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver…...
基于多反应堆的高并发服务器【C/C++/Reactor】(上)
(一)初始化服务器端用于监听的套接字 Server.h #pragma once // 初始化监听的套接字 int initListenFd(unsigned short port); Server.c int initListenFd(unsigned short port) {// 1.创建监听的fdint lfd socket(AF_INET, SOCK_STREAM, 0);if(lf…...
腾讯云debian服务器的连接与初始化
目录 1. 远程连接2. 软件下载3. 设置开机自启动 1. 远程连接 腾讯云给的服务器在安装好系统之后,只需要在防火墙里面添加一个白名单(ip 或者域名)就能访问了。 浏览器打开https://www.ipip.net/,在左下角找到自己所用的WIFI的公…...
医保购药小程序:智能合约引领医疗数字革新
在医疗领域,医保购药小程序通过引入智能合约技术,为用户提供更为高效、安全的购药体验。本文将通过简单的智能合约代码示例,深入探讨医保购药小程序如何利用区块链技术中的智能合约,实现医保结算、购药监控等功能,为医…...
神经网络:深度学习优化方法
1.有哪些方法能提升CNN模型的泛化能力 采集更多数据:数据决定算法的上限。 优化数据分布:数据类别均衡。 选用合适的目标函数。 设计合适的网络结构。 数据增强。 权值正则化。 使用合适的优化器等。 2.BN层面试高频问题大汇总 BN层解决了什么问…...
Unity中Shader旋转矩阵(二维旋转矩阵)
文章目录 前言一、旋转矩阵的原理1、我们以原点为中心,旋转坐标轴θ度2、求 P~2x~:3、求P~2y~:4、最后得到 P~2~点 的点阵5、该点阵可以拆分为以下两个矩阵相乘的结果 二、在Shader中,使用该旋转矩阵实现围绕 z 轴旋转1、在属性面板定义 floa…...
前端面试题(计算机网络):options请求方法及使用场景
OPTIONS请求方法及使用场景 回答思路:什么是options请求-->options请求方法-->options使用场景什么是options请求?(浅入)扩展:常见的HTTP请求有什么?扩展:常见的HTTP请求的作用࿱…...
使用docker-compose管理docker服务
使用docker-compose管理docker服务 1,创建docker-compose.yml version: 3 services:javaapp:build: context: ./javaappdockerfile: Dockerfileports:- "9202:9202"- "19202:19202"goapp:build: context: ./goappdockerfile: Dockerfileports…...
Python_Tkinter和OpenCV模拟行星凌日传输光度测定
传输光度测定 在天文学中,当相对较小的天体直接经过较大天体的圆盘和观察者之间时,就会发生凌日。 当小物体移过较大物体的表面时,较大物体会稍微变暗。 最著名的凌日是水星和金星对太阳的凌日。 借助当今的技术,天文学家可以在…...
【安全】使用auparse解析auditd审计日志
使用auparse解析auditd审计日志 1 审计日志特点 查看auditd.log的日志,审计日志的格式如下: typeSYSCALL msgaudit(1703148319.954:11680975): archc000003e syscall2 successyes exit5 a01102430 a10 a21b6 a324 items1 ppid7752 pid7761 auid0 uid0…...
flink watermark 实例分析
WATERMARK 定义了表的事件时间属性,其形式为: WATERMARK FOR rowtime_column_name AS watermark_strategy_expression rowtime_column_name 把一个现有的列定义为一个为表标记事件时间的属性。该列的类型必须为 TIMESTAMP(3)/TIMESTAMP_LTZ(3),且是 sche…...
系列十二(面试)、Java中的GC回收类型有哪些?
一、Java中的GC回收类型 1.1、概述 Java中的GC回收类型主要包含以下几种,即:UseSerialGC、UseParallelGC、UseConcMarkSweepGC、UseParNewGC、UseParallelOldGC、UseG1GC。 1.2、源码...
华为数通方向HCIP-DataCom H12-831题库(多选题:201-220)
第201题 在多集群RR组网中,每个集群中部署了一台RR设备及其客户机,各集群的RR与为非客户机关系,并建立IBGP全连接。以下关于BGP路由反射器发布路由规则的描述,正确的有哪些? A、若某RR从EBGP对等体学到的路由,此RR会传递给其他集群的RR B、若某RR从非客户机IBGP对等体学…...
NLP论文阅读记录 - | 使用GPT对大型文档集合进行抽象总结
文章目录 前言0、论文摘要一、Introduction二.相关工作2.1Summarization2.2 神经网络抽象概括2.2.1训练和测试数据集。2.2.2 评估。 2.3 最先进的抽象摘要器 三.本文方法3.1 查询支持3.2 文档聚类3.3主题句提取3.4 语义分块3.5 GPT 零样本总结 四 实验效果4.1数据集4.2 对比模型…...
华为全屋wifi6蜂鸟套装标准
华为政企42 华为政企 目录 上一篇华为安防监控摄像头下一篇华为企业级无线路由器...
系列二十八、如何在Oracle官网下载JDK的api文档
一、官网下载JDK的api文档 1.1、官网地址 https://www.oracle.com/java/technologies/javase-jdk21-doc-downloads.html 1.2、我分享的api.chm 链接:https://pan.baidu.com/s/1Bf55Fz-eMTErmQDtZZcewQ?pwdyyds 提取码:yyds 1.3、参考 https://ww…...
STM32-ADC模数转换器
目录 一、ADC简介 二、逐次逼近型ADC内部结构 三、STM32内部ADC转换结构 四、ADC基本结构 五、输入通道 六、转换模式 6.1单次转换,非扫描模式 6.2连续转换,非扫描模式 6.3单次转换,扫描模式 6.4连续转换,扫描模式 七、…...
谷歌手机安装证书到根目录
1、前提你已经root,安装好面具 2,下载movecert模块,自动帮你把证书从用户证书移动成系统证书 视频教程,手机为谷歌手机 https://www.bilibili.com/video/BV1pG4y1A7Cj?p11&vd_source9c0a32b00d6d59fecae05b4133f22f06 软件下…...
企业邮箱格式怎么填写才正确/深圳网站seo
刷题路线参考: https://github.com/chefyuan/algorithm-base https://github.com/youngyangyang04/leetcode-master 大家好,我是靠写博客督促自己刷题的老三,这一节我们对线栈和队列。 栈和队列基础 在正式开刷之前,我们先了解一…...
无锡高端网站开发/永久免费客服系统有哪些软件
关注我,和我一起学习新知识数据治理是企业数据建设必不可少的一个环节。好的数据治理体系可以盘活整条数据链路,最大化保障企业数据的采集、存储、计算和使用过程的可控和可追溯。如何构建企业数据治理体系?企业数据治理过程需要注意哪些问题…...
写文章赚稿费的app/廊坊自动seo
ArrayList、Vector、LinkedList的区别 ArrayList与Vector的区别: 1.出现版本: ArrayList:JDK1.2 Vector(老版动态数组实现类):JDK1.0、出现在ArrayList、Collection接口之前 2.无参构造实现(初始化策略不同)&…...
58同城保定网站建设/企业站seo价格
问题 Image和Label数据成对写入TFRecord文件,按理训练过程中读取的Image和Label也应该是一一对应的,但有的时候发现Image和Label并不能匹配。如: 将以下数据写入TFrecord中: Image 1 —— Label 1 Image 2 —— Label 2 Image …...
新势力网站建设/网站优化关键词排名
laravel-modules可以通过模块化的方式进行开发。 另外。我们开发可以不从app里面进行开发 因为app本身也携带了一些laravel的类。以后如果出来laravel 9 或者 laravel10的话 我们升级也好升级。因为我们已经新建了别的模块 效果如下 不需要手动 安装。 首先在 Laravel 项…...
如何做转发文章赚钱的网站/seo北京网站推广
这里是修真院前端小课堂,每篇分享文从 【背景介绍】【知识剖析】【常见问题】【解决方案】【编码实战】【扩展思考】【更多讨论】【参考文献】 八个方面深度解析前端知识/技能,本篇分享的是: 【 css中content属性,有什么作用&a…...