报警系统QuickAlarm之报警执行器的设计与实现

简介: 根据前面一篇总纲的博文,将整体结构划分为了四大块,本文则主要目标集中在第一块,报警执行器(AlarmExecute)的设计与加载上了主要的关注点无外乎 定义-》加载-》实现逻辑三块了:1.AlarmExecute 的接口定义2.如何加载用户自定义的AlarmExecute3.AlarmExecute的内部实现

根据前面一篇总纲的博文,将整体结构划分为了四大块,本文则主要目标集中在第一块,报警执行器(AlarmExecute)的设计与加载上了


主要的关注点无外乎 定义-》加载-》实现逻辑三块了:


  • AlarmExecute 的接口定义
  • 如何加载用户自定义的AlarmExecute
  • AlarmExecute的内部实现


I. AlarmExecute接口定义



在定义接口之前,先来根据几个问题来加深下这个概念的理解:


1. 基础知识


  1. 说一下这个报警执行器到底是干嘛的?


  • 执行具体的报警逻辑(感觉说了依据废话)
  • 因此不同的报警方式,可以选择不同的实现,这个强业务关联的逻辑可以交由适用方自己来把控


  1. 多个alarmExecute之间如何区分?


  • 给一个类似身份证的标识,将标识与alarmExecute绑定,则可以报警规则中,用这个标识来表示对应的报警执行器
  • 标识要求全局唯一,否则就没法找到对应的执行器


2. 接口定义


根据上面的基础知识,那么很容易给出接口的定义了


public interface IExecute {
    /**
     * 报警的具体实现
     *
     * @param users 报警用户,支持批量
     * @param title 报警信息的title
     * @param msg   报警的主题信息
     */
    void sendMsg(List<String> users, String title, String msg);
    /**
     * 获取报警单元唯一标识
     *
     * @return name  要求全局唯一
     */
    default String getName() {
        return ExecuteNameGenerator.genExecuteName(this.getClass());
    }
}
复制代码


  • 第一个方法sendMsg也就是需要使用者来实现的具体执行报警代码的核心模块了,比较清晰,其中用户是列表,因此,支持同时报警给多个用户(但是报警内容都是相同的)
  • 第二个方法getName表示获取标识,默认给了一个实现,规则如下
  • 获取类的 SimpleName
  • 干掉类名后面的 Execute (如果不是以这个结尾的就不需要了)
  • 剩下的全部转大写
  • 实例: SmsExecute -> SMS; LogExecute -> LOG;


3. 额外说明


上面接口定义中的sendMsg中,支持给多个用户发送报警信息,如果要求每个报警信息都不同,比如最常见的是:


  • 发送一段文本,其中通知人地方根据报警人来替换,其他的不变

当然这样的场景完全可以自己在实现中来做

  • 传入的content作为一个话术模板
  • 然后利用 String#format() 来实现参数代替


当然更激进一点就是,穿进来的title或者content作为一个key,然后我可以通过这个key,到其他的地方(如db,缓存等)获取报警内容,甚至我连传进来的报警人都不care,直接从其他地方来获取


简单来说,这个实现委托给用户自己实现,你完全可以随意的控制,做任何你想做的事情


II. AlarmExecute的加载



1. 问题分析


加载AlarmExecut,貌似没有什么特别复杂的东西,一般的思路是创建一个简单工厂类,然后实例化对应的Executor返回,(再多一点确保只有一个实例对象,加以缓存)


这样有什么问题?


很简单的实现,但是我们需要加载用户自定义的执行器,要怎么支持呢?

几种可行的解决手段


1. 开放一个注册接口


这个可算是最容易想到的了,直接让用户把自己的Executor实例,主动的扔进来


2. 抽象工厂


将前面说的简单工厂,改成抽象工厂类,让后具体的加载委托给用户自己来做


3. 借助Spring容器来加载


如果所有的AlarmExecute都委托给Spring容器来管理,那么就很简单了,直接通过ApplicationContext#getBean来获取所有的执行器即可


4. SPI加载方式


通过JDK的spi机制来实现(详细后面来说)

针对上面的几个手段,首先排除掉前面两个,因为不满足我们的设计目标一:

  • 简单 (只有报警这个接口进行交互,不需要额外的接口调用)

然后也排除掉spring容器,因为我们希望这个东西,可以较独立的被引用到java工程中,后面可以看情况实现一个spring版


从使用来讲,由spring容器来托管的方式,对使用者而言,是最简单,成本最低的,因为不需要额外添加SPI配置


2. 实现


我们采用SPI方式来实现加载,对于SPI是什么东西,这里不详细展看,有兴趣的童鞋可以看我之前的一个系类博文:自定义SPI框架设计


实现方式,可说是非常简单了

public class SimpleExecuteFactory {
    private static Map<String, IExecute> cacheMap;
    private static void loadAlarmExecute() {
        Map<String, IExecute> map = new HashMap<>();
        Iterator<IExecute> iExecutes = ServiceLoader.load(IExecute.class).iterator();
        IExecute tmp;
        while (iExecutes.hasNext()) {
            tmp = iExecutes.next();
            if (!map.containsKey(tmp.getName())) {
                map.put(tmp.getName(), tmp);
            } else {
                throw new DuplicatedAlarmExecuteDefinedException(
                        "duplicated alarm execute defined!" +
                                "\n" +
                                ">>name:" +
                                tmp.getName() +
                                ">>>clz:" +
                                tmp.getClass() +
                                ">>>clz:" +
                                map.get(tmp.getName())
                );
            }
        }
        cacheMap = map;
    }
    public static IExecute getExecute(String execute) {
        if (cacheMap == null) {
            synchronized (SimpleExecuteFactory.class) {
                if (cacheMap == null) {
                    loadAlarmExecute();
                }
            }
        }
        // 如果不存在,则降级为 LogExecute
        IExecute e = cacheMap.get(execute);
        return e == null ? cacheMap.get(LogExecute.NAME) : e;
    }
}
复制代码


