IT教程 ·

使用EventBus + Redis公布订阅模式提升营业执行性能

有点长的博客:Redis不是只有get set那么简单

媒介

近来一向奔走于口试,面了几家公司的研发。有让我受益颇多的口试履历,也有让我觉得浪费时候的口试阅历~
由于疫情缘由,近来宅在家里也没事,就想着运用Redis合营事宜总线去完成下详细的营业。

  • 需求

    一个简朴的电商,有几个主要的需求点

    商品下单后TODO

    • 存储定单信息
    • 锁定商品库存
    • 音讯推送商家端

    定单付出后TODO

    • 存储定单付出信息
    • 商品库存削减
    • 音讯推送商家端
    • 会员积分调解

手艺思绪

这里用控制台完成上面的营业功用外,自行编写一个基于C#反射特征的事宜总线,轻易详细营业事宜的后续扩大,比方定单付出后后续还要加会员音讯推送啥的。运用Redis的宣布定阅形式对事宜处置惩罚举行异步化,提拔实行机能。
所以终究手艺架构就是 事宜总线+Redis宣布定阅。

完成事宜总线

这里先不急着将上面的定单、付出、会员 等举行建模。先将事宜总线的架子搭好。起首须要明白事宜总线在营业体系的目标是什么。
事宜总线存在目标最主要的就是解耦 。我们须要完成的效果就是针对指定事宜源对象触发事宜后,凡是注册了该事宜参数的事宜处置惩罚类则入手下手实行相干代码。

下图能够看出我们的事宜处置惩罚类均须要援用事宜参数,一切事宜处置惩罚类都是基于对事宜参数处置惩罚的需求上来的。

使用EventBus + Redis公布订阅模式提升营业执行性能 IT教程 第1张

然则!并非意味建立了事宜处置惩罚类就肯定会去实行!可否实行除了取决于事宜源的触发外就是必需有一层注册(也可称映照)。
在WinForm程序里到处可见事宜的绑定,如 this.button1.OnClick+=button1OnClick;
那末在这里我将绑定事宜安排到一个字典里。C#的字典Dictionary是个key value的键值对数据鸠合,键和值都能够是恣意数据范例。
我们能够将事宜处置惩罚类EventHandle和事宜参数EventData作为键和值存储到字典里。在事宜源触发时依据EventData反向找出一切的EventHandle

思绪就是如许,入手下手编码了。
定义事宜参数接口,后续详细营业的事宜参数接口均要继续它。

    /// <summary>
    /// 事宜参数接口
    /// </summary>
    public interface IEventData
    {
        /// <summary>
        /// 事宜源对象
        /// </summary>
        object Source { get; set; }

        ///// <summary>
        ///// 事宜发作的数据
        ///// </summary>
        //TDataModel Data { get; set; }

        /// <summary>
        /// 事宜发作时候
        /// </summary>
        DateTime Time { get; set; }
    }

须要一个事宜处置惩罚接口,后续详细营业的事宜处置惩罚接口均须要继续它

    /// <summary>
    /// 事宜完成接口
    /// </summary>
    public interface IEventHandle<T> where T : IEventData
    {
        /// <summary>
        /// 处置惩罚品级
        /// 轻易事宜总线触发时刻能够有序的实行响应
        /// </summary>
        /// <returns></returns>
        int ExecuteLevel { get; }

        /// <summary>
        /// 事宜实行
        /// </summary>
        /// <param name="eventData">事宜参数</param>
        void Execute(T eventData);
    }

如今已将事宜参数和事宜处置惩罚都笼统出来了,接下来是要完成上面说的注册容器的完成了。

   /// <summary>
    /// 事宜堆栈
    /// </summary>
    public interface IEventStore
    {
        /// <summary>
        /// 事宜注册
        /// </summary>
        /// <param name="handle">事宜完成对象</param>
        /// <param name="data">事宜参数</param>
        void EventRegister(Type handle, Type data);

        /// <summary>
        /// 事宜作废注册
        /// </summary>
        /// <param name="handle">事宜完成对象</param>
        void EventUnRegister(Type handle);

        /// <summary>
        /// 猎取事宜处置惩罚对象
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        Type GetEventHandle(Type data);

        /// <summary>
        /// 依据事宜参数猎取事宜处置惩罚鸠合
        /// </summary>
        /// <typeparam name="TEventData">事宜参数范例</typeparam>
        /// <param name="data">事宜参数</param>
        /// <returns></returns>
        IEnumerable<Type> GetEventHandleList<TEventData>(TEventData data);
    }

