报警系统QuickAlarm之默认报警规则扩展

报警系统QuickAlarm之默认报警规则扩展

本篇主要是扩展默认的报警规则,使其能更加友好的支持同时选择多种报警方式

扩展遵循两个原则

  • 不影响原有的配置文件格式
  • 简化规则解析复杂度

I. 配置文件的扩展

先看一下原有的配置文件

{
    "default": {
        "level": "NONE",
        "autoIncEmergency": true,
        "max": 30,
        "min": 3,
        "threshold": [
            {
                "level": "LOG",
                "threshold": 5,
                "users": [
                    "yihui",
                    "erhui"
                ]
            }
        ],
        "users": [
            "yihui"
        ]
    },
    "NPE,SELFDEFINE": {
        "level": "LOG",
        "autoIncEmergency": false,
        "max": 30,
        "min": 0,
        "threshold": [
            {
                "level": "SMS",
                "threshold": 20,
                "users": [
                    "345345345345",
                    "123123123123"
                ]
            },
            {
                "level": "WEIXIN",
                "threshold": 10,
                "users": [
                    "小灰灰Blog",
                    "greyBlog"
                ]
            }
        ],
        "users": [
            "yihui"
        ]
    }
}

我们希望是能够支持多重报警方式同时选中,那么上面的配置中, threshold中只定义了一个阀值参数显然是不合适的,主要问题在于

  • 单一阀值,不允许不同报警方式存在交叉
  • 两个报警方式的threshold值相等时,选中的具体是哪个不可预期

所以我们的目标是将上面的参数中,新增一个指定上限的值max

{
    "default": {
        "level": "NONE",
        "autoIncEmergency": true,
        "max": 30,
        "min": 3,
        "threshold": [
            {
                "level": "LOG",
                "threshold": 5,
                "users": [
                    "yihui",
                    "erhui"
                ]
            }
        ],
        "users": [
            "yihui"
        ]
    },
    "NPE,SELFDEFINE": {
        "level": "LOG",
        "autoIncEmergency": false,
        "max": 30,
        "min": 0,
        "threshold": [
            {
                "level": "SMS",
                "threshold": 20,
                "users": [
                    "345345345345",
                    "123123123123"
                ]
            },
            {
                "level": "WEIXIN",
                "threshold": 10,
                "users": [
                    "小灰灰Blog",
                    "greyBlog"
                ]
            }
        ],
        "users": [
            "yihui"
        ]
    },
    "ZZZ": {
        "level": "LOG",
        "autoIncEmergency": true,
        "max": 30,
        "min": 3,
        "threshold": [
            {
                "level": "SMS",
                "threshold": 20,
                "max": 27,
                "users": [
                    "345345345345",
                    "123123123123"
                ]
            },
            {
                "level": "WEIXIN",
                "threshold": 10,
                "users": [
                    "yihui",
                    "erhui"
                ]
            },
            {
                "level": "EMAIL",
                "threshold": 9,
                "max": 14,
                "users": [
                    "yihui@xxx.com",
                    "erhui@xxx.com"
                ]
            }
        ],
        "users": [
            "yihui@xxx.com"
        ]
    }
}

向上面这般改动之后,相当于每个报警方式都可以定义自己的区间,因此允许多重报警方式存在区间的交叉,计数在交叉区间即表示选中这多重方式

II. 扩展的实现支持

从配置文件的变动来看,改动很小,只是新增一个参数而已,且这个参数不是必填的,那么对应的do应该为

public class BasicAlarmThreshold {

    private String level;

    /**
     * 启用定义的报警方式的阀值下限,
     *
     * 当报警计数 count >= min
     *   - max 非null, count < max 则选择本报警方式; 
    *       count >= max 则不选择本报警方式
     *   - max 为null(即表示为定义时),
     *      则max赋值为:恰好大于 min 的 {@link BasicAlarmThreshold#threshold}值
     *
     */
    private int threshold;


