报警系统QuickAlarm之频率统计及接口封装

简介: 前面将报警规则的制定加载解析,以及报警执行器的定义加载和扩展进行了讲解,基本上核心的内容已经完结,接下来剩下内容就比较简单了1.报警频率的统计2.报警线程池3.对外封装统一可用的解耦

前面将报警规则的制定加载解析,以及报警执行器的定义加载和扩展进行了讲解,基本上核心的内容已经完结,接下来剩下内容就比较简单了


  • 报警频率的统计
  • 报警线程池
  • 对外封装统一可用的解耦


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了,剩下的就是使用手册了

再看一下我们的整个逻辑,基本上就是下面这个流程了

image.png


  1. 提交报警
  • 封装报警内容(报警类型,报警主题,报警内容)
  • 维护报警计数(每分钟计数清零,每个报警类型对应一个报警计数)


  1. 选择报警
  • 根据报警类型选择报警规则
  • 根据报警规则,和当前报警频率选择报警执行器
  • 若不开启区间映射,则返回默认执行器
  • 否则遍历所有执行器的报警频率区间,选择匹配的报警规则


  1. 执行报警
  • 封装报警任务,提交线程池
  • 报警执行器内部实现具体报警逻辑


作者:一灰灰

链接:https://juejin.cn/post/6844903569456365576

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关文章
|
11天前
|
小程序 JavaScript 数据挖掘
ClkLog常见问题-指标定义与统计逻辑Sec.1
用户行为分析指标项是衡量产品和运营管理的关键因素,它们可以帮助企业深入了解用户需求、行为模式、产品表现等多个方面。 比如页面停留时间、平均停留时长可以分析用户的需求和兴趣;跳出率、留存率可以查询用户的体验情况;事件触发次数、转化率等可以评估业务流程是否顺畅或者营销策略是否成功。 这篇我们将完整介绍ClkLog的中使用到的指标项定义以及一些重点指标的统计逻辑,便于运营人员理解后做数据分析,同时如果大家在使用过程中发现了指标项为空或异常的情况,可以对照说明排查问题。
ClkLog常见问题-指标定义与统计逻辑Sec.1
|
6月前
|
运维 监控 算法
java实现一个动态监控系统,监控接口请求超时的趋势
java实现一个动态监控系统,监控接口请求超时的趋势
290 2
|
4月前
|
NoSQL 算法 Java
接口限流是一种控制访问频率的技术
在高并发场景下,合理的接口限流、防重复提交及接口防抖机制对保障系统稳定性至关重要。本文介绍了如何利用AOP在不改变业务代码的前提下,灵活添加这些功能。具体包括:通过`@AccessLimit`注解实现接口限流,利用Redis进行计数与控制;通过`@RepeatSubmit`注解防止重复提交,确保数据一致性;通过`@AntiShake`注解实现接口防抖,提升用户体验。此外,提供了基于Redisson和Spring Cloud的实现示例。
66 4
|
5月前
深入解析802.11g标准及其频率范围
【8月更文挑战第24天】
201 0
|
SQL 缓存 负载均衡
线上cpu报警的一次接口优化
春天到了大地都复苏了,沉寂了很久的cpu也开始慢慢复苏了,所谓前人埋坑后人填坑,伴随着阿里云监控报警,线上CPU使用率暴增,于是就开始了排查之路。
121 0
|
存储 JSON 数据格式
报警系统QuickAlarm之报警规则的设定与加载
既然命名为规则,那么就需要有对应的解析器,以根据报警规则和报警类型等相关输入条件,来选择对应的报警执行器,因此本文主要包括的内容就比较清晰了 1.报警规则的定义 2.报警规则的加载 3.报警规则的解析以及报警执行器选择
269 0
报警系统QuickAlarm之报警规则的设定与加载
|
缓存 Java Spring
报警系统QuickAlarm之报警执行器的设计与实现
根据前面一篇总纲的博文,将整体结构划分为了四大块,本文则主要目标集中在第一块,报警执行器(AlarmExecute)的设计与加载上了 主要的关注点无外乎 定义-》加载-》实现逻辑三块了: 1.AlarmExecute 的接口定义 2.如何加载用户自定义的AlarmExecute 3.AlarmExecute的内部实现
359 0
报警系统QuickAlarm之报警执行器的设计与实现
|
SQL 运维 监控
监控异常操作频率并报警
当企业上云后,监控云资源的异常操作就是一件非常重要的事情。如何监控这些异常操作呢?答案就是操作审计。接下来就以一些实际场景为例,介绍如何基于操作审计,监控云上异常操作或操作频率,进行报警。
监控异常操作频率并报警
|
应用服务中间件 PHP nginx