一灰灰blog 一灰灰blog
首页
  • InfluxDB
  • MongoDB
  • MySql
  • 基础系列
  • DB系列
  • 搜索系列
  • MQ系列
  • WEB系列
  • 中间件
  • 运维
  • SpringSecurity
  • SpringCloud
  • QuickAlarm
  • QuickCrawer
  • QuickFix
  • QuickMedia
  • QuickSpi
  • QuickTask
  • 分类
  • 标签
  • 归档
  • 收藏
  • 关于
GitHub (opens new window)

一灰灰blog

资深搬运工
首页
  • InfluxDB
  • MongoDB
  • MySql
  • 基础系列
  • DB系列
  • 搜索系列
  • MQ系列
  • WEB系列
  • 中间件
  • 运维
  • SpringSecurity
  • SpringCloud
  • QuickAlarm
  • QuickCrawer
  • QuickFix
  • QuickMedia
  • QuickSpi
  • QuickTask
  • 分类
  • 标签
  • 归档
  • 收藏
  • 关于
GitHub (opens new window)
  • QuickAlarm

    • 1. 报警系统QuickAlarm设计总纲
    • 2. 报警系统QuickAlarm之报警执行器的设计与实现
    • 3. 报警系统QuickAlarm之报警规则的设定与加载
    • 5. 报警系统QuickAlarm之频率统计及接口封装
      • I. 报警频率统计
        • 1. 设计
        • 2. 实现
      • II. 报警线程池
      • III. 接口封装
      • IV. 小结
      • V. 其他
        • 相关博文
        • 项目: QuickAlarm
        • 个人博客: Z+|blog
        • 声明
        • 扫描关注
    • 6. 报警系统QuickAlarm使用手册
    • 7. 报警系统QuickAlarm之默认报警规则扩展
    • Quick-Alarm 钉钉报警支持
  • QuickCrawer

  • QuickFix

  • QuickMedia

  • QuickSpi

  • QuickTask

  • Quick开源系列
  • QuickAlarm
一灰灰
2018-02-11

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

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

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

# I. 报警频率统计

# 1. 设计

前面在解析报警规则时,就有一个count参数,用来确定具体选择什么报警执行器的核心参数,我们维护的方法也比较简单:

  • 针对报警类型,进行计数统计,没调用一次,则计数+1
  • 每分钟清零一次

# 2. 实现

因为每种报警类型,都维护一个独立的计数器

定义一个map来存储对应关系

private ConcurrentHashMap<String, AtomicInteger> alarmCountMap;
1

每分钟执行一次清零

// 每分钟清零一把报警计数
ScheduledExecutorService scheduleExecutorService = Executors.newScheduledThreadPool(1);
scheduleExecutorService.scheduleAtFixedRate(() -> {
    for (Map.Entry<String, AtomicInteger> entry : alarmCountMap.entrySet()) {
        entry.getValue().set(0);
    }
}, 0, 1, TimeUnit.MINUTES);
1
2
3
4
5
6
7

注意上面的实现,就有什么问题?

有没有可能因为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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# II. 报警线程池

目前也只是提供了一个非常简单的线程池实现,后面的考虑是抽象一个基于forkjoin的并发框架来处理(主要是最近接触到一个大神基于forkjoin写的并发器组件挺厉害的,所以等我研究透了,山寨一个)

// 报警线程池
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

任务提交执行

private void doSend(final ExecuteHelper executeHelper, 
  final AlarmContent alarmContent) {
    alarmExecutorService.execute(() ->
      executeHelper.getIExecute().sendMsg(
        executeHelper.getUsers(), 
        alarmContent.getTitle(), 
        alarmContent.getContent()));
}
1
2
3
4
5
6
7
8

# 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);
    }
}
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

接口封装完毕之后如何使用呢?

我们使用单例模式封装了唯一对外使用的类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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

使用起来比较简单,就那么一行即可,从这个使用也可以知道,整个初始化,就是在这个对象首次被访问时进行

构造函数内容如下:

private AlarmWrapper() {
  // 记录每种异常的报警数
  alarmCountMap = new ConcurrentHashMap<>();

  // 加载报警配置信息
  confLoader = ConfLoaderFactory.loader();

  // 初始化线程池
  initExecutorService();
}
1
2
3
4
5
6
7
8
9
10

所有如果你希望在自己的应用使用之前就加载好所有的配置,不妨提前执行一下 AlarmWrapper.getInstance()

# IV. 小结

基于此,整个系统设计基本上完成,当然代码层面也ok了,剩下的就是使用手册了

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

IMAGE

  1. 提交报警
  • 封装报警内容(报警类型,报警主题,报警内容)
  • 维护报警计数(每分钟计数清零,每个报警类型对应一个报警计数)
  1. 选择报警
  • 根据报警类型选择报警规则
  • 根据报警规则,和当前报警频率选择报警执行器
    • 若不开启区间映射,则返回默认执行器
    • 否则遍历所有执行器的报警频率区间,选择匹配的报警规则
  1. 执行报警
  • 封装报警任务,提交线程池
  • 报警执行器内部实现具体报警逻辑

# V. 其他

# 相关博文

  1. 报警系统QuickAlarm总纲 (opens new window)
  2. 报警系统QuickAlarm之报警执行器的设计与实现 (opens new window)
  3. 报警系统QuickAlarm之报警规则的设定与加载 (opens new window)
  4. 报警系统QuickAlarm之报警规则解析 (opens new window)
  5. 报警系统QuickAlarm之频率统计及接口封装 (opens new window)
  6. 报警系统QuickAlarm使用手册 (opens new window)
  7. 报警系统QuickAlarm之默认报警规则扩展 (opens new window)

# 项目: QuickAlarm

  • 项目地址: Quick-Alarm (opens new window)
  • 博客地址: 小灰灰Blog (opens new window)

# 个人博客: Z+|blog (opens new window)

基于hexo + github pages搭建的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

# 声明

尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见识有限,如发现bug或者有更好的建议,随时欢迎批评指正,我的微博地址: 小灰灰Blog (opens new window)

# 扫描关注

QrCode

编辑 (opens new window)
#Java#技术方案
上次更新: 2021/10/15, 19:56:22
3. 报警系统QuickAlarm之报警规则的设定与加载
6. 报警系统QuickAlarm使用手册

← 3. 报警系统QuickAlarm之报警规则的设定与加载 6. 报警系统QuickAlarm使用手册→

最近更新
01
【WEB系列】从0到1实现自定义web参数映射器
01-23
02
【WEB系列】如何支持下划线驼峰互转的传参与返回
01-17
03
【DB系列】Mybatis之批量插入的几种姿势
01-11
更多文章>
Theme by Vdoing | Copyright © 2017-2022 一灰灰Blog
MIT License | 鄂ICP备18017282号 |
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×