使用C#的计时器加观察者模式完成报警推送需求


前言


这两天面试了一个物联网公司高级研发,面试题是下面这样子



公司领导,部门主管,小组组长,组成员4级,假如有个 疫情预警,先通知组人员(对个人,主要有一个处理就算处理了) 如果3分钟没处理,就往组长发短信,如果组长3分钟没处理就往上级推送。一级一级的。 要求单程序并发至少支持1000tps预警事件


说实话上面需求在我做的过往项目里有过类似需求,我也只是依稀记得自己是怎么做的。类似于使用第三方任务调度框架完成上面的3分钟超时处理,然后至于分级发送则更简单了,再不济就是使用if、else这样的最原始验证即可。但是这样的题目丢到面试题上我是一下就不知所措了。自然最终的结果也不尽人意(我最终提交的代码就是采用一个事件总线实现报警的推送,但是并未对其进行超时分级发送) 这个自然是错误的,我其实当时也想过使用Timer,但是内心对Timer就是感觉用性能做代价去换取最终的结果。


解析需求


过了几天还是觉得要手纯撸代码将上面功能撸出来,不然如何成长呢!
拆分下需求可以得到的消息是有一个事件。这个事件就是报警事件,通过报警事件需要将消息推送给不同职位的工作人员,而且必须遵循岗位从下至上,但凡人员收到报警消息则表示报警通知已完成,其次就是有一个三分钟需要处理。


通过上面的需求分析可以知道我们必须要定义一个枚举,记录职称高低。


1
2
3
4
5
6
7
8
9
10
    /// <summary>
    /// 工作级别
    /// </summary>
    public enum JobLevel : int
    {
        公司领导 = 1,
        部门主管 = 2,
        小组组长 = 3,
        组成员 = 4
    }


  


