2. 告警系统架构设计:告警数据流、告警生命周期、告警系统分层架构
聊到告警系统,很多人第一反应就是「收到告警通知」。但真正在生产环境里跑过的人都知道,这背后其实是一整套复杂的数据流转和状态管理。我最早接手公司告警平台时,面对的就是一堆告警满天飞、重复告警刷屏、没人知道告警到底处理到哪一步的烂摊子。后来花了整整两个季度重构架构,才把这块理顺。
今天我们就来拆解一下,一个成熟的告警系统,它的数据是怎么流的,告警从生到死经历了什么,以及系统本身应该怎么分层。
2.1 告警数据流:从采集到触达
告警数据流,说白了就是一条告警从产生到被用户看到,中间经过了哪些环节。我习惯把它分成四个阶段:采集、处理、路由、触达。
核心数据流路径:
监控数据源 → 告警引擎 → 告警存储 → 告警路由 → 通知渠道 → 用户终端
每个阶段都有它自己的职责和坑。我们一个个来看。
2.1.1 采集层
采集层负责从各种监控系统拉取数据。常见的来源有:Prometheus、Zabbix、云厂商的监控服务、自定义Agent上报的指标。这里有个容易被忽略的点——数据格式的统一。不同来源的告警,字段名可能完全不同。比如Prometheus的alertname,在Zabbix里叫trigger_name。我建议在采集层就做一次标准化,把字段映射成统一格式,后面处理起来会省很多事。
2.1.2 处理层
处理层是告警系统的核心大脑。它负责做几件事:
- 去重:同一台机器CPU飙高,可能同时被Prometheus和Zabbix都检测到了。不做去重,用户会收到两条一模一样的告警。
- 聚合:比如某个机房网络抖动,导致100台机器同时上报「连接超时」。这时候应该聚合成一条「机房网络异常」的告警,而不是100条。
- 抑制:如果已经有一条「数据库主库宕机」的告警,那所有从库的「连接失败」告警就应该被抑制掉。因为根因是主库挂了,从库的告警是衍生出来的。
- 关联分析:把时间上相近、资源上相关的告警关联起来,帮助运维人员快速定位根因。
我的经验:去重和抑制的逻辑一定要写在处理层,不要依赖告警源去做。我曾经遇到过一个团队,把去重逻辑写在了Prometheus的Alertmanager里,结果后来换了监控系统,去重逻辑全得重写。处理层做这件事,解耦性最好。
2.1.3 路由层
告警处理完之后,需要决定发给谁、怎么发。这就是路由层的职责。路由规则通常基于几个维度:
- 告警级别:P0级别的告警直接打电话+短信+邮件,P3级别的可能只发一条消息到群里。
- 业务归属:支付业务的告警发给支付团队,存储的告警发给DBA。
- 值班表:根据当前值班人员动态路由。
- 时间窗口:凌晨的告警可能先发到值班群,白天再升级到负责人。
2.1.4 触达层
触达层就是最终的通知渠道。常见的包括:邮件、短信、电话、钉钉/飞书/企微机器人、Webhook回调。这里有个细节——通知的可靠性。短信和电话有可能会被运营商拦截,邮件可能会进垃圾箱。我建议对P0/P1级别的告警,至少配置两个不同的通知渠道做冗余。
注意:不要把所有告警都发到同一个群里。我曾经见过一个团队,把所有环境的告警都发到一个群,结果开发群、运维群、业务群全混在一起,真正重要的告警反而被淹没了。按环境、按业务、按级别分开,是基本操作。
2.2 告警生命周期:从生到死
一条告警从产生到关闭,会经历多个状态。理解这个生命周期,对设计告警系统的状态机非常有帮助。
我一般把告警生命周期分成以下几个阶段:
| 阶段 | 状态 | 说明 |
|---|---|---|
| 1 | 触发(Firing) | 告警条件满足,系统生成告警记录 |
| 2 | 通知中(Notifying) | 系统正在尝试通过配置的渠道发送通知 |
| 3 | 已通知(Notified) | 通知成功发送,等待人工处理 |
| 4 | 确认中(Acknowledging) | 有人认领了这条告警,正在处理 |
| 5 | 已确认(Acknowledged) | 告警已被确认,但问题可能还没解决 |
| 6 | 恢复中(Resolving) | 告警条件不再满足,系统自动或手动触发恢复 |
| 7 | 已恢复(Resolved) | 告警问题已解决,状态正常 |
| 8 | 已关闭(Closed) | 告警生命周期结束,归档处理 |
这里有个容易踩的坑——告警恢复和告警关闭是两回事。恢复只代表指标回到了正常范围,但可能还需要人工确认问题是否真的解决了。关闭则意味着这条告警的生命周期彻底结束。我习惯在系统里把这两个状态分开,恢复后自动触发一个「恢复通知」,但告警记录仍然保留在「已恢复」列表里,等人去确认关闭。
关键设计点:告警状态机一定要支持「重入」——也就是一条告警在「已恢复」状态下,如果指标再次异常,应该能重新回到「触发」状态,而不是生成一条全新的告警。否则你会看到大量重复的告警记录,排查起来非常痛苦。
2.3 告警系统分层架构
一个生产级的告警系统,我建议至少分成四层。每一层各司其职,层与层之间通过API或消息队列解耦。
下面这张图是我自己画的一个分层架构示意,你可以参考一下:
这四层架构的好处是:每一层都可以独立扩展、独立部署。比如采集层压力大了,可以加采集节点;处理层逻辑复杂了,可以单独拆成微服务。我见过一些团队把处理层和路由层混在一起,结果改一个路由规则,整个处理逻辑都要重新部署,非常不灵活。
2.3.1 存储层的设计
虽然上面分层里没有单独画存储层,但它其实是贯穿整个架构的。告警数据需要持久化,用于历史查询、统计分析和告警复盘。我建议用两种存储:
- 时序数据库(如InfluxDB、TimescaleDB):存储告警指标数据,用于趋势分析和告警阈值调整。
- 关系型数据库(如PostgreSQL):存储告警记录、状态变更历史、处理日志。方便做复杂的查询和报表。
一个小技巧:告警记录表一定要做分区。按月份分区,查询历史告警时只扫描对应分区,性能会好很多。我曾经帮一个团队优化过,他们全表扫描几千万条告警记录,查询一次要十几秒。加了分区之后,秒级返回。
2.4 架构设计中的几个关键决策
最后,我想聊聊在架构设计中,几个需要你认真思考的决策点:
- 同步还是异步? 告警处理链路建议全异步。采集层把告警事件丢到消息队列,处理层消费处理,路由层再消费。这样即使某个环节挂了,告警事件也不会丢。
- 告警风暴怎么防? 一定要在采集层或处理层做限流和降级。比如某个监控项突然产生大量告警,可以配置一个「每分钟最多生成N条」的阈值。超过的部分直接丢弃或降级为日志。
- 告警的幂等性怎么保证? 每条告警应该有一个全局唯一的ID(比如用告警源+告警名称+时间戳+资源ID组合生成)。处理层根据这个ID做去重,确保同一条告警不会被重复处理。
- 告警的优先级怎么动态调整? 我建议不要写死优先级。比如一条P2告警,如果持续30分钟没人处理,可以自动升级为P1。这种动态升级策略,能有效避免告警被忽略。
曾经踩过的坑:有一次我们上线了一个新的监控项,结果配置的告警阈值太敏感,导致一小时内生成了20万条告警。消息队列直接被打满,其他正常的告警全部延迟了。从那以后,我要求所有新监控项上线时,必须先在「静默模式」下运行一周,观察告警量级,确认没问题了再正式开启通知。
好了,告警系统的架构设计就聊到这里。数据流、生命周期、分层架构,这三块是告警系统的骨架。骨架搭好了,后面做配置、做调优、做自动化,才有坚实的基础。下一节我们会深入聊聊告警规则的配置技巧,包括阈值怎么设、规则怎么写才能减少误报。嗯,到时候再细聊。
公众号:蓝海资料掘金营,微信deep3321