EventBus/EventQueue 再思考

Intro

之前写过两篇文章,造轮子系列的 EventBus/EventQueue,回想起来觉得当前的想法有点问题,当时对 EvenStore 可能有点误解,有兴趣可以参考 https://www.cnblogs.com/weihanli/p/implement-a-simple-event-bus.html/https://www.cnblogs.com/weihanli/p/implement-event-queue.html

最近把 Event 相关的逻辑做了一个重构,修改 EventStore,引入了 IEventHandlerFactory,重新设计了 Event 相关的组件

重构后的 Event

  • Event: 事件的抽象定义
  • EventHandler:事件处理器抽象定义
  • EventHandlerFactory:事件处理器工厂,用来根据事件类型获取事件处理器(新增)
  • EventPublisher:事件发布器,用于事件发布
  • EventSubscriber:事件订阅器,用于管理事件的订阅
  • EventSubscriptionManager:事件订阅管理器,在 EventSubscriber 的基础上增加了一个根据事件类型获取事件订阅器类型的方法
  • EventBus:事件总线,由 EventPubliser 和 EventSubscriber 组合而成,用来比较方便的做事件发布和订阅
  • EventQueue:事件队列,希望某些消息顺序处理的时候可以考虑用 EventQueue 的模式
  • EventStore:事件存储,事件的持久化存储(在之前的版本里,EventStore 实际作用是一个 EventSubscriptionManager,在最近的版本更新中已修改)

以上 EventSubscriberEventSubscriptionManager 一般不直接用,一般用 EventBus 来处理即可

EventHandlerFactory

这次引入了 EventHandlerFactory 用来抽象获取 EventHandler 的逻辑,原来的设计里是在处理 Event 的时候获取 EventHandler 的类型,然后从依赖注入框架中获取或创建新的 event handler 实例之后再调用 EventHandler 的 Handle 方法处理事件,有一些冗余

使用 EventHandlerFactory 之后就可以直接获取一个 EventHandler 实例集合,具体是实例化还是从依赖注入中获取就由 EventHandlerFactory 来决定了,这样就可以对依赖注入很友好,对于基于内存的简单 EventBus 来说,在服务注册之后就不需要再调用 Subscribe 去显式订阅了,因为再注册服务的时候就已经隐式实现了订阅的逻辑,这样实际就不需要 EventSubscriptionManager 来管理订阅了,订阅信息都在依赖注入框架内部,比如说 CounterEvent,要获取它的订阅信息,我只需要从依赖注入框架中获取 IEventHandler<CounterEvent> 的实例即可,实际就代替了原先 “EventStoreInMemory”,现在的 EventSubscriptionManagerInMemory

基于依赖注入的 EventHandlerFactory 定义:

public sealed class DependencyInjectionEventHandlerFactory : IEventHandlerFactory
{
private readonly IServiceProvider _serviceProvider; public DependencyInjectionEventHandlerFactory(IServiceProvider serviceProvider = null)
{
_serviceProvider = serviceProvider ?? DependencyResolver.Current;
} public ICollection<IEventHandler> GetHandlers(Type eventType)
{
var eventHandlerType = typeof(IEventHandler<>).MakeGenericType(eventType);
return _serviceProvider.GetServices(eventHandlerType).Cast<IEventHandler>().ToArray();
}
}

如果不使用依赖注入,也可以根据 IEventSubscriptionManager 订阅信息来实现:

public sealed class DefaultEventHandlerFactory : IEventHandlerFactory
{
private readonly IEventSubscriptionManager _subscriptionManager;
private readonly ConcurrentDictionary<Type, ICollection<IEventHandler>> _eventHandlers = new ConcurrentDictionary<Type, ICollection<IEventHandler>>();
private readonly IServiceProvider _serviceProvider; public DefaultEventHandlerFactory(IEventSubscriptionManager subscriptionManager, IServiceProvider serviceProvider = null)
{
_subscriptionManager = subscriptionManager;
_serviceProvider = serviceProvider ?? DependencyResolver.Current;
} public ICollection<IEventHandler> GetHandlers(Type eventType)
{
var eventHandlers = _eventHandlers.GetOrAdd(eventType, type =>
{
var handlerTypes = _subscriptionManager.GetEventHandlerTypes(type);
var handlers = handlerTypes
.Select(t => (IEventHandler)_serviceProvider.GetServiceOrCreateInstance(t))
.ToArray();
return handlers;
});
return eventHandlers;
}
}