完成上面的接口

    /// <summary>
    /// 基于反射完成的事宜仓储
    /// 存储事宜处置惩罚对象和事宜参数
    /// </summary>
    public class ReflectEventStore : IEventStore
    {
        private static Dictionary<Type, Type> StoreLst;

        public ReflectEventStore()
        {
            StoreLst = new Dictionary<Type, Type>();
        }

        public void EventRegister(Type handle, Type data)
        {
            if (handle == null || data == null) throw new NullReferenceException();
            if (StoreLst.Keys.Contains(handle))
                throw new ArgumentException($"事宜总线已注册范例为{nameof(handle)} !");

            if (!StoreLst.TryAdd(handle, data))
                throw new Exception($"注册{nameof(handle)}范例到事宜总线失利!");
        }


        public void EventUnRegister(Type handle)
        {
            if (handle == null) throw new NullReferenceException();
            StoreLst.Remove(handle);
        }

        public Type GetEventHandle(Type data)
        {
            if (data == null) throw new NullReferenceException();
            Type handle = StoreLst.FirstOrDefault(p => p.Value == data).Key;
            return handle;
        }

        public IEnumerable<Type> GetEventHandleList<TEventData>(TEventData data)
        {
            if (data == null) throw new NullReferenceException();
            var items = StoreLst.Where(p => p.Value == data.GetType())
                                  .Select(k => k.Key);
            return items;
        }
    }

依据上面代码能够看出来,我们存储到Dictionary内的是Type范例,GetEventHandleList要领终究猎取的是一个List<Type>的鸠合。
我们须要鄙人面建立的EventBus类里轮回List<Type>而且实行这个事宜处置惩罚类的Execute要领。

完成EventBus

    /// <summary>
    /// 事宜总线效劳
    /// </summary>
    public class EventBus : ReflectEventStore
    {

        public void Trigger<TEventData>(TEventData data, SortType sort = SortType.Asc) where TEventData : IEventData
        {
            // 这里如需保证次序实行则必需轮回两次 - -....
            var items = GetEventHandleList(data).ToList();
            Dictionary<object, Tuple<Type, int>> ds = new Dictionary<object, Tuple<Type, int>>();

            foreach (var item in items)
            {
                var instance = Activator.CreateInstance(item);
                MethodInfo method = item.GetMethod("get_ExecuteLevel");
                int value = (int)method.Invoke(instance, null);
                ds.Add(instance, new Tuple<Type, int>(item, value));
            }

            var lst = sort == SortType.Asc ? ds.OrderBy(p => p.Value.Item2).ToList() : ds.OrderByDescending(p => p.Value.Item2).ToList();

            foreach (var k in lst)
            {
                MethodInfo method = k.Value.Item1.GetMethod("Execute");
                method.Invoke(k.Key, new object[] { data });
            }
        }
    }

上面能够看到,我们的事宜总线是支撑对绑定的事宜处置惩罚对象举行有序处置惩罚,须要依靠下面这个罗列

    /// <summary>
    /// 排序范例
    /// </summary>
    public enum SortType
    {
        /// <summary>
        /// 升序
        /// </summary>
        Asc = 1,
        /// <summary>
        /// 降序
        /// </summary>
        Desc = 2
    }

好了,至此,我们的简易版的事宜总线就出来了~ 接下来就是去建模、完成响应的事宜参数和事宜处置惩罚类了。
建立定单模子:

   /// <summary>
    /// 定单模子
    /// </summary>
    public class OrderModel
    {
        /// <summary>
        /// 定单ID
        /// </summary>
        public Guid Id { get; set; }

        /// <summary>
        /// 用户ID
        /// </summary>
        public Guid UserId { get; set; }

        /// <summary>
        /// 定单建立时候
        /// </summary>
        public DateTime CreateTime { get; set; }

        /// <summary>
        /// 商品名称
        /// </summary>
        public string ProductName { get; set; }

        /// <summary>
        /// 购置数目
        /// </summary>
        public int Number { get; set; }

        /// <summary>
        /// 定单金额
        /// </summary>
        public decimal Money { get; set; }
    }

