夜莺监控设计思考(五)告警原理和处理流程深度剖析

巴辉特 2025-10-13 15:38:31

这将是一个系列,讲解 夜莺监控 的设计思考,可以理解为原理+最佳实践+产品设计时的折中取舍。

本系列其他文章:

本篇聊聊夜莺最核心的逻辑:告警。涉及告警事件的产生、告警事件的后续处理、告警事件的通知。

夜莺的告警逻辑整体是追随 Prometheus 的逻辑,本文默认你已经对 Prometheus 的告警逻辑比较清楚。

前置知识

夜莺有两个进程:

  • n9e 部署在中心,既是告警引擎,又是 webapi
  • n9e-edge 部署在边缘机房,作为告警引擎

夜莺作为告警引擎,可以对接多种数据源,哪些告警引擎负责哪些数据源的告警判定,是需要用户告知夜莺的。

所以,

  • 启动夜莺的进程的时候,要做好规则,在 config.toml 配置文件里指定 EngineName,即引擎的名字,相同名字的引擎会当做一个引擎集群
  • 要在夜莺里配置数据源的信息,包括连接地址,以及和告警引擎集群的关联关系

然后,告警引擎就开始干活了,对自己负责的那些数据源,做告警判定。

告警判定是需要有告警规则的,即:哪些监控指标阈值是多少,需要用户给出。所以,需要用户在夜莺页面上配置告警规则(可以导入内置的现成的一些告警规则,然后二次修改)。

OK,进入正题。

告警处理流程概述

我们来看整个告警流程示意图:

告警流程示意图

  1. 用户在 Web UI 配置告警规则,规则保存在 DB 中(通常是 MySQL)。
  2. 告警引擎(n9e 进程内置一个告警引擎,边缘模式下 n9e-edge 进程里也内置告警引擎)从 DB 同步告警规则到内存中(通常 n9e-edge 无法直接读 DB,是调用的中心端 n9e 的接口获取的告警规则)。
  3. 告警引擎会为每条告警规则创建一个 goroutine(协程,姑且可以理解为轻量级线程),按照用户在告警规则里配置的频率,周期性查询存储,对数据做异常判定,最终生成告警事件。
  4. 产生告警事件后,判定是否被屏蔽了,如果被屏蔽了直接丢弃事件,否则,要把事件持久化到 DB 中(通常是 MySQL),然后再走后面的通知规则。
  5. 通知规则包含两部分,一个是若干事件处理器(比如 relabel、event update、event drop、ai summary 等),另一个是若干告警通知配置(比如 Critical 的告警事件打电话、发短信,Warning 的告警事件只发邮件)。

1. 生成告警事件

这块逻辑和 Prometheus 很像,只不过 Prometheus 只支持对自身 TSDB 的数据做告警判定,夜莺支持其他更多类型的数据源。

以 Prometheus、VictoriaMetrics 等时序数据源为例,夜莺会根据告警规则里的 promql 查询数据(按照执行频率周期性查询),查到了就认为有异常数据,就要产生告警事件,如果配置了持续时长,则要连续等待更长时间,每次都查到异常数据才产生告警事件。

产生告警之后,如果后面某次查询查不到对应的异常数据了,就认为告警恢复。如果配置了留观时长,则要多等待留观时长对应的时间,这个时间内没有再查到异常数据才算恢复。

注意:告警之后,如果新的监控数据写入时序库有延迟,比如数据采集链路出了问题或者变慢了,告警引擎查询的时候查不到数据,也会报恢复。因为引擎分辨不出是数据不符合阈值了所以没有返回还是数据延迟了所以没有返回。

2. 告警事件的数据结构

告警事件是根据告警规则查询数据源产生的。告警事件有很多属性,以及标签。

  • 属性:比如告警级别、规则标题、事件状态、触发时间、触发时的值等,里边很多属性是来自事件对应的告警规则
  • 标签:事件的标签有两个来源,一个是查询数据源返回的数据里的标签,一个是告警规则的附加标签

比如某个告警规则,其 promql 为 disk_used_percent>0,根据这个 promql 查到的数据为:

promql 查询结果

那就会产生 3 条告警事件,因为 promql 返回了 3 条数据,3 条数据的标签不同,用于区分三个事件。

后续的各类规则,比如屏蔽规则、订阅规则、通知配置,都要对告警事件做筛选,核心就是按照告警事件的属性、标签进行筛选。