EventQueue Demo

来看一下 EventQueue 的示例,示例基于 asp.net core 的,定义了一个 HostedService 来实现一个 EventConsumer 来消费 EventQueue 中的事件信息

EventConsumer 定义如下:

public class EventConsumer : BackgroundService
{
private readonly IEventQueue _eventQueue;
private readonly IEventHandlerFactory _eventHandlerFactory; public EventConsumer(IEventQueue eventQueue, IEventHandlerFactory eventHandlerFactory)
{
_eventQueue = eventQueue;
_eventHandlerFactory = eventHandlerFactory;
} protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var queues = await _eventQueue.GetQueuesAsync();
if (queues.Count > 0)
{
await queues.Select(async q =>
{
var @event = await _eventQueue.DequeueAsync(q);
if (null != @event)
{
var handlers = _eventHandlerFactory.GetHandlers(@event.GetType());
if (handlers.Count > 0)
{
await handlers
.Select(h => h.Handle(@event))
.WhenAll()
;
}
}
})
.WhenAll()
;
} await Task.Delay(1000, stoppingToken);
}
}
}

定义 PageViewEventPageViewEventHandler,用来记录和处理请求访问记录

public class PageViewEvent : EventBase
{
} public class PageViewEventHandler : EventHandlerBase<PageViewEvent>
{
public static int Count; public override Task Handle(PageViewEvent @event)
{
Interlocked.Increment(ref Count);
return Task.CompletedTask;
}
}

事件很简单,事件处理也只是增加了 PageViewEventHandler 内定义的 Count。

服务注册:

// 注册事件核心组件
// 会注册 EventBus、EventHandlerFactory、EventQueue 等
services.AddEvents()
// 注册 EventHanlder
.AddEventHandler<PageViewEvent, PageViewEventHandler>()
;
// 注册 EventQueuePubliser,默认注册的 IEventPublisher 是 EventBus
services.AddSingleton<IEventPublisher, EventQueuePublisher>();
// 注册 EventConsumer
services.AddHostedService<EventConsumer>();

事件发布,定义了一个中间件来发布 PageViewEvent,定义如下:

// pageView middleware
app.Use((context, next) =>
{
var eventPublisher = context.RequestServices.GetRequiredService<IEventPublisher>();
eventPublisher.Publish(new PageViewEvent()); return next();
});

然后定义一个接口来获取上面定义的 PageViewEventHandler 中的 Count

[Route("api/[controller]")]
public class EventsController : ControllerBase
{
[HttpGet("pageViewCount")]
public IActionResult Count()
{
return Ok(new { Count = PageViewEventHandler.Count });
}
}

运行起来之后,访问几次接口,看上面的接口返回 Count 是否会增加,正常的话每访问一次接口就会增加 1,并发访问问题也不大,因为每个事件都是顺序处理的,即使并发访问也没有关系,事件发布之后,在队列里都是顺序处理的,这也就是引入事件队列的目的(好像上面的原子递增没什么用了...) 如果没看到了增加,稍等一会儿再访问试试,事件处理会迟到,但总会处理,毕竟是异步处理的,有些延迟很正常,而且上面我们还有一个 1s 的延迟

More

更多关于上述 Event 相关的信息可以参考代码: https://github.com/WeihanLi/WeihanLi.Common/tree/dev/src/WeihanLi.Common/Event

作者水平有限,如果上述有哪些不对的地方还望指出,万分感谢

Reference

