这将是一个系列,讲解 夜莺监控 的设计思考。你可以把它理解为:原理、最佳实践和产品设计时的折中取舍。
核心要点
- 夜莺的核心定位是告警引擎,而不是完整运维平台,也不是替代所有数据源和采集器的监控大一统系统。
- 夜莺通过对接 Prometheus、VictoriaMetrics、MySQL、ClickHouse、Postgres、ElasticSearch 等数据源,统一完成告警规则管理、异常判定和事件分发。
- 从功能部件看,夜莺至少需要 WebAPI 和 alert 两类能力:前者负责配置和交互,后者负责周期性执行告警规则。
- 对企业内部系统来说,多进程拆分更方便维护;对开源项目来说,单进程能明显降低普通用户部署复杂度。
- 夜莺的高可用设计选择复用 MySQL 存储 alert 心跳信息,用简单机制完成实例存活判断和规则分片。
整体定位:夜莺首先是告警引擎
了解一个开源项目,最应该先搞清楚它的定位,也就是它到底解决什么问题。
夜莺的定位可以概括为四个字:告警引擎。
夜莺对接多种数据源,比如 Prometheus、VictoriaMetrics、MySQL、ClickHouse、Postgres、ElasticSearch。用户在夜莺里配置告警规则,夜莺根据这些规则查询数据源,判定异常并产生告警事件。告警事件产生之后,再经过 Pipeline 处理,最终通过各类通知媒介发出去。
可以用 Grafana 做类比:Grafana 也对接多种数据源,但侧重数据可视化;夜莺同样对接多种数据源,但侧重告警。

