.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
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。