EventBus/EventQueue 再思考的更多相关文章

  1. HDU 5135(再思考)

    题意略. 思路:再思考后发现,为了构造出最大的三角形面积和,我们应该尽量让长的棍子相组合,这样构造出的三角形面积和最大,贪心能解. #include<bits/stdc++.h> usin ...

  2. EventBus 及一些思考

    EventBus 是 Android 开发的一种常用框架,其解耦的思维令人赞叹 从特性上来讲,其与 Android SDK中的BroadcastReceiver很像,二者都是注册,发送事件,反注册,都 ...

  3. Web系统开发构架再思考-前后端的完全分离

    前言 前后端完全分离其实一直是Web开发人员的梦想,也一直是我的梦想,遥想当年,无论是直接在代码里面输出HTML,还是在HTML里面嵌入各种代码,都不能让人感到满意.期间的痛苦和纠结,我想所有Web开 ...

  4. GPU计算的十大质疑—GPU计算再思考

    http://blog.csdn.NET/babyfacer/article/details/6902985 原文链接:http://www.hpcwire.com/hpcwire/2011-06-0 ...

  5. 开源应用框架BitAdminCore重构再思考

    索引 NET Core应用框架之BitAdminCore框架应用篇系列 框架演示:https://www.bitadmincore.com 框架源码:https://github.com/chenyi ...

  6. 【原】关于AdaBoost的一些再思考

    一.Decision Stumps: Decision Stumps称为单层分类器,主要用作Ensemble Method的组件(弱分类器).一般只进行一次判定,可以包含两个或者多个叶结点.对于离散数 ...

  7. 对 API 平台的再思考【eolink翻译】

    API 是推动现代企业数字化转型的基础.它不但连接了内部应用程序.合作伙伴和客户,同时也快速持续地向市场提供了各种新产品.版本和功能. 但当下还是以集中式的 API 交付为主.一个企业的对外 API ...

  8. 【转】【翻译】对响应式SVG的再思考

    来源: http://www.w3ctech.com/topic/1555 原文地址:http://www.smashingmagazine.com/2014/03/rethinking-respon ...

  9. vijos p1523 贪吃的九头龙 思考思考再思考,就荒废了4小时

    树形DP要有自己的风格,转二叉树是基础,考虑边界最头疼. #include<cstdio> #include<cstring> #include<algorithm> ...

随机推荐

  1. WSFC与DFS

    WSFC里面的文件服务器群集,文件始终是一份,数据始终存放在群集磁盘中,通过群集来维持文件服务器这项服务始终持续可用,在2012之前同一时间WSFC只能有一台节点对外提供文件服务,2012开始群集引入 ...

  2. 《JavaScript和jQuery实战手册(原书第2版)》——2.1节语句

    2.1 语句JavaScript语句是基本的编程单元,通常表示JavaScript程序中的单个步骤.可以把一条语句看做一个句子一样,就好像成串的句子一起组成一个段落(或一章,或一本书)一样,把语句组合 ...

  3. ELK收集日志到mysql数据库

    场景需求 在使用ELK对日志进行收集的时候,如果需要对数据进行存档,可以考虑使用数据库的方式.为了便于查询,可以同时写一份数据到Elasticsearch 中. 环境准备 CentOS7系统: 192 ...

  4. opencv-11-中值滤波及自适应中值滤波

    开始之前 在上一篇我们实现了读取噪声图像, 然后 进行三种形式的均值滤波得到结果, 由于我们自己写的均值滤波未作边缘处理, 所以效果有一定的下降, 但是总体来说, 我们得到的结果能够说明我们的算法执行 ...

  5. 算法竞赛进阶指南--快速幂,求a^b mod p

    // 快速幂,求a^b mod p int power(int a, int b, int p) { int ans = 1; for (; b; b >>= 1) { if (b &am ...

  6. 老师,你确定Java注释不会被执行吗?

    之前在博客上分享过一篇文章,涉及到 Java 中的注释,就信誓旦旦地写了一句话:"注释是不会被执行的!"结果,有小伙伴留言说,"老师,你确定吗?" 我这个人一直 ...

  7. jmeter4.0,启动jmeter.bat闪退问题

    问题描述: 电脑重装win10系统,配置好了java环境后,解压jmeter的zip包,然后按照网上的教程配置环境变量,然后兴冲冲启动jmeter.bat,结果,闪退,甚至连个报错信息都没有... 然 ...

  8. B - Save the problem! CodeForces - 867B 构造题

    B - Save the problem! CodeForces - 867B 这个题目还是很简单的,很明显是一个构造题,但是早训的时候脑子有点糊涂,想到了用1 2 来构造, 但是去算这个数的时候算错 ...

  9. G - Queue HDU - 5493 线段树+二分

    G - Queue HDU - 5493 题目大意:给你n个人的身高和这个人前面或者后面有多少个比他高的人,让你还原这个序列,按字典序输出. 题解: 首先按高度排序. 设每个人在其前面有k个人,设比这 ...

  10. JS 究竟是先有鸡还是有蛋,Object与Function究竟谁出现的更早,Function算不算Function的实例等问题杂谈

    壹 ❀ 引 我在JS 疫情宅在家,学习不能停,七千字长文助你彻底弄懂原型与原型链一文中介绍了JavaScript原型与原型链,以及衍生的__proto__.constructor等一系列属性.在解答了 ...