    /**
     * 报警上限值,注意这是包装类型,允许为null
     */
    private Integer max;

    private List<String> users;
}

然后顺带着,优化一把我们的映射规则,将配置规则的DO对象,映射为业务对象

主要的映射规则如下

/**
 * 将配置项转换为业务DO对象, 会做一些兼容, 保证 level. min, max, users, thresholds 都不会为null
 *
 * @param basicAlarmConfig
 * @return
 */
private static AlarmConfig parse2BizConfig(BasicAlarmConfig basicAlarmConfig) {
    if (basicAlarmConfig.getUsers() == null || basicAlarmConfig.getUsers().isEmpty()) { // 如果没有填写用户, 则直接抛弃
        return null;
    }

    AlarmConfig alarmConfig = new AlarmConfig();

    // 如果配置的报警类型是异常的, 则下面会兼容一把,设置为 NONE, 避免因为配置的原因导致系统异常
    alarmConfig.setExecutor(SimpleExecuteFactory.getExecute(basicAlarmConfig.getLevel()));
    alarmConfig.setAutoIncEmergency(basicAlarmConfig.isAutoIncEmergency());
    // 报警用户, 要求用户必须存在
    alarmConfig.setUsers(basicAlarmConfig.getUsers());
    // 报警上限, 如果用户没有填写,采用默认的(因为短信报警按条数要钱, 没必要一直无上限的报)
    alarmConfig.setMaxLimit(basicAlarmConfig.getMax() == null ? AlarmConfig.DEFAULT_MAX_NUM : basicAlarmConfig.getMax());
    // 报警下限, 如果用户没有填写, 采用默认的最小值0
    alarmConfig.setMinLimit(basicAlarmConfig.getMin() == null ? AlarmConfig.DEFAULT_MIN_NUM : basicAlarmConfig.getMin());


    // 获取配置中的阀值列表,并排序
    List<BasicAlarmThreshold> basicAlarmThresholdList = basicAlarmConfig.getThreshold();
    if(basicAlarmThresholdList == null) {
        basicAlarmThresholdList = Collections.emptyList();
    }
    basicAlarmThresholdList.sort(Comparator.comparingInt(BasicAlarmThreshold::getThreshold));

    List<AlarmThreshold> alarmThresholdList = new ArrayList<>(basicAlarmThresholdList.size() + 2);
    AlarmThreshold tmpAlarmThreshold;
    BasicAlarmThreshold tmpBasicAlarmThreshold;
    boolean containDefaultExecute = false;
    for (int i = 0; i < basicAlarmThresholdList.size(); i++) {
        tmpBasicAlarmThreshold = basicAlarmThresholdList.get(i);
        tmpAlarmThreshold = new AlarmThreshold();
        tmpAlarmThreshold.setExecutor(SimpleExecuteFactory.getExecute(tmpBasicAlarmThreshold.getLevel()));
        tmpAlarmThreshold.setUsers(tmpBasicAlarmThreshold.getUsers());
        tmpAlarmThreshold.setMin(tmpBasicAlarmThreshold.getThreshold());
        if (tmpBasicAlarmThreshold.getMax() == null || tmpBasicAlarmThreshold.getMax() <= tmpBasicAlarmThreshold.getThreshold()) {
            if (i == basicAlarmThresholdList.size() - 1) { // 最后一个,则使用默认的上限阀值
                tmpAlarmThreshold.setMax(alarmConfig.getMaxLimit());
            } else {
                tmpAlarmThreshold.setMax(basicAlarmThresholdList.get(i + 1).getThreshold());
            }
        } else {
            tmpAlarmThreshold.setMax(tmpBasicAlarmThreshold.getMax());
        }

        if (!containDefaultExecute) {
            containDefaultExecute = tmpBasicAlarmThreshold.getLevel().equals(basicAlarmConfig.getLevel());
        }


        alarmThresholdList.add(tmpAlarmThreshold);
    }


    int thresholdSize = alarmThresholdList.size();
    if (thresholdSize == 0) { // 没有配置阀值列表,直接使用默认
        tmpAlarmThreshold = new AlarmThreshold();
        tmpAlarmThreshold.setExecutor(alarmConfig.getExecutor());
        tmpAlarmThreshold.setUsers(alarmConfig.getUsers());
        tmpAlarmThreshold.setMin(alarmConfig.getMinLimit());
        tmpAlarmThreshold.setMax(alarmConfig.getMaxLimit());
        alarmThresholdList.add(tmpAlarmThreshold);
    } else if (!containDefaultExecute) { // 不包含时默认时,补全
        tmpAlarmThreshold = new AlarmThreshold();
        tmpAlarmThreshold.setExecutor(alarmConfig.getExecutor());
        tmpAlarmThreshold.setUsers(alarmConfig.getUsers());
        tmpAlarmThreshold.setMin(alarmConfig.getMinLimit());
        tmpAlarmThreshold.setMax(alarmThresholdList.get(0).getMin());
        alarmThresholdList.add(0, tmpAlarmThreshold);

        if (alarmThresholdList.get(thresholdSize).getMax() < alarmConfig.getMaxLimit()) {
            tmpAlarmThreshold = new AlarmThreshold();
            tmpAlarmThreshold.setExecutor(alarmConfig.getExecutor());
            tmpAlarmThreshold.setUsers(alarmConfig.getUsers());
            tmpAlarmThreshold.setMin(alarmThresholdList.get(thresholdSize).getMax());
            tmpAlarmThreshold.setMax(alarmConfig.getMaxLimit());
            alarmThresholdList.add(tmpAlarmThreshold);
        }
    }


    alarmConfig.setAlarmThreshold(alarmThresholdList);
    return alarmConfig;
}

