• 动手造轮子:实现简单的 EventQueue


    摘自:https://www.cnblogs.com/weihanli/p/implement-event-queue.html

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

    Copy
    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 定义:

    Copy
    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:

    Copy
    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 定义:

    Copy
    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:

    Copy
    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:

    Copy
    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:

    Copy
    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 扩展方法:

    Copy
    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 记录请求信息:

    Copy
    public class PageViewEvent : EventBase
    {
        public string Path { get; set; }
    }

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

    定义 PageViewEventHandler,处理 PageViewEvent

    Copy
    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 里什么都没做只是输出一个日志

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

    Copy
    // 发布 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 配置:

    Copy
    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#

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

    1. Consumer 消息逻辑,现在的实现有些问题,我们的应用场景目前比较简单还可以满足,如果事件比较多就会而且每个事件可能处理需要的时间长短不一样,会导致在一个批次中执行的 Event 中已经完成的事件要等待其他还没完成的事件完成之后才能继续取下一个事件,理想的消费模式应该是各个队列相互独立,在同一个队列中保持顺序消费即可
    2. 上面示例的 EventStore 的实现只是简单的实现了一个事件一个 Handler 的处理情况,实际业务场景中很可能会有一个事件需要多个 Handler 的情况
    3. 这个实现是基于内存的,如果要在分布式场景下使用就不适用了,需要自己实现一下基于redis或者数据库的以满足分布式的需求
    4. and more...

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

    Reference#

    作者: WeihanLi

    出处:https://www.cnblogs.com/weihanli/p/implement-event-queue.html

    版权:本站使用「CC BY 4.0」创作共享协议,转载请在文章明显位置注明作者及出处。

     
    分类: .NET, C#
    标签: C#
  • 相关阅读:
    插件化架构深入剖析<一>-----插庄式实现Activity跳转机制剖析
    网易云音乐动态式换肤框架分析与手写实现<三>
    网易云音乐动态式换肤框架分析与手写实现<二>
    网易云音乐动态式换肤框架分析与手写实现<一>
    跨进程架构HermesEventBus原理分析到手写实现<三>
    在eclipse里用jdbc连接MySQL
    jdk环境变量配置
    oracle设置主键自增
    关于Navicat连接oralcle出现Cannot load OCI DLL 87,126,193 ,ORA-28547等错误
    Oracle 11g 安装过程及测试方法
  • 原文地址:https://www.cnblogs.com/xichji/p/11925850.html
Copyright © 2020-2023  润新知