@

原理

本地事件总线是通过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. 为何每个开发者都在谈论Go?

    本文深入探讨了Go语言的多个关键方面,从其简洁的语法.强大的并发支持到出色的性能优势,进一步解析了Go在云原生领域的显著应用和广泛的跨平台支持.文章结构严谨,逐一分析了Go语言在现代软件开发中所占据的 ...

  2. 代码随想录算法训练营第二十八天| 93.复原IP地址 78.子集 90.子集II

      93.复原IP地址 卡哥建议:本期本来是很有难度的,不过 大家做完 分割回文串 之后,本题就容易很多了 题目链接/文章讲解:https://programmercarl.com/0093.%E5% ...

  3. DesignPattern-part1

    title: "modern C++ DesignPattern-Part1" date: 2018-04-03T16:06:33+08:00 lastmod: 2018-04-0 ...

  4. 基于Spring事务的可靠异步调用实践

    SpringTxAsync组件是仓储平台组(WMS6)自主研发的一个专门用于解决可靠异步调用问题的组件. 通过使用SpringTxAsync组件,我们成功地解决了在仓储平台(WMS6)中的异步调用需求 ...

  5. Python 实现Word转PDF

    通过将 Word 文档转换为 PDF,您可以确保文档在不同设备上呈现一致,并防止其他人对文档内容进行非授权修改.此外,在你需要打印文档时,转换为PDF还能确保打印输出的准确性.本文将介绍如何使用Pyt ...

  6. 算法2:寻找吸血鬼数(JS)

    任务二:寻找吸血鬼数 打印所有4位吸血鬼数和它们的獠牙   提示:一共有7个: 吸血鬼数: -该鬼的位数为偶数: -该数的所有位中.是0的位少一半: -该数每一位上的数字重新组合为两个位数相等的数,乘 ...

  7. 其它-Supervisor的使用

    文章目录 Supervisor 的使用 一 Supervisor介绍 二 安装 2.1 安装方式 2.2 验证 2.3 配置 2.4 配置详情(了解) 2.5 启动.停止.重启 三 program 配 ...

  8. TOP GP 把已经编译的per反编回对应版本的4fd(画面档)

    由于GP5.1,5.2,5.3的genero对应版本画面档开发互不兼容,下面提供各版本之间互转的操作方法: xshell切换到要反编译的per档目录,执行以下命令,就会在同目录下生成对应4fd档资料 ...

  9. maven error

    1 [INFO] Assembling webapp [crm9] in [/home/wukongcrm/72crm-java/target/ROOT] 2 [INFO] Processing wa ...

  10. CUDA C编程权威指南:2.2-给核函数计时

      本文主要通过例子介绍了如何给核函数计时的思路和实现.实现例子代码参考文献[7],只需要把相应章节对应的CMakeLists.txt文件拷贝到CMake项目根目录下面即可运行. 1.用CPU计时器计 ...