动手造轮子:实现简单的 EventQueue
动手造轮子:实现简单的 EventQueue
Intro
最近项目里有遇到一些并发的问题,想实现一个队列来将并发的请求一个一个串行处理,可以理解为使用消息队列处理并发问题,之前实现过一个简单的 EventBus,于是想在 EventBus 的基础上改造一下,加一个队列,改造成类似消息队列的处理模式。消息的处理(Consumer)直接使用 .netcore 里的 IHostedService 来实现了一个简单的后台任务处理。
初步设计
- Event 抽象的事件
- EventHandler 处理 Event 的方法
- EventStore 保存订阅 Event 的 EventHandler
- EventQueue 保存 Event 的队列
- EventPublisher 发布 Event
- EventConsumer 处理 Event 队列里的 Event
- EventSubscriptionManager 管理订阅 Event 的 EventHandler
实现代码
EventBase 定义了基本事件信息,事件发生时间以及事件的id:
public abstract class EventBase
{
[JsonProperty]
public DateTimeOffset EventAt { get; private set; }
[JsonProperty]
public string EventId { get; private set; }
protected EventBase()
{
this.EventId = GuidIdGenerator.Instance.NewId();
this.EventAt = DateTimeOffset.UtcNow;
}
[JsonConstructor]
public EventBase(string eventId, DateTimeOffset eventAt)
{
this.EventId = eventId;
this.EventAt = eventAt;
}
}
EventHandler 定义:
public interface IEventHandler
{
Task Handle(IEventBase @event);
}
public interface IEventHandler<in TEvent> : IEventHandler where TEvent : IEventBase
{
Task Handle(TEvent @event);
}
public class EventHandlerBase<TEvent> : IEventHandler<TEvent> where TEvent : EventBase
{
public virtual Task Handle(TEvent @event)
{
return Task.CompletedTask;
}
public Task Handle(IEventBase @event)
{
return Handle(@event as TEvent);
}
}
EventStore:
public class EventStore
{
private readonly Dictionary<Type, Type> _eventHandlers = new Dictionary<Type, Type>();
public void Add<TEvent, TEventHandler>() where TEventHandler : IEventHandler<TEvent> where TEvent : EventBase
{
_eventHandlers.Add(typeof(TEvent), typeof(TEventHandler));
}
public object GetEventHandler(Type eventType, IServiceProvider serviceProvider)
{
if (eventType == null || !_eventHandlers.TryGetValue(eventType, out var handlerType) || handlerType == null)
{
return null;
}
return serviceProvider.GetService(handlerType);
}
public object GetEventHandler(EventBase eventBase, IServiceProvider serviceProvider) =>
GetEventHandler(eventBase.GetType(), serviceProvider);
public object GetEventHandler<TEvent>(IServiceProvider serviceProvider) where TEvent : EventBase =>
GetEventHandler(typeof(TEvent), serviceProvider);
}
EventQueue 定义:
public class EventQueue
{
private readonly ConcurrentDictionary<string, ConcurrentQueue<EventBase>> _eventQueues =
new ConcurrentDictionary<string, ConcurrentQueue<EventBase>>();
public ICollection<string> Queues => _eventQueues.Keys;
public void Enqueue<TEvent>(string queueName, TEvent @event) where TEvent : EventBase
{
var queue = _eventQueues.GetOrAdd(queueName, q => new ConcurrentQueue<EventBase>());
queue.Enqueue(@event);
}
public bool TryDequeue(string queueName, out EventBase @event)
{
var queue = _eventQueues.GetOrAdd(queueName, q => new ConcurrentQueue<EventBase>());
return queue.TryDequeue(out @event);
}
public bool TryRemoveQueue(string queueName)
{
return _eventQueues.TryRemove(queueName, out _);
}
public bool ContainsQueue(string queueName) => _eventQueues.ContainsKey(queueName);
public ConcurrentQueue<EventBase> this[string queueName] => _eventQueues[queueName];
}
EventPublisher:
public interface IEventPublisher
{
Task Publish<TEvent>(string queueName, TEvent @event)
where TEvent : EventBase;
}
public class EventPublisher : IEventPublisher
{
private readonly EventQueue _eventQueue;
public EventPublisher(EventQueue eventQueue)
{
_eventQueue = eventQueue;
}
public Task Publish<TEvent>(string queueName, TEvent @event)
where TEvent : EventBase
{
_eventQueue.Enqueue(queueName, @event);
return Task.CompletedTask;
}
}
EventSubscriptionManager:
public interface IEventSubscriptionManager
{
void Subscribe<TEvent, TEventHandler>()
where TEvent : EventBase
where TEventHandler : IEventHandler<TEvent>;
}
public class EventSubscriptionManager : IEventSubscriptionManager
{
private readonly EventStore _eventStore;
public EventSubscriptionManager(EventStore eventStore)
{
_eventStore = eventStore;
}
public void Subscribe<TEvent, TEventHandler>()
where TEvent : EventBase
where TEventHandler : IEventHandler<TEvent>
{
_eventStore.Add<TEvent, TEventHandler>();
}
}
EventConsumer:
public class EventConsumer : BackgroundService
{
private readonly EventQueue _eventQueue;
private readonly EventStore _eventStore;
private readonly int maxSemaphoreCount = 256;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger _logger;
public EventConsumer(EventQueue eventQueue, EventStore eventStore, IConfiguration configuration, ILogger<EventConsumer> logger, IServiceProvider serviceProvider)
{
_eventQueue = eventQueue;
_eventStore = eventStore;
_logger = logger;
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using (var semaphore = new SemaphoreSlim(Environment.ProcessorCount, maxSemaphoreCount))
{
while (!stoppingToken.IsCancellationRequested)
{
var queues = _eventQueue.Queues;
if (queues.Count > 0)
{
await Task.WhenAll(
queues
.Select(async queueName =>
{
if (!_eventQueue.ContainsQueue(queueName))
{
return;
}
try
{
await semaphore.WaitAsync(stoppingToken);
//
if (_eventQueue.TryDequeue(queueName, out var @event))
{
var eventHandler = _eventStore.GetEventHandler(@event, _serviceProvider);
if (eventHandler is IEventHandler handler)
{
_logger.LogInformation(
"handler {handlerType} begin to handle event {eventType}, eventId: {eventId}, eventInfo: {eventInfo}",
eventHandler.GetType().FullName, @event.GetType().FullName,
@event.EventId, JsonConvert.SerializeObject(@event));
try
{
await handler.Handle(@event);
}
catch (Exception e)
{
_logger.LogError(e, "event {eventId} handled exception", @event.EventId);
}
finally
{
_logger.LogInformation("event {eventId} handled", @event.EventId);
}
}
else
{
_logger.LogWarning(
"no event handler registered for event {eventType}, eventId: {eventId}, eventInfo: {eventInfo}",
@event.GetType().FullName, @event.EventId,
JsonConvert.SerializeObject(@event));
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "error running EventConsumer");
}
finally
{
semaphore.Release();
}
})
);
}
await Task.Delay(50, stoppingToken);
}
}
}
}
为了方便使用定义了一个 Event 扩展方法:
public static IServiceCollection AddEvent(this IServiceCollection services)
{
services.TryAddSingleton<EventStore>();
services.TryAddSingleton<EventQueue>();
services.TryAddSingleton<IEventPublisher, EventPublisher>();
services.TryAddSingleton<IEventSubscriptionManager, EventSubscriptionManager>();
services.AddSingleton<IHostedService, EventConsumer>();
return services;
}
使用示例
定义 PageViewEvent 记录请求信息:
public class PageViewEvent : EventBase
{
public string Path { get; set; }
}
这里作为示例只记录了请求的Path信息,实际使用可以增加更多需要记录的信息
定义 PageViewEventHandler,处理 PageViewEvent
public class PageViewEventHandler : EventHandlerBase<PageViewEvent>
{
private readonly ILogger _logger;
public PageViewEventHandler(ILogger<PageViewEventHandler> logger)
{
_logger = logger;
}
public override Task Handle(PageViewEvent @event)
{
_logger.LogInformation($"handle pageViewEvent: {JsonConvert.SerializeObject(@event)}");
return Task.CompletedTask;
}
}
这个 handler 里什么都没做只是输出一个日志
这个示例项目定义了一个记录请求路径的事件以及一个发布请求记录事件的中间件
// 发布 Event 的中间件
app.Use(async (context, next) =>
{
var eventPublisher = context.RequestServices.GetRequiredService<IEventPublisher>();
await eventPublisher.Publish("pageView", new PageViewEvent() { Path = context.Request.Path.Value });
await next();
});
Startup 配置:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddEvent();
services.AddSingleton<PageViewEventHandler>();// 注册 Handler
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IEventSubscriptionManager eventSubscriptionManager)
{
eventSubscriptionManager.Subscribe<PageViewEvent, PageViewEventHandler>();
app.Use(async (context, next) =>
{
var eventPublisher = context.RequestServices.GetRequiredService<IEventPublisher>();
await eventPublisher.Publish("pageView", new PageViewEvent() { Path = context.Request.Path.Value });
await next();
});
// ...
}
使用效果:

