将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 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/259925.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL之表的约束

目录 前言 not null约束 default约束 同时设置not null约束和default约束 comment约束 zerofill约束 primary key约束&#xff08;又称主键约束&#xff09; 复合主键约束 auto_increment约束&#xff08;又称自增长约束&#xff09; unique约束&#xff08;又称唯一…

Redis分布式缓存-Redis持久化

RDB持久化 RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff09;&#xff0c;也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后&#xff0c;从磁盘读取快照文件&#xff0c;恢复数据。快照文件称为RDB文…

cpp_04_类_对象_this指针_常对象_常(成员)函数

1 类 1.1 类的定义 类的作用是抽象事物&#xff08;抽取事物特征&#xff09;的规则。 类的外化表现是用户自定义的复合数据类型&#xff08;包括成员变量、成员函数&#xff09;&#xff1a; 成员变量用于表达事物的属性&#xff0c;成员函数用于表达事物的行为。 类的表现…

力扣79. 单词搜索(java DFS解法)

Problem: 79. 单词搜索 文章目录 题目描述思路解题方法复杂度Code 题目描述 思路 该问题可以归纳为一类遍历二维矩阵的题目&#xff0c;此类中的一部分题目可以利用DFS来解决&#xff0c;具体到本题目&#xff08;该题目可以的写法大体不变可参看前面几个题目&#xff1a;&…

校园圈子交友系统,APP小程序H5,三端源码交付,支持二开!实名认证,大V认证,地图找伴,二手平台!

校园圈子交友系统&#xff0c;是属于自主定义开发的系统&#xff0c;内容有很多&#xff0c;先截取一些给大家看看&#xff0c;让大家更多的了解本系统&#xff0c;然后再做评价&#xff01; 校园后端下载地址&#xff1a;校园圈子系统小程序&#xff0c;校园拼车&#xff0c;校…

Netty Review - StringEncoder字符串编码器和StringDecoder 解码器的使用与源码解读

文章目录 概念概述StringEncoderStringDecoder Code源码分析StringEncoderStringDecoder 小结 概念 概述 Netty是一个高性能的网络应用程序框架&#xff0c;它提供了丰富的功能&#xff0c;包括编解码器&#xff0c;这些编解码器用于在网络中发送和接收数据时进行数据的编码和…

mac电脑安装虚拟机教程

1、准备一台虚拟机&#xff0c;安装CentOS7 常用的虚拟化软件有两种&#xff1a; VirtualBoxVMware 这里我们使用VirtualBox来安装虚拟机&#xff0c;下载地址&#xff1a;Downloads – Oracle VM VirtualBox 001 点击安装 002 报错&#xff1a;he installer has detected an…

uni-app 用于开发H5项目展示饼图,使用ucharts 饼图示例

先下载ucharts H5示例源码&#xff1a; uCharts: 高性能跨平台图表库&#xff0c;支持H5、APP、小程序&#xff08;微信小程序、支付宝小程序、钉钉小程序、百度小程序、头条小程序、QQ小程序、快手小程序、360小程序&#xff09;、Vue、Taro等更多支持canvas的框架平台&#…

在Windows系统平台下部署运行服务端Idea工程的jar服务

前言 目前云原生docker等技术&#xff0c;加上部署流水线大大的简化了各种流程&#xff0c;我们后端开发的人员只需要提交代码后&#xff0c;构建、部署、测试、发布等环节都无需人员接入&#xff0c;完全的自动化交付了。那么你肯定不禁想问&#xff0c;如题的需求不是点击一…

pyCharm 创建一个FastApi web项目,实现接口调用

FastApi和Django区别 我这边演示项目使用的fastApi作为web框架&#xff0c;当然主流一般都是使用Django做web框架&#xff0c;但是Django是一个重量级web框架他有很多组件&#xff0c;如授权&#xff0c;分流等全套web功能。我这边呢只需要有个接口可以被别人调用&#xff0c;…

python 绘制网格图/马赛克图