上面对外就暴露一个方法,内部比较简单,如果传入标识对应的报警器没有,则返回一个默认的,确保不会因此挂掉


通过SPI加载所有的执行器的逻辑就一行


Iterator<IExecute> iExecutes = ServiceLoader.load(IExecute.class).iterator();
复制代码

然后需要关注的是循环内部,做了name的唯一性判断,不满足就直接抛出异常了


III. AlarmExecute内部实现



内部提供了两个基本的报警实现,比较简单


日志报警执行器

/**
 * 有些报警,不需要立即上报,但是希望计数, 当大量出现时, 用于升级
 * <p/>
 * Created by yihui on 2017/4/28.
 */
public class LogExecute implements IExecute {
    public static final String NAME = ExecuteNameGenerator.genExecuteName(LogExecute.class);
    private static final Logger logger = LoggerFactory.getLogger("alarm");
    @Override
    public void sendMsg(List<String> users, String title, String msg) {
        logger.info("Do send msg by {} to user:{}, title: {}, msg: {}", getName(), users, title, msg);
    }
}
复制代码


空报警执行器

/**
 * 空报警执行器, 什么都不干
 * <p>
 * Created by yihui on 2017/5/12.
 */
public class NoneExecute implements IExecute {
    public static final String NAME = ExecuteNameGenerator.genExecuteName(NoneExecute.class);
    @Override
    public void sendMsg(List<String> users, String title, String msg) {
    }
}
复制代码


IV. 小结



AlarmExecute 的定义,加载以及实现规则目前都已经完成


  • 定义:两个方法,一个执行报警方法,一个返回唯一标识方法
  • 加载:通过SPI方式加载所有定义的alarmExecute
  • 实现:由用户自定义实现IExecute接口,内部逻辑无任务特殊要求,只是需要确保每个executor的name唯一


整个系统的第一步已经迈出,但是有个问题就是什么时候,才会来调用 com.hust.hui.alarm.core.execut.SimpleExecuteFactory#getExecute 从而触发执行器的加载呢?


image.png


相关文章
|
1月前
|
弹性计算 安全 机器人
一键搞定定时自动化通知
您是否经常忘了需要每周要填报工作时长?您的团队是否需要每月定时盘点HC?您是否每月末都在工作群提醒大家更新OKR? 这些简单的定时任务是不是经常会忘记或者占用您的精力?如果你也有这些烦恼,是时候来试试这个应用与数据集成平台——阿里云计算巢AppFlow了,它能够像一个神经中枢,高效地串联起所有关键数据流,并且能够巧妙地运用现代化的通讯工具如钉钉群机器人,实现定时消息通知,让每一个重要信息都能准时送达,不再因为简单重复的定时工作而占用您的时间和精力~
173 0
|
1月前
|
弹性计算 运维 监控
动态服务器监控与通知系统
【4月更文挑战第30天】
22 0
|
6月前
|
监控
Hologres中,CPU水位告警是通过配置预警规则来实现的
Hologres中,CPU水位告警是通过配置预警规则来实现的
52 1
|
JSON 监控 Dubbo
基于SkyWalking的分布式跟踪系统 - 异常告警
基于SkyWalking的分布式跟踪系统 - 异常告警
246 0
|
资源调度 运维 监控
如何通过任务调度实现百万规则报警
报警是一个公司的日常需求,常见的形态除了满足运维过程中的基础设施监控报警(CPU/内存/磁盘等)之外,部分公司也会在应用指标(如 QPS、RT 等)及业务指标(如 GMV/日活 等)上有相应的报警需求。
3855 1
如何通过任务调度实现百万规则报警
|
SQL 缓存 负载均衡
线上cpu报警的一次接口优化
春天到了大地都复苏了,沉寂了很久的cpu也开始慢慢复苏了,所谓前人埋坑后人填坑,伴随着阿里云监控报警,线上CPU使用率暴增,于是就开始了排查之路。
|
资源调度 运维 Java
定时任务报警通知解决方案详解
随着微服务和云计算的兴起,定时任务技术也是发展迅速,不仅能做单机的定时任务,而且在分布式系统下应用也很广泛,成为了业务做兜底、数据处理的第一选择。
2443 3
定时任务报警通知解决方案详解
|
存储 资源调度 运维
如何通过链路追踪进行定时任务诊断
分布式任务调度平台 SchedulerX 有效地将用于微服务场景下的可视化全链路追踪能力引入至定时任务处理场景,这将大大提升定时任务在运行时可观测能力,有效地帮助定时任务执行过程中异常、耗时、执行卡住等问题的定位分析。
如何通过链路追踪进行定时任务诊断
|
监控 NoSQL 数据库
监控与报警|学习笔记
快速学习监控与报警
86 0
监控与报警|学习笔记
|
存储 JSON 数据格式
报警系统QuickAlarm之报警规则的设定与加载
既然命名为规则,那么就需要有对应的解析器,以根据报警规则和报警类型等相关输入条件,来选择对应的报警执行器,因此本文主要包括的内容就比较清晰了 1.报警规则的定义 2.报警规则的加载 3.报警规则的解析以及报警执行器选择
225 0
报警系统QuickAlarm之报警规则的设定与加载