在映射为业务对象的逻辑中,直接保障了AlarmThreshold列表中的顺序为最终的需求顺序,映射规则为

/**
 *  如果配置的basicAlarmThresholdList列表中包含默认的报警方式
 *    - 则报警方式完全按照basicAlarmThresholdList的定义来
 *    - eg: 默认报警为 Log, min=5, max=30
 *    -       basicAlarmThresholdList 中定义为  : { Log, min=6 }, { Email, min=8 }, { WeiXin, min=10, max=16 }, { SMS, min=14, max=26 }
 *    - 则转换后的 alarmThresholdList为:
 *    -     { Log, min=6, max=8 }, { Email, min=8, max=10 }, { WeiXin, min=10, max=16 }, { SMS, min=14, max=26 }
 *    -       count : [6, 8)  Log
 *    -       count : [8, 10) Email
 *    -       count : [10, 16) WeiXin
 *    -       count : [14, 26) SMS
 *
 *  如果不包含默认报警方式
 *    - 则需要补全最外层定义的Min-Max区间中的空余位
 *    - eg:   默认报警为 Log, min=5, max=30
 *    -       basicAlarmThresholdList 中定义为  : { Email, min=8 }, { WeiXin, min=10, max=16 }, { SMS, min=14, max=26 }
 *    - 则转换后的 alarmThresholdList为:
 *    -       { Log, min=5, max=8 }, { Email, min=8, max=10 }, { WeiXin, min=10, max=16 }, { SMS, min=14, max=26 }, { Log, min=26, max=30 }
 *    -       count : [5, 8)  Log
 *    -       count : [8, 10) Email
 *    -       count : [10, 16) WeiXin
 *    -       count : [14, 26) SMS
 *    -       count : [26, 30) Log
 *
 *
 *    上面改造后,很容易得知,支持多重报警方式同时工作,即当技术为14,15 时,同时发起WeiXin和SMS报警
 */

相应的就可以干掉原来不太好懂的Executor选择逻辑,对应的代码为