其次我们至少存在两个类,一个是产生报警的对象,这个对象有需要通知的报警信息和报警信息发送结果,当然更加少不了我们订阅了报警消息的订阅者。这里想了下,可以采用观察者设计模式进行解耦。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    /// <summary>
    /// 发布者接口
    /// </summary>
    public interface IPublish
    {
        /// <summary>
        /// 新增订阅者(观察者)
        /// </summary>
        /// <param name="subscriber"></param>
        void AddSubscriber(SubscriberPeopleBase subscriber);

        /// <summary>
        /// 移除订阅者(观察者)
        /// </summary>
        /// <param name="subscriber"></param>
        void RemoveSubscriber(SubscriberPeopleBase subscriber);

        /// <summary>
        /// 发送报警消息
        /// </summary>
        void SendNotify();
   
        /// <summary>
        /// 出现警报
        /// </summary>
        /// <param name="msg">警报消息</param>
        void CreateJB(string msg);


  接下来就是实现上面接口了!


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/// <summary>
/// 报警发布者
/// </summary>
public class Baojing : IPublish
{
        /// <summary>
        /// 订阅者集合
        /// </summary>
        public List<SubscriberPeopleBase> SubscriberLst { get; set; }

        /// <summary>
        /// 报警消息
        /// </summary>
        public string Msg { get; set; }

        /// <summary>
        /// 有无接收成功
        /// </summary>
        public bool IsSucess { get; set; }

        /// <summary>
        /// 消息通知计数器
        /// </summary>
        public Timer NotifyTimer { get; set; }

        /// <summary>
        /// 记录当前发送消息的岗位
        /// </summary>
        public JobLevel CurrentJobLevel = JobLevel.组成员;

        public void AddSubscriber(SubscriberPeopleBase subscriber)
        {
            if (SubscriberLst == null) SubscriberLst = new  List<SubscriberPeopleBase>();
            SubscriberLst.Add(subscriber);
        }

        public void CreateJB(string msg)
        {
            Msg = msg;
        }

        public void RemoveSubscriber(SubscriberPeopleBase subscriber)
        {
            if (SubscriberLst != null) SubscriberLst.Remove(subscriber);
        }


        /// <summary>
        /// 发送报警消息
        /// </summary>
        public void SendNotify()
        {
            if (SubscriberLst?.Count > 0)
            {
                //循环从职位低到高
                SubscriberLst.OrderByDescending(p => (int)p.Title);    
                 //立即执行CheckNotifySendResult,以为3秒为间隔
                NotifyTimer = new Timer(CheckNotifySendResult, null, 0, 3000);              
            }
        }

        public void CheckNotifySendResult(object stat)
        {
            //先要停止定时器,防止并发
            NotifyTimer.Change(-1, 3000);
            //第一次发给职位最小 枚举最大的组成员
            SubscriberPeopleBase subscriberPeople = SubscriberLst.Find(p =>
             (((int)CurrentJobLevel + 1) - (int)p.Title) == 1);
            if (subscriberPeople == null) return; //已经是最小的
            if (!IsSucess)
            {
                IsSucess = subscriberPeople.ReceiveData(this);
                if (!IsSucess)
                {
                    CurrentJobLevel = CurrentJobLevel!= JobLevel.公司领导?(JobLevel)((int)CurrentJobLevel) - 1: JobLevel.公司领导;
                    NotifyTimer.Change(3000, 3000);
                }
            }
            else
            {
                Console.WriteLine($"停止计数器 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
                NotifyTimer.Dispose();
            }
        }
    }


  还需要有订阅者,订阅者使用抽象类方式降低耦合性(其实还是要用事件总线,将耦合性和扩展性提高一个档次)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  /// <summary>
    /// 订阅者
    /// 其实这里还不够完善,理论上后续如果每个职位的订阅者对报警消息的处理不同则在下面的接收消息里会存在一些冗余代码,
    /// 理想情况是不同级别应该有不同的实现,这样可以足够抽象,后续扩展也更加方便
    /// </summary>
    public abstract class SubscriberPeopleBase
    {
        /// <summary>
        /// 工作职位(级别)
        /// </summary>
        public JobLevel Title { get; set; }

        public SubscriberPeopleBase(JobLevel title)
        {
            Title = title;
        }

        public virtual bool ReceiveData(Baojing baojing)
        {
            if (Title == JobLevel.公司领导)
            {
                Console.WriteLine($"出现报警信息:{baojing.Msg},当前订阅者:{Title.ToString()} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
                return true;
            }
            else
            {
                Console.WriteLine($"出现报警信息:{baojing.Msg},当前订阅者:{Title.ToString()},默认未应答  {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
                return false;
            }
        }
    }


  再加个子类,重写下接收警报的方法


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 public class SubscriberPeople : SubscriberPeopleBase
 {
        public SubscriberPeople(JobLevel title) : base(title) { }
        public override bool ReceiveData(Baojing baojing)
        {          
            if (Title == JobLevel.公司领导)
            {
                Console.WriteLine($"出现最新报警信息:{baojing.Msg},当前订阅者:{Title.ToString()} 已成功应答 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
                return true;
            }
            else
            {
                Console.WriteLine($"出现报警信息:{baojing.Msg},当前订阅者:{Title.ToString()},默认未应答 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
                return false;
            }
        }
 }


  最终我的main方法是这样


1
2
3
4
5
6
7
8
9
10
11
  static void Main(string[] args)
  {
            IPublish publish = new Baojing();
            publish.AddSubscriber(new SubscriberPeople(JobLevel.组成员));
            publish.AddSubscriber(new SubscriberPeople(JobLevel.公司领导));
            publish.AddSubscriber(new SubscriberPeople(JobLevel.部门主管));
            publish.AddSubscriber(new SubscriberPeople(JobLevel.小组组长));
            publish.CreateJB("主机温度过高!");
            publish.SendNotify();
            Console.ReadLine();
   }


  调试的效果如下:




明天还是完善下,将使用EventBus,将耦合性再度降低,同时也能让接收方法更加灵活性,能实现不同级别的职员做出不同的响应!