没有夜莺时,告警通常怎么做
在没有夜莺之前,各个数据源往往各自处理告警:
- Prometheus 的告警规则通常直接配置在
prometheus.yml里,管理起来稍有不便。 - VictoriaMetrics 使用 vmalert,逻辑和 Prometheus 类似。
- ElasticSearch 社区里用得比较多的是 elastalert 开源项目做告警判定。
- ClickHouse、MySQL、Postgres 等数据源,通常没有特别统一的告警引擎。
这样会带来一个问题:告警规则、通知媒介、消息模板、用户联系方式和历史事件都分散在不同系统里。数据源越多,规则治理越麻烦。
有了夜莺之后,就可以在一个地方统一管理告警规则、通知媒介、消息模板、用户联系方式等内容。同时,夜莺还可以对告警事件做 Pipeline 处理,例如:
- Relabel:类似指标 Relabel,对告警事件做标签改写。
- Enrichment:事件丰富,例如调用 CMDB 接口为事件补充上下文。
- Drop:丢弃一些特定告警事件。
- 其他自定义处理逻辑。
这里的核心不是“夜莺能不能接很多数据源”,而是“告警规则和事件处理能不能统一治理”。
夜莺的核心功能部件
确定项目定位之后,如果你是夜莺的设计者,接下来要回答的问题就是:一个告警引擎至少需要哪些功能部件?
首先,需要一个 WebAPI,用于和用户、第三方系统交互。用户需要在页面上做很多配置,例如:
- 数据源配置。
- 用户、角色和权限管理。
- 用户联系方式管理,例如电话、手机号等。
- 各类规则配置,例如告警规则、屏蔽规则、订阅规则。
- 通知媒介和消息模板管理。
- Pipeline 管理。
- 历史告警事件查看、统计和分析。
其次,需要有后台任务执行逻辑。它根据用户配置的告警规则周期性执行,查询数据源,判定数据异常并生成告警事件,最终发送通知。
这个后台逻辑至少要考虑几件事:
- 最简单的实现方式是一个告警规则一个 goroutine(轻量级线程)后台执行。
- 如果执行失败,需要通过监控指标反映异常,同时打印执行失败日志。
- 需要考虑高可用:某个实例挂掉后,其他实例要顶上来。
- 需要考虑 sharding:比如有两个实例、1000 条规则,每个实例应处理约 500 条规则,不能重复执行,而且要尽量均匀分配。
- 如果某个实例挂了,剩余实例要能接管原来由宕机实例负责的规则。
- 对单个实例而言,它需要知道当前总共有多少实例、哪些实例存活、哪些实例已经挂掉,否则无法判断自己应该接管哪些规则。
这就需要一个中心状态存储,或者引入 Raft 等协议。夜莺选择了更简单的路径,后面高可用部分会展开。
这个负责告警执行的功能部件,姑且称为 alert。所以,夜莺至少有两个基础部件:webapi + alert。实际上,夜莺还有其他功能部件,但这两个是理解整体设计的入口。
单进程还是多进程
既然夜莺至少包含 webapi + alert 两类功能,那它们应该做成一个进程,还是两个进程?
如果是公司内部系统,我更倾向于做成两个进程,方便维护。但作为开源项目,还要考虑普通用户的部署复杂度,所以更倾向于做成一个进程。
这是一个典型的产品工程取舍:
| 方案 | 优点 | 代价 |
|---|---|---|
| 多进程 | 模块边界清楚,扩缩容和故障隔离更方便 | 部署、配置、排障复杂度更高 |
| 单进程 | 部署简单,普通用户更容易跑起来 | 模块边界不如多进程直观 |
对开源项目来说,“用户能快速部署起来”非常重要。一个项目如果第一步部署就很复杂,会劝退大量社区用户。因此夜莺在这个阶段选择把多个功能收敛在一个进程里。
高可用设计
对于 WebAPI 功能部件而言,它本质上是一个无状态组件:接收 API 请求,然后对数据库做 CRUD。因此 WebAPI 可以水平扩展,部署多个实例,前面架设负载均衡,就能实现高可用。
alert 模块则不同。它需要协调分配告警规则,是有状态的。既然夜莺不可避免要使用数据库存储各类配置信息,那么就顺便用数据库存储 alert 的心跳信息,这样最简单。
夜莺的设计是:所有 alert 复用一个 MySQL,周期性写心跳。这样数据库的心跳表里就可以查到所有实例列表,以及每个实例最近一次心跳时间,从而判断哪些实例还活着、哪些实例已经挂了。
这个方案的好处是架构极简:
- 每个实例配置相同。
- 部署多个实例即可实现高可用。
- 实例存活状态可通过 MySQL 心跳表判断。
- 规则分片和故障接管可以基于实例列表完成。
对社区用户来说,这个方案也容易理解和运维。
设计取舍总结
| 设计问题 | 夜莺选择 | 背后原因 |
|---|---|---|
| 项目定位 | 告警引擎 | 监控领域已有采集器、时序库、可视化工具,统一告警能力更缺 |
| 数据源策略 | 对接多种数据源 | 不重新造所有存储,利用已有生态 |
| 进程模型 | 单进程为主 | 降低开源用户部署复杂度 |
| 高可用协调 | 复用 MySQL 心跳 | 简单、可理解、依赖少 |
| 告警事件处理 | Pipeline 化 | 给 relabel、enrichment、drop 等场景留扩展空间 |
这些选择不一定是所有系统的最优解,但它们符合夜莺的目标:做一个普通用户也能部署、企业团队也能扩展的统一告警引擎。
FAQ
夜莺为什么不定位成完整运维平台?
完整运维平台范围太大,资产、发布、变更、权限、监控、成本、流程都会牵扯进来。开源项目如果定位过宽,很容易什么都做一点但都不够深。夜莺选择把重点放在告警引擎上,是为了让项目边界更清晰。
单进程会不会影响扩展性?
单进程不等于单实例。夜莺可以部署多个相同配置的 n9e 实例,通过负载均衡承接 WebAPI 请求,并通过实例心跳和规则分片处理 alert 高可用问题。
为什么不用 Raft 做 alert 协调?
Raft 可以解决分布式一致性问题,但也会增加实现和运维复杂度。夜莺已经依赖 MySQL 存储配置,所以复用 MySQL 心跳表来做实例存活判断,是更简单、更适合社区用户的方案。
后记
本文介绍了夜莺的整体定位、核心架构、单进程还是多进程的抉择,以及高可用设计。
如果公司只有一个机房,或者有多个机房但机房之间有质量很好的网络专线,部署一套夜莺就可以了。如果有多个机房,但机房之间网络链路很差,就需要考虑夜莺的边缘机房架构模式。下一节详细介绍这个问题。