前面两篇分别说了报警执行器和报警规则的定义及用户扩展加载,接下来就是比较核心的一块了,如何将报警规则和报警执行器关联起来,即当发生报警时,应该call哪一个报警执行器
I. 背景知识点
0. 声明
在正式进入之前,有必要额外声明一下,因为目前的v1版本,没有开放报警规则的自定义,也就是说,目前只支持默认的报警规则,所以接下来的主要内容将集中在
- 系统默认的报警规则的解析
- 即基于报警频率阀值,自动选择报警执行器的规则解析
1. 报警规则
如果对于报警规则,依然不是很清晰的,可以阅读一下《报警系统QuickAlarm之报警规则的设定与加载》
这里简单的进行说明,系统中默认的报警规则结构为:
- key为报警类型(即用户执行报警时,传进来的报警类型参数)
- value为具体报警规则
- 每个报警执行器拥有一个报警频率区间,通过报警频率映射到报警执行器的区间来选择对应的AlarmExecutor,这就是系统定义的报警规则
II. 报警规则解析
通过前面的报警规则的简单说明,基本上也可以捞出报警规则的解析原则了
- 每种报警类型,对应一个报警规则
- 每个报警规则中,可以有多个报警执行器
- 每个报警执行器都有一个对应的报警频率的阀值
- 根据阀值对所有的报警执行器排序
- 计算报警频率,映射到哪个区间,则选择哪个报警执行器
上面是一个简单的解析规则,当然实际上和这个差不多,但有一些问题需要额外注意
- 只想选择一种报警方式,是否可以支持?
- 多重报警方式同时调用怎么处理?(如我希望用短信提示说有问题,同时用邮件包含详细的异常堆栈)
- 频率限制
- 报警类型没有设置报警规则如何处理?
- 报警规则中使用了一个未注册的报警执行器会怎样?
1. 实现方案说明
再次将报警规则类拿出来看一下
/** * 报警用户 */ private List<String> users; /** * 报警的阀值 */ private List<AlarmThreshold> alarmThreshold; /** * 最小的报警数 */ private int minLimit; /** * 最大的报警数 */ private int maxLimit; /** * 报警类型 {@link IExecute#getName()} */ private String alarmLevel; /** * true 表示当报警超过当前的阀值之后, 将提升报警的程度 */ private boolean autoIncEmergency; 复制代码
针对上面的问题,逐一说明
- 首先是
autoIncEmergency
这个参数,如果为true,则表示可以走上面的哪个区间映射的规则;否则就全部走AlarmConfig中默认的报警类型了 - minLimit : 表示发生报警的频率下限值,小于这个值就不会执行具体的报警逻辑
- maxLimit : 最大的报警频率,超过了也不报警(简单的频率控制)
- alarmLevel: 对应的就是具体的报警类型
- alarmThreshold: 这个只有在
autoIncEmergency=true
时,才有小,也就是我们前面说的不同的报警执行器,根据阀值区间进行排序,开启之后,遍历,判断频率是否在这个区间内,若在,则表示可以选择它了 - 如果不存在报警规则,则采用默认的兜底规则
- 若报警执行器也不存在,就直接采用系统定义的日志报警执行器
2. 实现
基本上前面已经将整个逻辑都说了,所以实际的编码反而比较清晰了
/** * 获取具体的报警执行器 * <p> * 1. 未开启严重等级上升时, 直接返回 * 2. 开启之后, 判断当前的计数 范围 * * @param alarmConfig 报警配置项, 内部所有的参数都不可能为null */ public static ExecuteHelper getExecute(final AlarmConfig alarmConfig, int count) { // 未达到报警的下限 or 超过报警的上限时 if (count < alarmConfig.getMinLimit() || count > alarmConfig.getMaxLimit()) { return new ExecuteHelper(SimpleExecuteFactory.getExecute(NoneExecute.NAME), alarmConfig.getUsers()); } // 未开启报警升级, 直接返回 if (!alarmConfig.isAutoIncEmergency()) { return new ExecuteHelper(SimpleExecuteFactory. getExecute(alarmConfig.getAlarmLevel()), alarmConfig.getUsers()); } // 报警等级开启上升之趋势 // 1. 获取设置的默认等级 // 2. 判断当前的报警次数, 选择对应的报警类型 // 3. 选择具体的报警类型 String defaultLevel = alarmConfig.getAlarmLevel(); String selectLevel = null; List<String> selectUser = alarmConfig.getUsers(); List<AlarmThreshold> list = alarmConfig.getAlarmThreshold(); boolean useNew = false; boolean containDefaultLevel = false; for (AlarmThreshold alarmThreshold : list) { if (Objects.equals(alarmThreshold.getAlarmLevel(), defaultLevel)) { containDefaultLevel = true; } } for (AlarmThreshold alarmThreshold : list) { // 表示当前的报警等级已经赶上默认的报警等级了, 所以要选择新的报警类型 if (Objects.equals(alarmThreshold.getAlarmLevel(), defaultLevel)) { useNew = true; } if (count < alarmThreshold.getThreshold()) { break; } selectLevel = alarmThreshold.getAlarmLevel(); // 选择新的报警类型时, 需要更新报警用户 selectUser = alarmThreshold.getUsers(); } // 阀值列表中不包含默认报警类型,则根据新的来 if (!containDefaultLevel && selectLevel != null) { return new ExecuteHelper(SimpleExecuteFactory.getExecute(selectLevel), selectUser); } // 如果阀值列表中包含了默认报警类型, 且已经超过默认阀值 if (useNew && selectLevel != null) { return new ExecuteHelper(SimpleExecuteFactory.getExecute(selectLevel), selectUser); } else { return new ExecuteHelper(SimpleExecuteFactory.getExecute(defaultLevel), alarmConfig.getUsers()); } } 复制代码
具体的实现基本和我们前面分析的一样,但有一个地方需要额外注意
- 默认报警阀值,可以直接决定是否需要报警
- 因此定义的其他报警方式的阀值,应该在默认的阀值区间内
- 当然
AlarmThreshold
中不包含默认报警方式时,优先选择阀值区间的报警方式 - 当然
AlarmThreshold
中包含默认报警方式时,根据新的规则做处理
(吐槽:上面这个实现有点绕,后面想办法规避下,搞得不太好理解了)
另外一个问题就是,上面的实现没有支持可以同时选择多个报警执行器的情况
因为考虑到后面肯定会对报警规则的定义和解析放开,所以先实现了一个简单的场景,具体的放在后面处理
III. 小结
到这里报警规则和报警执行器之间的解析关系已确定,剩下的东西就简单了,一个维持报警频率计数,一个报警线程池,再加上一个对外接口的封装而言
基本上,到这里主要的核心逻辑已经完成,小结一下本系统中的核心设计理念 -- 一切可自定义(当然目前差得有点远)
1. 报警执行器
- 通过SPI机制支持用户自定义扩展
- 要求 Executor 拥有唯一标识
- 因为报警执行器支持扩展,所以Executor的内部实现,完全可以由用户决定
2. 报警规则
- 目前报警规则只提供默认的基于频率区间的选择方案
- 报警规则通过报警执行器的name与之唯一对应,若对应不上,则选择默认执行器
- 报警规则的加载同样基于SPI,支持自定义,因此报警规则可以存在任何地方
- 报警规则加载器,提供一个报警规则变动的钩子(load()),若采用自定义的加载类,则确保规则变动时,主动回调这个方法
- 默认的报警规则加载类,是基于系统的配置文件实现,内部托管了文件的变动更新事件(使用commons-io实现)