// com.hust.hui.alarm.core.execut.AlarmExecuteSelector#getExecute
public static List<ExecuteHelper> getExecute(final AlarmConfig alarmConfig, int count) {
    // 未达到报警的下限 or 超过报警的上限时
    if (count < alarmConfig.getMinLimit() || count > alarmConfig.getMaxLimit()) {
        return Collections.singletonList(new ExecuteHelper(SimpleExecuteFactory.getExecute(NoneExecute.NAME), alarmConfig.getUsers()));
    }


    // 未开启报警升级, 直接返回
    if (!alarmConfig.isAutoIncEmergency()) {
        return Collections.singletonList(new ExecuteHelper(alarmConfig.getExecutor(), alarmConfig.getUsers()));
    }

    if (count < alarmConfig.getAlarmThreshold().get(0).getMin()) {
        // 未达到报警的下限
        return Collections.singletonList(new ExecuteHelper(SimpleExecuteFactory.getExecute(NoneExecute.NAME), alarmConfig.getUsers()));
    }

    List<ExecuteHelper> list = new ArrayList<>();
    for(AlarmThreshold alarmThreshold: alarmConfig.getAlarmThreshold()) {
        if (alarmThreshold.getMin() <= count && count < alarmThreshold.getMax()) {
            list.add(new ExecuteHelper(alarmThreshold.getExecutor(), alarmThreshold.getUsers()));
        }
        
        if(alarmThreshold.getMin() > count) {
            break;
        }
    }
    return list;
}

III. 其他

相关博文

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

项目: QuickAlarm

个人博客: Z+|blog

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

声明

尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见识有限,如发现bug或者有更好的建议,随时欢迎批评指正

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏mukekeheart的iOS之旅

MySQL学习笔记(一)

一、MySQL基础知识 MySQL 是一个真正的多用户、多线程 SQL 数据库服务器。 SQL(结构化查询语言)是世界上最流行的和标准化的数据库语言。MySQL...

2458
来自专栏逍遥剑客的游戏开发

Nebula3学习笔记(7): 网络系统

1926
来自专栏沈唁志

文本处理,第2部分:OH,倒排索引

这是我的文本处理系列的第二部分。在这篇博客中,我们将研究如何将文本文档存储在可以通过查询轻松检索的表单中。我将使用流行的开源Apache Lucene索引进行说...

1644
来自专栏数据派THU

独家 | 手把手教你如何用Python从PDF文件中导出数据(附链接)

有很多时候你会想用Python从PDF中提取数据,然后将其导出成其他格式。不幸的是,并没有多少Python包可以很好的执行这部分工作。在这篇贴子中,我们将探讨多...

1823
来自专栏linux驱动个人学习

高通 display 驱动【转】

1.5K4
来自专栏Golang语言社区

【golang】调优工具 pprof

Golang 提供了 pprof 包(runtime/pprof)用于输出运行时的 profiling 数据,这些数据可以被 pprof 工具(或者 go to...

1433
来自专栏程序员叨叨叨

4.3 CG 编译

计算机只能理解和执行由 0、1 序列(电压序列)构成的机器语言,所以汇编语言和高级语言程序都需要进行翻译才能被计算机所理解,担负这一任务的程序称为语言处理程序,...

1192
来自专栏CaiRui

详细grep、sed、awk

[root@VM_0_7_centos tmp]# cat 1.txt 1 2 3 4 5 6 [root@VM_0_7_centos tmp]# cat 2...

4667
来自专栏LeoXu的博客

启动 mini-web 报错 java.lang.ClassNotFoundException...

在学习Springside的实例mini-web的时候遇到了Tomcat报错:

982
来自专栏JackieZheng

可视化(番外篇)——在Eclipse RCP中玩转OpenGL

  最近在看有关Eclipse RCP方面的东西,鉴于Gephi是使用opengl作为绘图引擎,所以,萌生了在Eclipse RCP下添加画布,使用opengl...

2065

扫码关注云+社区

领取腾讯云代金券