博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
报警系统QuickAlarm之报警规则的设定与加载
阅读量:6710 次
发布时间:2019-06-25

本文共 12352 字,大约阅读时间需要 41 分钟。

前面一篇是报警执行器的定义与加载已经完成,但与之对应的报警规则有是如何定义和加载的呢?

此外,既然命名为规则,那么就需要有对应的解析器,以根据报警规则和报警类型等相关输入条件,来选择对应的报警执行器,因此本文主要包括的内容就比较清晰了

  • 报警规则的定义
  • 报警规则的加载
  • 报警规则的解析以及报警执行器选择

I. 报警规则定义

目前针对报警规则没有给出自定义配置的入口,即完全采用了默认的方案,后续可以考虑支持适用方来自定义报警规则以及解析器,这样扩展性就更强了

首先说明下我们的设计规则,我们针对不同的AlarmExecute定义了一个优先级,我们的目标是

  • 针对报警频率设置不同区间,每个区间对应一种报警类型
  • 当实际调用的报警频率达到这个区间,就选择这种报警类型
  • 同时也允许关闭根据频率选择报警器的功能,全程用一个默认
  • 每种报警类型的用户都可以自定义

针对上面的目标,我们设计的类就比较明确了

阀值类:

@Getter@Setter@ToStringpublic class AlarmThreshold implements Comparable
{ /** * 报警类型,对应 {
@link IExecute#getName()} */ private String alarmLevel; /** * 晋升此报警的阀值 */ private int threshold; /** * 对应的报警用户 */ private List
users; @Override public int compareTo(AlarmThreshold o) { if (o == null) { return -1; } return threshold - o.getThreshold(); }}复制代码

配置类:

@Getter@Setter@ToStringpublic class AlarmConfig {    public static final int DEFAULT_MIN_NUM = 0;    public static final int DEFAULT_MAX_NUM = 30;    /**     * 报警用户     */    private List
users; /** * 报警的阀值 */ private List
alarmThreshold; /** * 最小的报警数 */ private int minLimit; /** * 最大的报警数 */ private int maxLimit; /** * 报警类型 {
@link IExecute#getName()} */ private String alarmLevel; /** * true 表示当报警超过当前的阀值之后, 将提升报警的程度 */ private boolean autoIncEmergency;}复制代码

一个报警类型对应一个AlarmConfig,这样当执行报警时,就可以很容易的获取对应的规则

同样根据定义,也可以看出报警规则比较简单,直接根据阀值区间来选择

II. 报警规则加载

关于如何加载报警规则,想了很久,选择把这块放开,因为我们无法确定,使用方的配置是存在什么地方的,而且使用的配置是否能和我们的设计的DO兼容也是个问题,因此干脆放手,同样是通过SPI的方式来做的

我们定义规则加载接口: IConfLoader

public interface IConfLoader {    /**     * 加载配置到内存的操作,启动时,被调用     *     * @return true 表示加载成功; false 表示加载失败     */    default boolean load() {        return true;    }    /**     * 排序,越小优先级越高     * 

* 说明: 当系统中多个Loader存在时,会根据优先级来选择order最小的一个作为默认的Loader * * @return */ default int order() { return 10; } /** * 获取注册信息 * * @return */ RegisterInfo getRegisterInfo(); /** * 是否开启报警 * * @return */ boolean alarmEnable(); /** * 根据报警类型,获取对应的报警规则 * * @param alarmKey * @return */ AlarmConfig getAlarmConfig(String alarmKey);}复制代码

上面的方法,可以划分为两类:

  • 加载时使用
    • load 为具体的执行加载配置到内存的方法,返回true表示加载成功
    • order 排序
    • getRegisterInfo 获取基础的配置信息(包括应用名等相关配置)
  • 业务运行时使用
    • alarmEnable : 是否开启报警 (当大量报警时,可以先关闭报警,然后再查问题)
    • getAlarmConfig:核心方法,根据报警类型,返回对应的报警规则

