报警系统QuickAlarm之频率统计及接口封装
时间:2022-05-06
本文章向大家介绍报警系统QuickAlarm之频率统计及接口封装,主要内容包括I. 报警频率统计、2. 实现、II. 报警线程池、III. 接口封装、IV. 小结、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。
前面将报警规则的制定加载解析,以及报警执行器的定义加载和扩展进行了讲解,基本上核心的内容已经完结,接下来剩下内容就比较简单了
- 报警频率的统计
- 报警线程池
- 对外封装统一可用的解耦
I. 报警频率统计
1. 设计
前面在解析报警规则时,就有一个count参数,用来确定具体选择什么报警执行器的核心参数,我们维护的方法也比较简单:
- 针对报警类型,进行计数统计,没调用一次,则计数+1
- 每分钟清零一次
2. 实现
因为每种报警类型,都维护一个独立的计数器
定义一个map来存储对应关系
private ConcurrentHashMap<String, AtomicInteger> alarmCountMap;
每分钟执行一次清零
// 每分钟清零一把报警计数
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
*
* @param key
* @return
*/
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写的并发器组件挺厉害的,所以等我研究透了,山寨一个)
// 报警线程池
private ExecutorService alarmExecutorService = new ThreadPoolExecutor(3, 5, 60,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(10),
new DefaultThreadFactory("sms-sender"),
new ThreadPoolExecutor.CallerRunsPolicy());
任务提交执行
private void doSend(final ExecuteHelper executeHelper,
final AlarmContent alarmContent) {
alarmExecutorService.execute(() ->
executeHelper.getIExecute().sendMsg(
executeHelper.getUsers(),
alarmContent.getTitle(),
alarmContent.getContent()));
}
III. 接口封装
这个就没什么好说的了
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));
}
/**
* 1. 获取报警的配置项
* 2. 获取当前报警的次数
* 3. 选择适当的报警类型
* 4. 执行报警
* 5. 报警次数+1
*
* @param alarmContent
*/
private void sendMsg(AlarmContent alarmContent) {
try {
// get alarm config
AlarmConfig alarmConfig = confLoader.getAlarmConfig(alarmContent.key);
// get alarm count
int count = getAlarmCount(alarmContent.key);
alarmContent.setCount(count);
ExecuteHelper executeHelper;
if (confLoader.alarmEnable()) { // get alarm execute
executeHelper = AlarmExecuteSelector.getExecute(alarmConfig, count);
} else { // 报警关闭, 则走空报警流程, 将报警信息写入日志文件
executeHelper = AlarmExecuteSelector.getDefaultExecute();
}
// do send msg
doSend(executeHelper, alarmContent);
} catch (Exception e) {
logger.error("AlarmWrapper.sendMsg error! content:{}, e:{}", alarmContent, e);
}
}
接口封装完毕之后如何使用呢?
我们使用单例模式封装了唯一对外使用的类AlarmWrapper,使用起来也比较简单,下面就是一个测试case
@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);
}
使用起来比较简单,就那么一行即可,从这个使用也可以知道,整个初始化,就是在这个对象首次被访问时进行
构造函数内容如下:
private AlarmWrapper() {
// 记录每种异常的报警数
alarmCountMap = new ConcurrentHashMap<>();
// 加载报警配置信息
confLoader = ConfLoaderFactory.loader();
// 初始化线程池
initExecutorService();
}
所有如果你希望在自己的应用使用之前就加载好所有的配置,不妨提前执行一下 AlarmWrapper.getInstance()
IV. 小结
基于此,整个系统设计基本上完成,当然代码层面也ok了,剩下的就是使用手册了
再看一下我们的整个逻辑,基本上就是下面这个流程了
- 提交报警
- 封装报警内容(报警类型,报警主题,报警内容)
- 维护报警计数(每分钟计数清零,每个报警类型对应一个报警计数)
- 选择报警
- 根据报警类型选择报警规则
- 根据报警规则,和当前报警频率选择报警执行器
- 若不开启区间映射,则返回默认执行器
- 否则遍历所有执行器的报警频率区间,选择匹配的报警规则
- 执行报警
- 封装报警任务,提交线程池
- 报警执行器内部实现具体报警逻辑
- WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(上篇)
- 谈谈分布式事务之二:基于DTC的分布式事务管理模型[下篇]
- 孙明俊:人工智能之算力演进
- 探秘Tomcat——一个简易的Servlet容器
- 2017年与机器学习相关的10大Python开源库
- 控制并发访问的三道屏障: WCF限流(Throttling)体系探秘[上篇]
- 探秘Tomcat——从一个简陋的Web服务器开始
- EnterLib PIAB又一个BUG?[续]——这是一个致命的BUG
- Google工程师:谷歌翻译在几个月内效果明显提升的秘诀
- 回调与并发: 通过实例剖析WCF基于ConcurrencyMode.Reentrant模式下的并发控制机制
- EnterLib PIAB又一个BUG?[续]——这是一个致命的BUG
- 年终盘点2017年发生在上海的科技大新闻
- 数字供应链第六章-网络风险、知识产权盗窃、合规和数据挖掘业务合同
- 使命必达: 深入剖析WCF的可靠会话[实例篇](内含美女图片,定力差者慎入)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- appium教程_2.概念加深
- [javascript] cdn模式下vue和vue-router实现路由
- (干货)前端实现导出excel的功能
- 完美解决JavaScript的深浅拷贝
- 来,我们在重新说下,线程状态?
- [不定时一题]LeetCode两数相加
- [不定时一题]Leetcode两数之和
- 深入浅出mongodb(一)
- 深入浅出mongodb(二)
- 算法——快速排序
- 算法——二分查找算法
- 第一篇——Ubuntu 安装 Elasticsearch
- Ubuntu安装Redis(手动编译)
- 第五节:Activiti6.0——流程定义相关API
- 第四节:Activiti6.0——使用zip、bpmn代码生成流程图的资源部署以及资源的查询