动手造轮子:实现简单的 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:


   
   
  1. public abstract class EventBase

  2. {

  3. [JsonProperty]

  4. public DateTimeOffset EventAt { get; private set; }

  5. [JsonProperty]

  6. public string EventId { get; private set; }

  7. protected EventBase()

  8. {

  9. this.EventId = GuidIdGenerator.Instance.NewId();

  10. this.EventAt = DateTimeOffset.UtcNow;

  11. }

  12. [JsonConstructor]

  13. public EventBase(string eventId, DateTimeOffset eventAt)

  14. {

  15. this.EventId = eventId;

  16. this.EventAt = eventAt;

  17. }

  18. }

EventHandler 定义:


   
   
  1. public interface IEventHandler

  2. {

  3. Task Handle(IEventBase @event);

  4. }

  5. public interface IEventHandler<in TEvent> : IEventHandler where TEvent : IEventBase

  6. {

  7. Task Handle(TEvent @event);

  8. }

  9. public class EventHandlerBase<TEvent> : IEventHandler<TEvent> where TEvent : EventBase

  10. {

  11. public virtual Task Handle(TEvent @event)

  12. {

  13. return Task.CompletedTask;

  14. }

  15. public Task Handle(IEventBase @event)

  16. {

  17. return Handle(@event as TEvent);

  18. }

  19. }

EventStore:


   
   
  1. public class EventStore

  2. {

  3. private readonly Dictionary<Type, Type> _eventHandlers = new Dictionary<Type, Type>();

  4. public void Add<TEvent, TEventHandler>() where TEventHandler : IEventHandler<TEvent> where TEvent : EventBase

  5. {

  6. _eventHandlers.Add(typeof(TEvent), typeof(TEventHandler));

  7. }

  8. public object GetEventHandler(Type eventType, IServiceProvider serviceProvider)

  9. {

  10. if (eventType == null || !_eventHandlers.TryGetValue(eventType, out var handlerType) || handlerType == null)

  11. {

  12. return null;

  13. }

  14. return serviceProvider.GetService(handlerType);

  15. }

  16. public object GetEventHandler(EventBase eventBase, IServiceProvider serviceProvider) =>

  17. GetEventHandler(eventBase.GetType(), serviceProvider);

  18. public object GetEventHandler<TEvent>(IServiceProvider serviceProvider) where TEvent : EventBase =>

  19. GetEventHandler(typeof(TEvent), serviceProvider);

  20. }

EventQueue 定义:


   
   
  1. public class EventQueue

  2. {

  3. private readonly ConcurrentDictionary<string, ConcurrentQueue<EventBase>> _eventQueues =

  4. new ConcurrentDictionary<string, ConcurrentQueue<EventBase>>();

  5. public ICollection<string> Queues => _eventQueues.Keys;

  6. public void Enqueue<TEvent>(string queueName, TEvent @event) where TEvent : EventBase

  7. {

  8. var queue = _eventQueues.GetOrAdd(queueName, q => new ConcurrentQueue<EventBase>());

  9. queue.Enqueue(@event);

  10. }

  11. public bool TryDequeue(string queueName, out EventBase @event)

  12. {

  13. var queue = _eventQueues.GetOrAdd(queueName, q => new ConcurrentQueue<EventBase>());

  14. return queue.TryDequeue(out @event);

  15. }

  16. public bool TryRemoveQueue(string queueName)

  17. {

  18. return _eventQueues.TryRemove(queueName, out _);

  19. }

  20. public bool ContainsQueue(string queueName) => _eventQueues.ContainsKey(queueName);

  21. public ConcurrentQueue<EventBase> this[string queueName] => _eventQueues[queueName];

  22. }