建立定单下单事宜参数

    public interface IOrderCreateEventData : IEventData
    {
        /// <summary>
        /// 定单信息
        /// </summary>
        OrderModel Order { get; set; }
    }

    /// <summary>
    /// 定单建立事宜参数
    /// </summary>
    public class OrderCreateEventData : IOrderCreateEventData
    {
        public OrderModel Order { get; set; }
        public object Source { get; set; }
        public DateTime Time { get; set; }
    }

OK~接下来就是完成我们上面需求上的那些功用了。

  • 存储定单信息
  • 锁定商品库存
  • 音讯推送商家端
    这里我不完成存储定单信息的事宜处置惩罚对象,我默许此营业必需同步处置惩罚,至于背面两个则能够采用异步处置惩罚。经由过程下面代码建立响应的事宜处置惩罚类。
    定单建立事宜之音讯推送商家端处置惩罚类。
    /// <summary>
    /// 定单建立事宜之音讯处置惩罚类
    /// </summary>
    public class OrderCreateEventNotifyHandle : IEventHandle<IOrderCreateEventData>
    {
        public int ExecuteLevel { get; private set; }

        public OrderCreateEventNotifyHandle()
        {
            Console.WriteLine($"建立OrderCreateEventNotifyHandle对象");
            this.ExecuteLevel = 2;
        }

        public void Execute(IOrderCreateEventData eventData)
        {
            Thread.Sleep(1000);
            Console.WriteLine($"实行定单建立事宜之音讯推送!定单ID:{eventData.Order.Id.ToString()},商品名称:{eventData.Order.ProductName}");
        }
       
    }

定单建立音讯之锁定库存处置惩罚类

   /// <summary>
    /// 定单建立事宜 锁定库存 处置惩罚类
    /// </summary>
    public class OrderCreateEventStockLockHandle : IEventHandle<IOrderCreateEventData>
    {
        public int ExecuteLevel { get; private set; }

        public OrderCreateEventStockLockHandle()
        {
            Console.WriteLine($"建立OrderCreateEventStockLockHandle对象");
            this.ExecuteLevel = 1;
        }


        public void Execute(IOrderCreateEventData eventData)
        {
            Thread.Sleep(1000);
            Console.WriteLine($"实行定单建立事宜之库存锁定!定单ID:{eventData.Order.Id.ToString()},商品名称:{eventData.Order.ProductName}");
        }
    }

OK~ 到main要领下入手下手实行定单建立相干代码。

        static void Main(string[] args)
        {
          
            Guid userId = Guid.NewGuid();
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            EventBus eventBus = new EventBus();
            eventBus.EventRegister(typeof(OrderCreateEventNotifyHandle), typeof(OrderCreateEventData));
            eventBus.EventRegister(typeof(OrderCreateEventStockLockHandle), typeof(OrderCreateEventData));
            var order = new Order.OrderModel()
            {
                CreateTime = DateTime.Now,
                Id = Guid.NewGuid(),
                Money = (decimal)300.00,
                Number = 1,
                ProductName = "鲜花一束",
                UserId = userId
            };
            Console.WriteLine($"模仿存储定单");
            Thread.Sleep(1000);
            eventBus.Trigger(new OrderCreateEventData()
            {
                Order = order
            });
            stopwatch.Stop();
            Console.WriteLine($"下单总耗时:{stopwatch.ElapsedMilliseconds}毫秒");
            Console.ReadLine();
        }

至此,我们采用事宜总线的体式格局胜利将需求完成了,实行后效果以下:

 

 

能够看到我们的下单总耗时是3038毫秒,如您所见,我们处理了代码的耦合性然则没有处理代码的实行效力。
下一章,将我们的Redis的宣布定阅形式再到场进来,看是否能改良我们的代码实行效力~~

 

实现一个简单的解释器(5)

参与评论