系统默认提供一个从配置文件中加载报警规则的方案,主要会依赖两个配置文件

  • alarm.properties : 初始化注册信息,内部保存 RegisterInfo 所需要的属性
  • alarmConfig : 保存具体的报警规则,json格式

1. 配置加载

配置加载的实现逻辑,如下

public class PropertiesConfLoader implements IConfLoader {    private RegisterInfo registerInfo;    private Map
cacheMap; public boolean load() { // 获取注册信息 registerInfo = RegisterInfoLoaderHelper.load(); if (registerInfo == null) { return false; } // 获取报警的配置类 File file; String path = registerInfo.getAlarmConfPath(); if (path.startsWith("/")) { file = new File(path); } else { URL url = this.getClass().getClassLoader().getResource(path); file = new File(url.getFile()); } // 加载成功,才替换 cacheMap的内容; 主要是为了防止修改配置出现问题 Map
tmp = init(file); boolean ans = tmp != null; // 注册配置文件的变动 ans = ans && PropertiesConfListenerHelper.registerConfChangeListener(file, this::init); if (ans) { cacheMap = tmp; } return ans; } private Map
init(File file) { try { // 正常来讲,是一个完整的json串 List
list = IOUtils.readLines(new FileInputStream(file), "utf-8"); String config = Joiner.on("").join(list); return AlarmConfParse.parseConfig(config, Splitter.on(",").splitToList(registerInfo.getDefaultAlarmUsers())); } catch (IOException e) { log.error("load config into cacheMap error! e: {}", e); return null; } } @Override public RegisterInfo getRegisterInfo() { return registerInfo; } @Override public boolean alarmEnable() { return true; } @Override public AlarmConfig getAlarmConfig(String alarmKey) { AlarmConfig config = cacheMap.get(alarmKey); if (config == null) { return cacheMap.get(AlarmConfParse.DEFAULT_ALARM_KEY); } else { return config; } }}复制代码

主要查看默认的load方法即可, alarmEnable 和 getAlarmConfig还是比较简单的,看一下就知道怎么玩的

2. RegisterInfo 加载

上面的实现中,第一步就是从 alarm.properteis 文件中读取对应的配置,然后初始化 RegisterInfo对象

@Datapublic class RegisterInfo implements Serializable {    // 报警规则文件的路径,系统默认加载时,必填;否则选填    private String alarmConfPath;    // 最大报警类型数,非必填,默认1000    private Integer maxAlarmType;    // 默认报警用户, 必须    private String defaultAlarmUsers;    // 应用名, 必须    private String appName;}复制代码

一个配置文件实例

appName=testalarmConfPath=/tmp/alarmConfigmaxAlarmType=1000defaultAlarmUsers=yihui复制代码

从配置文件中读取信息,然后初始化对象的过程就比较简单了,我这里做了一个小简化,使用反射的方式实现对象拷贝

public static void copy(Properties source, Object dest) throws IllegalAccessException {    Field[] fields = dest.getClass().getDeclaredFields();    for (Field f : fields) {        // 不修改静态变量        if (Modifier.isStatic(f.getModifiers())) {            continue;        }        f.setAccessible(true);        // 值拷贝,因为不同数据类型的问题,所以需要对properties中获取的String类型转换一把        f.set(dest, parseObj(source.getProperty(f.getName()), f.getType()));    }}// 强制类型转换private static 
T parseObj(String obj, Class
clz) { return ParseFuncEnum.getFunc(clz).apply(obj);}复制代码

上面的实现目前比较简单,没有考虑父类的情况,没有考虑复杂的数据类型转换,目前只支持了基本类型的转换,后续可考虑抽象

public enum ParseFuncEnum {    INT_PARSE(Arrays.asList(int.class, Integer.class)) {        @Override        public Function
getFunc() { return Integer::valueOf; } }, LONG_PARSE(Arrays.asList(long.class, Long.class)) { @Override public Function
getFunc() { return Long::valueOf; } }, BOOLEAN_PARSE(Arrays.asList(boolean.class, Boolean.class)) { @Override public Function
getFunc() { return Boolean::valueOf; } }, FLOAT_PARSE(Arrays.asList(float.class, Float.class)) { @Override public Function
getFunc() { return Float::valueOf; } }, DOUBLE_PARSSE(Arrays.asList(double.class, Double.class)) { @Override public Function
getFunc() { return Double::valueOf; } }, SHORT_PARSE(Arrays.asList(short.class, Short.class)) { @Override public Function
getFunc() { return Short::valueOf; } }, BYTE_PARSE(Arrays.asList(byte.class, Byte.class)) { @Override public Function
getFunc() { return Byte::valueOf; } }, CHAR_PARSE(Arrays.asList(char.class, Character.class)) { @Override public Function
getFunc() { return s -> s.charAt(0); } }, STRING_PARSE(Arrays.asList(String.class)) { @Override public Function
getFunc() { return s -> s; } },; private List
clzList; public abstract
Function
getFunc(); private static Map
map = new ConcurrentHashMap<>(20); static { for (ParseFuncEnum enu : ParseFuncEnum.values()) { for (Class clz : enu.clzList) { map.put(clz, enu); } } } ParseFuncEnum(List
clz) { this.clzList = clz; } public static
Function
getFunc(Class
clz) { return map.get(clz).getFunc(); }}复制代码

3. 报警规则加载

注册信息加载完毕之后,就可以获取报警规则的文件地址了,因此首先是读取配置规则的内容(我们要求是JSON格式),然后反序列化即可

将json串格式配置,反序列化为 BaseAlarmConf 对象

private static final TypeReference
> typeReference = new TypeReference
>() {};/** * 将json串格式的报警规则配置,映射为对应实体类 *

* 如果传如的是null, 则采用默认的兜底配置 * 如果传入的是非法的配置,直接返回null, 这样做的目的如下 *

* - 启动时,直接获知配置有问题,需要修改 * - 启动中,修改配置,此时新配置有问题,依然使用旧的配置 * * @param configs * @return */private static Map

parseStrConfig2Map(String configs) { Map
map = null; if (configs != null) { try { map = JSON.parseObject(configs, typeReference); } catch (Exception e) { logger.error("ConfigWrapper.parseStrConfig2Map() init config error! configs: {}, e:{}", configs, e); return null; } } if (map == null) { map = new HashMap<>(1); } if (!map.containsKey(DEFAULT_ALARM_KEY)) { map.put(DEFAULT_ALARM_KEY, DEFAULT_ALARM_CONFIG); } return map;}复制代码

需要额外说明一下,json串并没有直接的映射我们前面定义的 AlarmConfig 对象,因为在原型版本的设计的过程中,考虑到配置与内部的使用对象,可能不是特别匹配,最初的设计中,是希望直接将AlarmConfig中的alarmLevel直接替换成 AlarmExecute 实例对象的,然而在实际实现中没有这么干...,所以看源码时,这里就有点奇怪,后面完全可以干掉这个无用的逻辑

此外,就是需要给一个默认的配置项,当报警类型匹配不到对应的报警规则时,就选择默认的了

下面是一个报警配置的demo

{    "default": {        "level": "LOG",        "autoIncEmergency": true,        "max": 30,        "min": 3,        "threshold": [            {                "level": "SMS",                "threshold": 20,                "users": [                    "345345345345",                    "123123123123"                ]            },            {                "level": "WEIXIN",                "threshold": 10,                "users": [                    "yihui",                    "erhui"                ]            },            {                "level": "LOG",                "threshold": 5,                "users": [                    "yihui",                    "erhui"                ]            }        ],        "users": [            "yihui"        ]    },    "NPE": {        "level": "WEIXIN",        "autoIncEmergency": false,        "max": 30,        "min": 0,        "threshold": [            {                "level": "SMS",                "threshold": 20,                "users": [                    "345345345345",                    "123123123123"                ]            },            {                "level": "WEIXIN",                "threshold": 10,                "users": [                    "3h    ui",                    "4hui"                ]            }        ],        "users": [            "yihui"        ]    },    "XXX,YYY": {        "level": "EMAIL",        "autoIncEmergency": true,        "max": 30,        "min": 3,        "threshold": [            {                "level": "SMS",                "threshold": 20,                "users": [                    "345345345345",                    "123123123123"                ]            },            {                "level": "WEIXIN",                "threshold": 10,                "users": [                    "yihui",                    "erhui"                ]            },            {                "level": "EMAIL",                "threshold": 5,                "users": [                    "yihui@xxx.com",                    "erhui@xxx.com"                ]            }        ],        "users": [            "yihui@xxx.com"        ]    }}复制代码

III. ConfLoader选择并初始化

前面说明,为了确保报警规则的多样性存储与加载,我们支持用户自定义加载类,所以就会有这么个ConfLoaderFactory, 来创建系统中使用的ConfLoader

public class ConfLoaderFactory {    private static IConfLoader currentAlarmConfLoader;    public static IConfLoader loader() {        if (currentAlarmConfLoader == null) {            synchronized (ConfLoaderFactory.class) {                if (currentAlarmConfLoader == null) {                    initConfLoader();                }            }        }        return currentAlarmConfLoader;    }    private static void initConfLoader() {        Iterator
iterator = ServiceLoader.load(IConfLoader.class).iterator(); List
list = new ArrayList<>(); // 根据优先级进行排序,选择第一个加载成功的Loader while (iterator.hasNext()) { list.add(iterator.next()); } list.sort(Comparator.comparingInt(IConfLoader::order)); for (IConfLoader iConfLoader : list) { if (iConfLoader.load()) { currentAlarmConfLoader = iConfLoader; break; } } if (currentAlarmConfLoader == null) { throw new NoAlarmLoaderSpecifyException("no special alarmConfLoader selected!"); } }}复制代码

实现逻辑依旧采取了SPI机制,不够我们定义了一个优先级,默认从最高优先级的开始加载,加载成功之后,就选择这个东西了;否则继续加载下一个,当所有的ConfLoader加载完毕,都没有一个成功的,就抛出一个异常

IV. 小结

鉴于篇幅问题,关于报警规则与报警执行器之间的关系,对应的解释器放在下一篇进行说明,简要小结一下本文内容

  • 报警规则: 采用阀值区间方式,将报警频率与报警执行器关联起来
  • 规则加载: 支持SPI方式注入用户加载器,默认提供基于配置文件的加载器,且优先级最低

基本上本文说的就是下面这张图的内容了

V. 其他

相关博文

项目

  • 项目地址:
  • 博客地址:

声明

尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见解不全,如有问题,欢迎批评指正

扫描关注,java分享

转载地址:http://alalo.baihongyu.com/

你可能感兴趣的文章
ocserv配置文件
查看>>
对你同样重要的非技术贴,一封有效的求职信的具体写法
查看>>
在路由器里插入和删除ACL
查看>>
我的友情链接
查看>>
前端开发IDE
查看>>
OpenStack从入门到放弃
查看>>
戴尔和EMC已经成为正式的竞争对手
查看>>
6425C-Lab12 管理DC(1)
查看>>
RocketMQ调研笔记
查看>>
maven 注册 jar
查看>>
高并发写入mysql的设计
查看>>
成长点滴:我不知道该说些什么?
查看>>
Android widget 桌面组件开发
查看>>
HP EVA4400服务器RAID信息丢失数据恢复方法
查看>>
我的友情链接
查看>>
heap中的heapify与依次压入队列的差异
查看>>
找工作体会
查看>>
linux之使用man查看命令手册
查看>>
健康常识
查看>>
Centos 6.3安装配置supervisor进程管理工具
查看>>