python 绘制网格图/马赛克图 文章目录 python 绘制网格图/马赛克图前言 前言 python绘制网格并在相应的坐标填充颜色 参考博客 def mplot_intf(t, data):plt.rcParams["figure.figsize"] (t, len(data))plt.rcParams["xtick.major.size"] 0plt.rcParams…

ios微信小程序table头部与左侧固定双重滚动会抖动的坑,解决思路

正常情况是左右滑动时&#xff0c;左侧固定不动&#xff0c;上下滑动时表头不动&#xff1b;而且需求不是完整页面滚动。而是单独这个表滚动&#xff1b; 第一个坑是他有一个ios自带的橡胶上下回弹效果。导致滚动时整个表都跟着回弹&#xff1b; 这个是很好解决。微信开发官网…

Achronix提供由FPGA赋能的智能网卡(SmartNIC)解决方案来打破智能网络性能极限

作者&#xff1a;Achronix 随着人工智能/机器学习&#xff08;AI/ML&#xff09;和其他复杂的、以数据为中心的工作负载被广泛部署&#xff0c;市场对高性能计算的需求持续飙升&#xff0c;对高性能网络的需求也呈指数级增长。高性能计算曾经是超级计算机这样一个孤立的领域&a…

【控制器局域网】CAN报文学习笔记(四)之 字节排序、信号提取实例1

以下面的表格来表示字节顺序和位顺序&#xff0c;用红色表示高位MSB&#xff0c;蓝色表示低位LSB&#xff0c;绿色为LSB到MSB的过度 Bit oderMSB→→→→→→LSBByte oder\Bit7Bit6Bit5Bit4Bit3Bit2Bit1Bit0MSBByte076543210↓Byte115141312111098↓Byte22322212019181716↓By…

谷歌手机安装证书到根目录

1、前提你已经root&#xff0c;安装好面具 2&#xff0c;下载movecert模块&#xff0c;自动帮你把证书从用户证书移动成系统证书 视频教程&#xff0c;手机为谷歌手机 https://www.bilibili.com/video/BV1pG4y1A7Cj?p11&vd_source9c0a32b00d6d59fecae05b4133f22f06 软件下…

【C语言指针专题(4)】指针与一维数组

一、数组名的理解 在之前我们我们使用指针访问数组的时候&#xff0c;使用到了这样一段代码&#xff1a; int arr[10] { 0 }; int* pa &arr[0]; 这里我们使用 &arr[0] 的方式拿到了数组第一个元素的地址&#xff0c;但是其实数组名本来就是地址&#xff0c;而且 是…

使用opencv实现图像中几何图形检测

1 几何图形检测介绍 1.1 轮廓(contours) 什么是轮廓&#xff0c;简单说轮廓就是一些列点相连组成形状、它们拥有同样的颜色、轮廓发现在图像的对象分析、对象检测等方面是非常有用的工具&#xff0c;在OpenCV 中使用轮廓发现相关函数时候要求输入图像是二值图像&#xff0c;这…

麒麟V10 ARM 离线生成RabbitMQ docker镜像并上传Harbor私有仓库

第一步在外网主机执行&#xff1a; docker pull arm64v8/rabbitmq:3.8.9-management 将下载的镜像打包给离线主机集群使用 在指定目录下执行打包命令&#xff1a; 执行&#xff1a; docker save -o rabbitmq_arm3.8.9.tar arm64v8/rabbitmq:3.8.9-management 如果懒得打包…

力扣:77. 组合(回溯, path[:]的作用)

题目&#xff1a; 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ] 示例 2&#xff1a; 输入&…

机器学习数据的清洗,转化,汇总及建模完整步骤(基于Titanic数据集)

目录 介绍&#xff1a; 一、数据 二、检查数据缺失 三、数据分析 四、数据清洗 五、数据类别转化 六、数据汇总和整理 七、建模 介绍&#xff1a; 线性回归是一种常用的机器学习方法&#xff0c;用于建立一个输入变量与输出变量之间线性关系的预测模型。线性回归的目标…