• .Net Core 5.x Api开发笔记 -- 消息队列RabbitMQ实现事件总线EventBus(一)


    本文主要围绕RabbitMQ消息队列和EventBus事件总线做笔记,其中有很多自己的理解和观点,有不对之处还望大神指出,我也学习一下。

    1,消息队列

    什么是消息队列就不说了,这里只介绍为什么用它!!!

    RabbitMQ提供了可靠的消息机制、跟踪机制和灵活的消息路由,支持消息集群和分布式部署。适用于排队算法、秒杀活动、消息分发、异步处理、数据同步、处理耗时任务、CQRS等应用场景。它的以上好处在高并发等三高场景是十分必要的。

    前提:使用RabbitMQ必须考虑高可用性

    1、高可用:如果使用消息队列,基本要配合集群的,因为如果MQ服务器崩了,那就整个服务灾难了。

    2、数据安全:必须保证数据不能丢失,也就是要考虑好最终一致性,做好补偿机制。

    3、合理的消费。

    2,事件总线

    事件总线是对发布-订阅模式的一种实现。它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。

    说人话就是:集中对消息队列中处理的事件进行订阅和绑定管理。

    实现事件总线的关键是:

    • 事件总线维护一个事件源与事件处理的映射字典;
    • 通过单例模式,确保事件总线的唯一入口;
    • 利用反射完成事件源与事件处理的初始化绑定;
    • 提供统一的事件注册、取消注册和触发接口;
     1 public class EventBus
     2 {
     3     /// <summary>
     4     /// EventBus 单例
     5     /// </summary>
     6     public static EventBus Default { get; private set; }
     7 
     8     /// <summary>
     9     /// 事件与事件的处理程序映射
    10     /// </summary>
    11     private static readonly ConcurrentDictionary<Type, List<Type>> m_EventAndHandlersMapping = new ConcurrentDictionary<Type, List<Type>>();
    12 
    13     /// <summary>
    14     /// 加锁对象
    15     /// </summary>
    16     private object m_lockObj;
    17 
    18     static EventBus()
    19     {
    20         Default = new EventBus();
    21     }
    22 
    23     private EventBus()
    24     {
    25         m_lockObj = new object();
    26     }
    27 
    28     /// <summary>
    29     /// 注册事件源和事件处理程序
    30     /// </summary>
    31     /// <typeparam name="TEvent">事件类型</typeparam>
    32     /// <typeparam name="THandler">处理程序类型</typeparam>
    33     public void RegisterHandler<T, THandler>()
    34         where T : EventBase
    35         where THandler : IEventHandler<T>
    36     {
    37         AddMapping(typeof(T), typeof(THandler));
    38     }
    39 
    40     /// <summary>
    41     /// 新增映射 事件源+事件处理程序
    42     /// </summary>
    43     /// <param name="eventType"></param>
    44     /// <param name="handlerType"></param>
    45     private void AddMapping(Type eventType, Type handlerType)
    46     {
    47         var handlers = m_EventAndHandlersMapping.GetOrAdd(eventType, type => new List<Type>());
    48         lock (m_lockObj)
    49         {
    50             if (!handlers.Contains(handlerType))
    51             {
    52                 handlers.Add(handlerType);
    53             }
    54         }
    55     }
    56   
    57     /// <summary>
    58     /// 异步触发事件
    59     /// </summary>
    60     /// <param name="e"></param>
    61     /// <returns></returns>
    62     public async Task TriggerAsync<TEvent>(TEvent e)
    63         where TEvent : EventBase
    64     {
    65         List<Type> value;
    66         if (m_EventAndHandlersMapping.TryGetValue(e.GetType(), out value))
    67         {
    68             foreach (var handler in value.OrderBy(p => p.Name))
    69             {
    70                 var instance = IocManager.ServiceProvider.GetService(handler) as IEventHandler<TEvent>;
    71                 await instance?.HandleEvent(e);
    72             }
    73         }
    74     }
    75 }

    上边就是一个简单的事件总线管理类,主要任务就是对一个事件源与事件处理的映射,包括事件处理程序的触发。

    3,事件源+事件处理

    为了集中对事件进行处理,就需要进一步提取基类来处理,如下所示:

     1 /// <summary>
     2 /// 事件处理程序基类
     3 /// </summary>
     4 public interface IBaseEventHandler
     5 {
     6 }
     7 
     8 /// <summary>
     9 /// 事件处理程序
    10 /// 解释:where T : EventBase 表示传入的类型必须继承 EventBase类
    11 /// </summary>
    12 public interface IEventHandler<T> : IBaseEventHandler
    13     where T : EventBase
    14 {
    15     /// <summary>
    16     /// 处理事件
    17     /// </summary>
    18     /// <param name="e">事件传递的数据</param>
    19     Task HandleEvent(T e);
    20 }

    然后还需要声明一个事件基类来传递数据信息,这里声明一个强类型的带数据的事件

     1 /// <summary>
     2 /// 事件基类
     3 /// </summary>
     4 public abstract class EventBase
     5 {
     6     protected EventBase()
     7         : this(null)
     8     { 
     9     }
    10     protected EventBase(object source)
    11     {
    12         this.Source = source;
    13     }
    14     /// <summary>
    15     /// 引发事件的源
    16     /// </summary>
    17     public object Source { get; private set; }
    18 }
    19 
    20 /// <summary>
    21 /// 声明一个强类型的带数据的事件 传递数据
    22 /// </summary>
    23 public class EventWithData<TData> : EventBase
    24 {
    25 
    26     /// <summary>
    27     /// 初始化 EventBase
    28     /// </summary>
    29     /// <param name="data">传递的数据</param>
    30     public EventWithData(TData data)
    31     {
    32         this.Data = data;
    33     }
    34 
    35     /// <summary>
    36     /// 事件传递的数据
    37     /// </summary>
    38     public TData Data { get; private set; }
    39 
    40     /// <summary>
    41     /// 创建 EventWithData'T'
    42     /// </summary>
    43     /// <param name="data">数据</param>
    44     /// <returns></returns>
    45     public static EventWithData<TData> New(TData data)
    46     {
    47         return new EventWithData<TData>(data);
    48     }
    49 }

    到这里关于事件总线的设计就初步完成了,接下来是关于消息队列 发布+订阅的设计。

    作者:PeterZhang
    本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
  • 相关阅读:
    从零开始学习Sencha Touch MVC应用之二
    PHPMailer IIS下的PHP脚本使用GMAIL发送邮件PHP
    php群发邮件
    PHP编辑器
    PHP使用GET传输汉字的编码转换
    转一个手机开发的帖子(来自开发者俱乐部)
    Zend_Mail收发smtp(gmail,163)邮件Zend Framework
    just a simple for a mail() amd get array()
    ecshop二次开发 结构分析和代码研究 呵呵
    如何重置Drupal 7的用户密码
  • 原文地址:https://www.cnblogs.com/peterzhang123/p/14682405.html
Copyright © 2020-2023  润新知