EventPublisher:


   
   
  1. public interface IEventPublisher

  2. {

  3. Task Publish<TEvent>(string queueName, TEvent @event)

  4. where TEvent : EventBase;

  5. }

  6. public class EventPublisher : IEventPublisher

  7. {

  8. private readonly EventQueue _eventQueue;

  9. public EventPublisher(EventQueue eventQueue)

  10. {

  11. _eventQueue = eventQueue;

  12. }

  13. public Task Publish<TEvent>(string queueName, TEvent @event)

  14. where TEvent : EventBase

  15. {

  16. _eventQueue.Enqueue(queueName, @event);

  17. return Task.CompletedTask;

  18. }

  19. }

EventSubscriptionManager:


   
   
  1. public interface IEventSubscriptionManager

  2. {

  3. void Subscribe<TEvent, TEventHandler>()

  4. where TEvent : EventBase

  5. where TEventHandler : IEventHandler<TEvent>;

  6. }

  7. public class EventSubscriptionManager : IEventSubscriptionManager

  8. {

  9. private readonly EventStore _eventStore;

  10. public EventSubscriptionManager(EventStore eventStore)

  11. {

  12. _eventStore = eventStore;

  13. }

  14. public void Subscribe<TEvent, TEventHandler>()

  15. where TEvent : EventBase

  16. where TEventHandler : IEventHandler<TEvent>

  17. {

  18. _eventStore.Add<TEvent, TEventHandler>();

  19. }

  20. }

EventConsumer:


   
   
  1. public class EventConsumer : BackgroundService

  2. {

  3. private readonly EventQueue _eventQueue;

  4. private readonly EventStore _eventStore;

  5. private readonly int maxSemaphoreCount = 256;

  6. private readonly IServiceProvider _serviceProvider;

  7. private readonly ILogger _logger;

  8. public EventConsumer(EventQueue eventQueue, EventStore eventStore, IConfiguration configuration, ILogger<EventConsumer> logger, IServiceProvider serviceProvider)

  9. {

  10. _eventQueue = eventQueue;

  11. _eventStore = eventStore;

  12. _logger = logger;

  13. _serviceProvider = serviceProvider;

  14. }

  15. protected override async Task ExecuteAsync(CancellationToken stoppingToken)

  16. {

  17. using (var semaphore = new SemaphoreSlim(Environment.ProcessorCount, maxSemaphoreCount))

  18. {

  19. while (!stoppingToken.IsCancellationRequested)

  20. {

  21. var queues = _eventQueue.Queues;

  22. if (queues.Count > 0)

  23. {

  24. await Task.WhenAll(

  25. queues

  26. .Select(async queueName =>

  27. {

  28. if (!_eventQueue.ContainsQueue(queueName))

  29. {

  30. return;

  31. }

  32. try

  33. {

  34. await semaphore.WaitAsync(stoppingToken);

  35. //

  36. if (_eventQueue.TryDequeue(queueName, out var @event))

  37. {

  38. var eventHandler = _eventStore.GetEventHandler(@event, _serviceProvider);

  39. if (eventHandler is IEventHandler handler)

  40. {

  41. _logger.LogInformation(

  42. "handler {handlerType} begin to handle event {eventType}, eventId: {eventId}, eventInfo: {eventInfo}",

  43. eventHandler.GetType().FullName, @event.GetType().FullName,

  44. @event.EventId, JsonConvert.SerializeObject(@event));

  45. try

  46. {

  47. await handler.Handle(@event);

  48. }

  49. catch (Exception e)

  50. {

  51. _logger.LogError(e, "event {eventId} handled exception", @event.EventId);

  52. }

  53. finally

  54. {

  55. _logger.LogInformation("event {eventId} handled", @event.EventId);

  56. }

  57. }

  58. else

  59. {

  60. _logger.LogWarning(

  61. "no event handler registered for event {eventType}, eventId: {eventId}, eventInfo: {eventInfo}",

  62. @event.GetType().FullName, @event.EventId,

  63. JsonConvert.SerializeObject(@event));

  64. }

  65. }

  66. }

  67. catch (Exception ex)

  68. {

  69. _logger.LogError(ex, "error running EventConsumer");

  70. }

  71. finally

  72. {

  73. semaphore.Release();

  74. }

  75. })

  76. );

  77. }

  78. await Task.Delay(50, stoppingToken);

  79. }

  80. }

  81. }

  82. }

