@

原理

本地事件总线是通过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 event
this.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默认事件总线改造为分布式事件总线的更多相关文章

  1. 深度解读设备的“万能语言”HarmonyOS的分布式软总线能力

    摘要:本文分享鸿蒙分布式软总线,并对相关源代码进行解析,为在鸿蒙系统平台上工作的相关人员的信息参考和指导. 总线是一种内部结构,在计算机系统中,主机的各个部件通过总线相连,外部设备通过相应的接口电路再 ...

  2. [Abp vNext 源码分析] - 13. 本地事件总线与分布式事件总线 (Rabbit MQ)

    一.简要介绍 ABP vNext 封装了两种事件总线结构,第一种是 ABP vNext 自己实现的本地事件总线,这种事件总线无法跨项目发布和订阅.第二种则是分布式事件总线,ABP vNext 自己封装 ...

  3. 源码解析-Abp vNext丨分布式事件总线DistributedEventBus

    前言 上一节咱们讲了LocalEventBus,本节来讲本地事件总线(DistributedEventBus),采用的RabbitMQ进行实现. Volo.Abp.EventBus.RabbitMQ模 ...

  4. .Net Core对于`RabbitMQ`封装分布式事件总线

    首先我们需要了解到分布式事件总线是什么: 分布式事件总线是一种在分布式系统中提供事件通知.订阅和发布机制的技术.它允许多个组件或微服务之间的协作和通信,而无需直接耦合或了解彼此的实现细节.通过事件总线 ...

  5. Solon2 分布式事件总线的技术价值?

    分布式事件总线在分布式开发(或微服务开发)时,是极为重要的架构手段.它可以分解响应时长,可以削峰,可以做最终一致性的分布式事务,可以做业务水平扩展. 1.分解响应时长 比如我们的一个接口处理分为四段代 ...

  6. 分布式消息总线,基于.NET Socket Tcp的发布-订阅框架之离线支持,附代码下载

    一.分布式消息总线以及基于Socket的实现 在前面的分享一个分布式消息总线,基于.NET Socket Tcp的发布-订阅框架,附代码下载一文之中给大家分享和介绍了一个极其简单也非常容易上的基于.N ...

  7. 分享一个分布式消息总线,基于.NET Socket Tcp的发布-订阅框架,附代码下载

    一.分布式消息总线 在很多MIS项目之中都有这样的需求,需要一个及时.高效的的通知机制,即比如当使用者A完成了任务X,就需要立即告知使用者B任务X已经完成,在通常的情况下,开发人中都是在使用者B所使用 ...

  8. 【第二篇】学习 android 事件总线androidEventbus之异步事件的传递

    1,不同Activity直接发送Ansy的事件,以及其他任何事件,必须通过 postSticky方式来进行事件的传递,而不能通过post的形式来进行传递:EventBus.getDefault().p ...

  9. javaScript事件机制深入学习(事件冒泡,事件捕获,事件绑定方式,移除事件方式,阻止浏览器默认行为,事件委托,模拟浏览器事件,自定义事件)

    前言 JavaScript与HTML之间的交互是通过事件实现的.事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间.可以使用侦听器(或处理程序)来预订事件,以便事件发生时执行相应的代码.这种在传统软 ...

  10. jQuery 学习笔记(5)(事件绑定与解绑、事件冒泡与事件默认行为、事件的自动触发、自定义事件、事件命名空间、事件委托、移入移出事件)

    1.事件绑定: .eventName(fn) //编码效率略高,但部分事件jQuery没有实现 .on(eventName, fn) //编码效率略低,所有事件均可以添加 注意点:可以同时添加多个相同 ...

随机推荐

  1. win11安装ubuntu(by wsl2)

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本机情况 省吃俭用入手了ThinkPad T14,打算 ...

  2. PHPStudy hosts文件可能不存在或被阻止打开及同步hosts失败问题

    在使用PHPStudy建站包时,有时会遇到同步hosts失败的问题,可能是因为hosts文件不存在或被阻止打开.这个问题通常可以通过以下几个步骤解决: 步骤一:检查hosts文件是否存在 首先,我们需 ...

  3. Python socket实现简单聊天,同步输入和接收消息

    查的资料很多都是必须等待接收数据后才能再次输入.做了修改,使用多线程的形式,实现一边输入,一边接收 服务端代码 import socket import threading import sys im ...

  4. MySQL系列之优化——1.优化哲学、2. 优化工具的使用、3. 优化思路分解、4. MySQL参数优化测试、5.1 参数优化、6. 参数优化结果、7. 锁的监控及处理、8. 主从优化

    文章目录 1.优化哲学 1.1 为什么优化? 1.2 优化风险 1.3 谁参与优化 1.4 优化方向 1.5 优化的范围及思路 优化效果和成本的评估: 2. 优化工具的使用 2.1 系统层面的 2.1 ...

  5. Ubuntu更新软件的命令

    更新软件源 apt-get update 更新升级所有软件 apt-get upgrade 更新某个软件 apt-get upgrade 名 列出可更新的软件 apt list --upgradabl ...

  6. CF1363C

    题目简化和分析: 首先注意特判 $ x $ 在叶子节点上 ( 即度为 \(1\) ). 因为每人都采用最优策略所以不可能有人执着的为别人开路. 就是不在同一颗子树上挣扎,会从外围不断清理. 但是每步必 ...

  7. MMKV源码解读与理解

    概述 通过 mmap 技术实现的高性能通用 key-value 组件.同时选用 protobuf 协议,进一步压缩数据存储. 标准 protobuf 不提供增量更新的能力,每次写入都必须全量写入.考虑 ...

  8. AI图形算法的应用之一:仪表识别

    目前AI智能算法如火如荼,各大型厂商对此趋之若鹜般加大开发力度,比如大华.海康这些视频处理类,以及百度.腾讯这些IT软件厂商,因为业务开展需要,我也把研发方向转向于此,小有成绩,在此展示一下. 最近研 ...

  9. LocalDateTime、LocalDate、Date、String相互转化大全及其注意事项

    一.前言 大家在开发过程中必不可少的和日期打交道,对接别的系统时,时间日期格式不一致,每次都要转化! 每次写完就忘记了,小编专门来整理一篇来详细说一下他们四个的转换的方法,方便后面使用!! 二.Loc ...

  10. SpringBoot如何缓存方法返回值?

    目录 Why? HowDo annotation MethodCache MethodCacheAspect controller SpringCache EnableCaching Cacheabl ...