将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 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}");

}

运行程序(同时运行消费者端和生产者端),可以看到消费者端打印出了实体的增删改事件。

在这里插入图片描述

注意:

分布式事件总线在两个独立系统间传递事件,所以需要定义一个共同的类型对象,用于事件参数的传递。因此消费者端需要引用生产者端的模块,以便获取共同的类型对象。

传递自定义事件

定义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,
});

运行程序(同时运行消费者端和生产者端),可以看到消费者端打印出了自定义事件。

在这里插入图片描述

文章转载自:林晓lx

原文链接:https://www.cnblogs.com/jevonsflash/p/17917031.html

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

<think>我们正在处理ABP框架中的事件总线阻塞问题。事件总线ABP中用于处理应用内的事件通信,但默认情况下可能是同步和阻塞的。这可能导致性能问题,尤其是在高并发场景下。解决方案通常涉及使事件处理异步化,避免阻塞主线程。ABP框架提供了几种机制来实现这一点。参考ABP文档和社区实践,我们可以采用以下方法:1.**使用后台作业**:将事件处理逻辑包装为后台作业,这样事件发布后可以立即返回,而处理在后台线程中执行。ABP提供了`IBackgroundJobManager`来管理后台作业。2.**使用分布式事件总线**:如果事件处理需要较长时间,可以考虑使用分布式事件总线(如RabbitMQ、Kafka)并配合异步消费者。ABP的`RabbitMqDistributedEventBus`可以配置为异步处理。3. **调整事件处理为异步方法**:确保事件处理程序(`IEventHandler`)的方法是异步的(使用`async Task`),并在其中使用`await`调用异步操作,避免阻塞。4. **使用并行处理**:如果事件处理程序有多个且相互独立,可以考虑并行执行。ABP默认是顺序执行事件处理程序,但可以通过自定义事件总线实现并行。5.**限制事件处理程序的执行时间**:对于长时间运行的事件处理程序,可以设置超时,避免长时间阻塞。下面是一个使用后台作业处理事件的示例:首先,创建一个后台作业类: ```csharppublicclass MyEventBackgroundJob :AsyncBackgroundJob<MyEventData>, ITransientDependency{publicoverride asyncTask ExecuteAsync(MyEventData eventData){//在这里处理事件数据awaitLongRunningTask(eventData);}} ```然后,在事件处理程序中,不直接处理业务逻辑,而是将工作排入后台作业队列: ```csharppublicclass MyEventHandler :IEventHandler<MyEventData>, ITransientDependency{privatereadonly IBackgroundJobManager _backgroundJobManager;publicMyEventHandler(IBackgroundJobManagerbackgroundJobManager){_backgroundJobManager =backgroundJobManager;}publicasync TaskHandleEventAsync(MyEventData eventData){//将事件数据作为参数传递给后台作业await_backgroundJobManager.EnqueueAsync<MyEventBackgroundJob, MyEventData>(eventData);}} ```这样,事件处理程序会立即返回,而实际的处理工作由后台作业执行。另外,对于分布式事件总线,我们可以配置RabbitMQ作为事件总线,并确保消费者是异步的。在ABP中,使用分布式事件总线需要配置模块:在模块的`ConfigureServices`方法中: ```csharpConfigure<AbpRabbitMqOptions>(options=> {options.Connections.Default.HostName= "localhost";options.Connections.Default.Port=5672; });Configure<AbpDistributedEventBusOptions>(options=> {options.BusName ="MyAppBus";options.UseRabbitMq(); //使用RabbitMQ作为分布式事件总线}); ```然后,在事件处理程序中,同样使用异步方法。注意:使用后台作业或分布式事件总线时,需要考虑到事件处理的顺序和可靠性。分布式事件总线可能无法保证顺序,而后台作业则依赖于持久化存储(如数据库)来保证可靠性。以上方法可以帮助解决事件总线阻塞问题。根据具体场景选择合适的方式。相关问题:1. ABP框架中如何配置RabbitMQ作为分布式事件总线?2.如何确保事件处理的顺序性和可靠性?3.在ABP中,如何实现事件处理程序的并行执行?</think>### ABP框架事件总线阻塞问题解决方案 在ABP框架中,事件总线阻塞问题通常发生在同步事件处理程序执行耗时操作时,导致整个调用链被阻塞。以下是系统性的解决方案: #### 1. **异步事件处理(推荐方案)** 将事件处理程序改为异步模式,避免阻塞主线程: ```csharp public class MyAsyncEventHandler : IAsyncEventHandler<MyEventData>, ITransientDependency { public async Task HandleEventAsync(MyEventData eventData) { // 使用异步API调用 await _someService.LongRunningOperationAsync(eventData.Parameter); // 而不是同步调用:_someService.LongRunningOperation(eventData.Parameter); } } ``` **关键点**: - 实现`IAsyncEventHandler<T>`接口而非`IEventHandler<T>` - 所有内部调用使用`async/await`模式 - 避免`.Result`或`.Wait()`等阻塞调用[^1] #### 2. **后台作业队列** 将耗时操作委托给后台任务系统: ```csharp public class MyEventHandler : IEventHandler<MyEventData>, ITransientDependency { private readonly IBackgroundJobManager _jobManager; public MyEventHandler(IBackgroundJobManager jobManager) { _jobManager = jobManager; } public async Task HandleEventAsync(MyEventData eventData) { // 将任务加入后台队列 await _jobManager.EnqueueAsync<MyBackgroundJob, MyJobArgs>( new MyJobArgs { Data = eventData.Value } ); } } ``` 使用Hangfire或Quartz作为后台任务管理器[^1] #### 3. **分布式事件总线** 对于跨服务场景,使用分布式事件总线: ```csharp // 发布事件 await _distributedEventBus.PublishAsync(new MyDistributedEvent { Message = "Non-blocking event" }); // 处理事件 public class MyDistributedEventHandler : IDistributedEventHandler<MyDistributedEvent>, ITransientDependency { public async Task HandleEventAsync(MyDistributedEvent eventData) { // 异步处理逻辑 } } ``` 配置RabbitMQ或Kafka作为消息传输层[^1] #### 4. **性能优化策略 - **超时控制**:在事件处理中设置执行超时 ```csharp using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); await ProcessEventAsync(eventData, cts.Token); ``` - **批处理**:对批量事件使用`IEventBatch<T>`接口 - **错误隔离**:通过`[ExposeServices(typeof(MyEventHandler))]`控制异常传播 #### 5. **监控与诊断 启用ABP的诊断模块: ```json // appsettings.json { "Logging": { "LogLevel": { "Volo.Abp.EventBus": "Debug" } }, "AbpEventBus": { "RetryStrategyOptions": { "MaxRetryAttempts": 3, "RetryInterval": 5000 } } } ``` ### 典型应用场景对比 | 方案 | 适用场景 | 吞吐量 | 复杂度 | |------|----------|--------|--------| | 异步处理 | 单个服务内事件 | 高 | 低 | | 后台作业 | 耗时>1s的操作 | 中 | 中 | | 分布式总线 | 跨微服务通信 | 极高 | 高 | > **最佳实践**:优先采用异步事件处理+后台作业组合方案,分布式事件总线用于系统间通信[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值