3. 告警屏蔽

告警事件产生之后,会去找告警规则所属的业务组下面是否有屏蔽规则,如果有,就拿着这个告警事件挨个去匹配屏蔽规则,如果匹配了某个屏蔽规则,则告警被屏蔽,直接丢弃告警事件,不会写入 DB。

后面可能会修改这块的逻辑,屏蔽的告警事件也存 DB,只是打个“已屏蔽”的标记。

注意,屏蔽规则只生效在同业务组下的告警规则产生的事件。防止某个新兵配置了一个全局屏蔽规则,把公司所有告警都屏蔽了。

屏蔽规则的多个条件之间是 AND 的关系,即 的关系,这点请注意。官网文档里也有相关说明,这里不再赘述。

4. 告警事件持久化

告警事件如果没有被屏蔽,就要入库持久化,写入 alert_his_event 表,这是所有历史事件的全量存档表,同时,也会修改 alert_cur_event 表,这是活跃告警表,即当前尚未恢复的告警事件的一个表。

如果从细节来讲,其实可以把告警和事件区分开(后面版本的夜莺可能会做这个改进)。

比如 a 机器 mem_free 指标在 10:00:00 告警了,产生了一条告警事件,其 id 是 1,hash 是 xx,告警规则里通常会配置重复通知,每隔 1h 重复产生一条新的事件。即 a 机器 mem_free 指标在 11:00:00 会再次产生一条告警事件,其 id 可能为 2,hash 仍然是 xx。

这两条事件是在两个时刻产生的,是两个对象,所以 id 不同,但是确实都是说的一个事,都是 a 机器的 mem_free 告警,所以 hash 值相同,后面告警恢复的时候,会产生一个新的事件,其 id 可能为 3,hash 值仍然是 xx,这样才能关联起来。

有了上面的讲解,alert_cur_event 表的更新逻辑就成了:

  • 来了一个新的事件,根据这个事件的 hash,删除 alert_cur_event 的记录
  • 如果这个新事件是恢复事件,就啥都不用干了,相当于:恢复告警就要从活跃告警里清理掉;如果新事件是告警事件,就把最新的事件写入 alert_cur_event

这个设计其实不太好,在整个体系里只有 event 的概念,没有 alert 的概念,少抽象了一个领域对象。后面的版本可能会改进。

5. 事件关联通知规则

告警事件产生了,后面可能涉及对告警事件的二次处理,比如做 relabel、做过滤等,以及把告警事件投递发送出去。这就涉及到“通知规则”了。

那告警事件具体和哪个通知规则关联呢?在夜莺里有两种方式:

  1. 告警规则里直接配置通知规则:这个告警规则产生的所有告警事件,都直接走这个通知规则
  2. 使用订阅规则:订阅规则相当于是写了一个告警事件的筛选条件,会筛选到一堆告警事件,筛到的这些事件交予订阅规则里配置的通知规则去处理

通常走第 1 种方案就可以了,对于某些全局级别的通知规则,可以走方案 2。

6. 通知规则

通知规则最核心的是配置通知媒介和告警接收人。之前这些信息是在告警规则里直接配置的,感觉不灵活,从 V8 开始单独提取出来了。

如果各个团队分别收告警,通常,每个团队要对应一个通知规则,在这个通知规则里,一次性配置好什么样的告警发什么通知媒介,然后这个团队的所有告警规则都绑定这个通知规则即可。

通常,根据告警级别定义不同的通知媒介,比如:

  • Critical 的告警,打电话、发短信、发邮件、钉钉
  • Warning 的告警,只发短信、发邮件、钉钉
  • Info 的告警,只发邮件、钉钉

通知这里,还要对两个东西做实体建模抽象,一个是通知媒介,一个是消息模板。V8 版本可以很方便对接一个企业内部的通知媒介。具体可以参考相关文档,这里不再赘述。

7. 事件处理器

V8 还引入了一个事件处理器的概念,可以把多个处理器组成一个 Pipeline,通知规则里可以引用多个 Pipeline。

这里的思考是:告警事件产生之后,可能有各种不同的处理,比如做 relabel、filter、enrichment 等,Pipeline 相当于抽象了一个 workflow,让用户在事件处理的链路上插入一些自定义的逻辑,给用户极大的灵活性。

快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云
OpenSource
开源版
Flashcat
Flashcat