为了方便使用定义了一个 Event 扩展方法:


   
   
  1. public static IServiceCollection AddEvent(this IServiceCollection services)

  2. {

  3. services.TryAddSingleton<EventStore>();

  4. services.TryAddSingleton<EventQueue>();

  5. services.TryAddSingleton<IEventPublisher, EventPublisher>();

  6. services.TryAddSingleton<IEventSubscriptionManager, EventSubscriptionManager>();

  7. services.AddSingleton<IHostedService, EventConsumer>();

  8. return services;

  9. }

使用示例

定义 PageViewEvent 记录请求信息:


   
   
  1. public class PageViewEvent : EventBase

  2. {

  3. public string Path { get; set; }

  4. }

这里作为示例只记录了请求的Path信息,实际使用可以增加更多需要记录的信息

定义 PageViewEventHandler,处理 PageViewEvent


   
   
  1. public class PageViewEventHandler : EventHandlerBase<PageViewEvent>

  2. {

  3. private readonly ILogger _logger;

  4. public PageViewEventHandler(ILogger<PageViewEventHandler> logger)

  5. {

  6. _logger = logger;

  7. }

  8. public override Task Handle(PageViewEvent @event)

  9. {

  10. _logger.LogInformation($"handle pageViewEvent: {JsonConvert.SerializeObject(@event)}");

  11. return Task.CompletedTask;

  12. }

  13. }

这个 handler 里什么都没做只是输出一个日志

这个示例项目定义了一个记录请求路径的事件以及一个发布请求记录事件的中间件


   
   
  1. // 发布 Event 的中间件

  2. app.Use(async (context, next) =>

  3. {

  4. var eventPublisher = context.RequestServices.GetRequiredService<IEventPublisher>();

  5. await eventPublisher.Publish("pageView", new PageViewEvent() { Path = context.Request.Path.Value });

  6. await next();

  7. });

Startup 配置:


   
   
  1. public void ConfigureServices(IServiceCollection services)

  2. {

  3. // ...

  4. services.AddEvent();

  5. services.AddSingleton<PageViewEventHandler>();// 注册 Handler

  6. }

  7. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

  8. public void Configure(IApplicationBuilder app, IHostingEnvironment env, IEventSubscriptionManager eventSubscriptionManager)

  9. {

  10. eventSubscriptionManager.Subscribe<PageViewEvent, PageViewEventHandler>();

  11. app.Use(async (context, next) =>

  12. {

  13. var eventPublisher = context.RequestServices.GetRequiredService<IEventPublisher>();

  14. await eventPublisher.Publish("pageView", new PageViewEvent() { Path = context.Request.Path.Value });

  15. await next();

  16. });

  17. // ...

  18. }

使用效果:

More

注:只是一个初步设计,基本可以实现功能,还是有些不足,实际应用的话还有一些要考虑的事情

  1. Consumer 消息逻辑,现在的实现有些问题,我们的应用场景目前比较简单还可以满足,如果事件比较多就会而且每个事件可能处理需要的时间长短不一样,会导致在一个批次中执行的 Event 中已经完成的事件要等待其他还没完成的事件完成之后才能继续取下一个事件,理想的消费模式应该是各个队列相互独立,在同一个队列中保持顺序消费即可

  2. 上面示例的 EventStore 的实现只是简单的实现了一个事件一个 Handler 的处理情况,实际业务场景中很可能会有一个事件需要多个 Handler 的情况

  3. 这个实现是基于内存的,如果要在分布式场景下使用就不适用了,需要自己实现一下基于redis或者数据库的以满足分布式的需求

  4. and more...

上面所有的代码可以在 Github 上获取,示例项目 Github 地址:https://github.com/WeihanLi/AspNetCorePlayground/tree/master/TestWebApplication

Reference

  • https://github.com/WeihanLi/AspNetCorePlayground/tree/master/TestWebApplication/Event

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值