More
注:只是一个初步设计,基本可以实现功能,还是有些不足,实际应用的话还有一些要考虑的事情
Consumer消息逻辑,现在的实现有些问题,我们的应用场景目前比较简单还可以满足,如果事件比较多就会而且每个事件可能处理需要的时间长短不一样,会导致在一个批次中执行的Event中已经完成的事件要等待其他还没完成的事件完成之后才能继续取下一个事件,理想的消费模式应该是各个队列相互独立,在同一个队列中保持顺序消费即可- 上面示例的
EventStore的实现只是简单的实现了一个事件一个Handler的处理情况,实际业务场景中很可能会有一个事件需要多个Handler的情况 - 这个实现是基于内存的,如果要在分布式场景下使用就不适用了,需要自己实现一下基于redis或者数据库的以满足分布式的需求
- and more...
上面所有的代码可以在 Github 上获取,示例项目 Github 地址:https://github.com/WeihanLi/AspNetCorePlayground/tree/master/TestWebApplication
Reference
动手造轮子:实现简单的 EventQueue的更多相关文章
- 动手造轮子:实现一个简单的 EventBus
动手造轮子:实现一个简单的 EventBus Intro EventBus 是一种事件发布订阅模式,通过 EventBus 我们可以很方便的实现解耦,将事件的发起和事件的处理的很好的分隔开来,很好的实 ...
- 动手造轮子:实现一个简单的 AOP 框架
动手造轮子:实现一个简单的 AOP 框架 Intro 最近实现了一个 AOP 框架 -- FluentAspects,API 基本稳定了,写篇文章分享一下这个 AOP 框架的设计. 整体设计 概览 I ...
- 动手造轮子:基于 Redis 实现 EventBus
动手造轮子:基于 Redis 实现 EventBus Intro 上次我们造了一个简单的基于内存的 EventBus,但是如果要跨系统的话就不合适了,所以有了这篇基于 Redis 的 EventBus ...
- h5engine造轮子
基于学习的造轮子,这是一个最简单,最基础的一个canvas渲染引擎,通过这个引擎架构,可以很快的学习canvas渲染模式! 地址:https://github.com/RichLiu1023/h5en ...
- 重新造轮子之静态链接1(Static linking)
最近学习计算机病毒学的过程中,又讲到了静态链接的问题,联想到了之前保健哥在信息安全的课堂上向我们展示了一个没有main()函数的C程序到底应该如何编写.个人觉得这个小实验对于加深静态链接的过程的理解也 ...
- React造轮子:拖拽排序组件「Dragact」
先来一张图看看: 项目地址:Github地址 (无耻求星!) 在线观看(第一次加载需要等几秒):预览地址 说起来不容易,人在国外没有过年一说,但是毕竟也是中国年,虽然不放假,但是家里总会主内一顿丰盛的 ...
- 造轮子系列之RPC 1:如何从零开始开发RPC框架
前言 RPC 框架是后端攻城狮永远都绕不开的知识点,目前业界比较知名有 Dubbo.Spring Cloud 等.很多人都停留在了只会用的阶段,作为程序猿,拥有好奇心深入学习,才能有效提高自己的竞争力 ...
- 【疯狂造轮子-iOS】JSON转Model系列之二
[疯狂造轮子-iOS]JSON转Model系列之二 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇<[疯狂造轮子-iOS]JSON转Model系列之一> ...
- 【疯狂造轮子-iOS】JSON转Model系列之一
[疯狂造轮子-iOS]JSON转Model系列之一 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 之前一直看别人的源码,虽然对自己提升比较大,但毕竟不是自己写的,很容易遗 ...
随机推荐
- 20.Linux进程管理-企业案例
1.管理进程状态 当程序运行为进程后,如果希望停止进程,怎么办呢? 那么此时我们可以使用linux的kill命令对进程发送关闭信号.当然除了kill.还有killall,pkill 1.使用kill ...
- Nginx 了解一下?
这篇文章主要简单的介绍下 Nginx 的相关知识,主要包括以下几部分内容: Nginx 适用于哪些场景? 为什么会出现 Nginx? Nginx 优点 Nginx 的编译与配置 Nginx 适用于哪些 ...
- (day28)操作系统发展史+进程
目录 一.操作系统发展史 (一)穿孔卡片(手工操作) (二)批处理系统(磁带存储) 1. 联机批处理系统 2. 脱机批处理系统 (三)多道技术 二.进程 (一)程序和进程 (二)进程调度 1. 先来先 ...
- 小白初入Python人工智能
想要了解人工智能首先要知道“百度大脑”(https://ai.baidu.com/?track=cp:aipinzhuan|pf:pc|pp:AIpingtai|pu:title|ci:|kw:100 ...
- 设计模式C++描述----01.单例(Singleton)模式
一.概念 单例模式:其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享. class CSingleton { //公有的静态方法,来获取该实例 public: s ...
- spring cloud 2.x版本 Zuul路由网关教程
前言 本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka ...
- springboot(3)——配置文件和自动配置原理详细讲解
原文地址 目录 概述 1. 配置文件作用 2.配置文件位置 3.配置文件的定义 3.1如果是定义普通变量(数字 字符串 布尔) 3.2如果是定义对象.Map 3.3如果是定义数组 4.配置文件的使用 ...
- 使用“反向传播”迭代法求解y=√10
X=√10,求X,也就是求Y=10 =X2 , X是多少. *重要的思想是,如何转化为可迭代求解的算法问题. *解数学问题,第一时间画图,求导,“直线化”. Y = X2 假如已知Y = 10 ,要求 ...
- ctf pwn ida 分析技巧
几年前的笔记,搬运过来 --- 1 先根据运行程序得到的信息命名外围函数,主要函数大写开头 2 /添加注释 3 直接vim程序,修改alarm为isnan可以patch掉alarm函数 4 y 可 ...
- 感谢ZhangYu dalao回关