前面将报警规则的制定加载解析,以及报警执行器的定义加载和扩展进行了讲解,基本上核心的内容已经完结,接下来剩下内容就比较简单了
报警频率的统计
报警线程池
对外封装统一可用的解耦
I. 报警频率统计 1. 设计 前面在解析报警规则时,就有一个count参数,用来确定具体选择什么报警执行器的核心参数,我们维护的方法也比较简单:
针对报警类型,进行计数统计,没调用一次,则计数+1
每分钟清零一次
2. 实现 因为每种报警类型,都维护一个独立的计数器
定义一个map来存储对应关系
1 private ConcurrentHashMap<String, AtomicInteger> alarmCountMap;
每分钟执行一次清零
1 2 3 4 5 6 7 ScheduledExecutorService scheduleExecutorService = Executors.newScheduledThreadPool(1 ); scheduleExecutorService.scheduleAtFixedRate(() -> { for (Map.Entry<String, AtomicInteger> entry : alarmCountMap.entrySet()) { entry.getValue().set(0 ); } }, 0 , 1 , TimeUnit.MINUTES);
注意上面的实现,就有什么问题?
有没有可能因为map中的数据过大(或者gc什么原因),导致每次清零花不少的时间,而导致计数不准呢? (先不给出回答)
计数加1操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private int getAlarmCount (String key) { if (!alarmCountMap.containsKey(key)) { synchronized (this ) { if (!alarmCountMap.containsKey(key)) { alarmCountMap.put(key, new AtomicInteger(0 )); } } } return alarmCountMap.get(key).addAndGet(1 ); }
II. 报警线程池 目前也只是提供了一个非常简单的线程池实现,后面的考虑是抽象一个基于forkjoin的并发框架来处理(主要是最近接触到一个大神基于forkjoin写的并发器组件挺厉害的,所以等我研究透了,山寨一个)
1 2 3 4 5 6 private ExecutorService alarmExecutorService = new ThreadPoolExecutor(3 , 5 , 60 , TimeUnit.SECONDS, new LinkedBlockingDeque<>(10 ), new DefaultThreadFactory("sms-sender" ), new ThreadPoolExecutor.CallerRunsPolicy());
任务提交执行
1 2 3 4 5 6 7 8 private void doSend (final ExecuteHelper executeHelper, final AlarmContent alarmContent) { alarmExecutorService.execute(() -> executeHelper.getIExecute().sendMsg( executeHelper.getUsers(), alarmContent.getTitle(), alarmContent.getContent())); }
III. 接口封装 这个就没什么好说的了
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 public void sendMsg (String key, String content) { sendMsg(new AlarmContent(key, null , content)); } public void sendMsg (String key, String title, String content) { sendMsg(new AlarmContent(key, title, content)); } private void sendMsg (AlarmContent alarmContent) { try { AlarmConfig alarmConfig = confLoader.getAlarmConfig(alarmContent.key); int count = getAlarmCount(alarmContent.key); alarmContent.setCount(count); ExecuteHelper executeHelper; if (confLoader.alarmEnable()) { executeHelper = AlarmExecuteSelector.getExecute(alarmConfig, count); } else { executeHelper = AlarmExecuteSelector.getDefaultExecute(); } doSend(executeHelper, alarmContent); } catch (Exception e) { logger.error("AlarmWrapper.sendMsg error! content:{}, e:{}" , alarmContent, e); } }
接口封装完毕之后如何使用呢?
我们使用单例模式封装了唯一对外使用的类AlarmWrapper,使用起来也比较简单,下面就是一个测试case
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void sendMsg () throws InterruptedException { String key = "NPE" ; String title = "NPE异常" ; String msg = "出现NPE异常了!!!" ; AlarmWrapper.getInstance().sendMsg(key, title, msg); AlarmWrapper.getInstance().sendMsg("zzz" , "不存在xxx异常配置" , "报警嗒嗒嗒嗒" ); Thread.sleep(1000 ); }
使用起来比较简单,就那么一行即可,从这个使用也可以知道,整个初始化,就是在这个对象首次被访问时进行
构造函数内容如下:
1 2 3 4 5 6 7 8 9 10 private AlarmWrapper () { alarmCountMap = new ConcurrentHashMap<>(); confLoader = ConfLoaderFactory.loader(); initExecutorService(); }
所有如果你希望在自己的应用使用之前就加载好所有的配置,不妨提前执行一下 AlarmWrapper.getInstance()
IV. 小结 基于此,整个系统设计基本上完成,当然代码层面也ok了,剩下的就是使用手册了
再看一下我们的整个逻辑,基本上就是下面这个流程了
提交报警
封装报警内容(报警类型,报警主题,报警内容)
维护报警计数(每分钟计数清零,每个报警类型对应一个报警计数)
选择报警
根据报警类型选择报警规则
根据报警规则,和当前报警频率选择报警执行器
若不开启区间映射,则返回默认执行器
否则遍历所有执行器的报警频率区间,选择匹配的报警规则
执行报警
封装报警任务,提交线程池
报警执行器内部实现具体报警逻辑
V. 其他 相关博文
报警系统QuickAlarm总纲
报警系统QuickAlarm之报警执行器的设计与实现
报警系统QuickAlarm之报警规则的设定与加载
报警系统QuickAlarm之报警规则解析
报警系统QuickAlarm之频率统计及接口封装
报警系统QuickAlarm使用手册
报警系统QuickAlarm之默认报警规则扩展
项目: QuickAlarm
基于hexo + github pages搭建的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
声明 尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见识有限,如发现bug或者有更好的建议,随时欢迎批评指正,我的微博地址: 小灰灰Blog
扫描关注