系统设计:监控与告警系统
监控系统要回答三个问题:系统现在健康吗、哪里出问题了、出问题该通知谁。它面对的挑战不是功能复杂,而是数据量巨大(每秒百万级指标点)与告警必须精准(漏报误报都是事故)。
1. 需求分析
功能性需求
- 指标采集:主机、容器、应用、中间件的指标上报
- 存储与查询:按时序存储、支持区间查询与聚合函数
- 告警:规则配置、阈值/表达式、触发与恢复
- 通知:电话、短信、IM、邮件多渠道
- 值班与升级:轮班表、超时升级、认领与关闭
非功能性需求
- 采集延迟:秒级,指标写入后 10 秒内可查
- 查询延迟:仪表盘 P99 低于 1 秒
- 可用性:监控系统自身 99.99%,不能先于被监控系统挂掉
- 保留期:原始数据 15 天,降采样数据 1 年
核心难点
- 写入洪峰:百万级 series,每 15 秒一个点
- 基数爆炸:标签维度组合导致 series 数量失控
- 告警风暴:一次故障引发上百条告警,必须收敛
2. 容量估算
- 采集对象:10 万台主机,每台 500 个指标 → 5000 万 series
- 采集间隔:15 秒 → 每 series 每天 5760 个点
- 日写入点:5000 万 × 5760 ≈ 2.88 万亿点/天
- 每点约 2 字节(压缩后)→ 约 5.8 TB/天原始数据
- 保留 15 天:约 87 TB
- 降采样(1 分钟粒度,保留 1 年):约 43 亿点/天 × 365 ≈ 1.6 万亿点,约 3 TB
结论:压缩与降采样是存储可行性的前提。时序数据库的压缩率可达 10 倍以上,正是为此设计。
3. 整体架构
被监控对象(主机/容器/应用)
│ 上报(推) 或 被拉取(拉)
▼
采集层(Agent / Exporter / 网关)
│
▼
写入缓冲(Kafka,削峰填谷)
│
▼
时序数据库(分片 + 副本)
│
┌──┴───────────────┐
▼ ▼
查询服务(仪表盘) 告警引擎(规则求值)
│ │
▼ ▼
可视化前端 通知分发(抑制/静默/聚合)
│
▼
值班系统(电话/短信/IM)
数据流
采集层把指标写入缓冲,时序库消费落盘;告警引擎周期性拉取规则涉及的时间序列做求值,命中则生成告警事件走通知链路。
4. 数据模型
时序数据模型
指标 = 名字 + 标签集合 + 时间戳 + 数值。标签是维度,也是查询与聚合的依据。
metric: http_requests_total
labels: {job="api", instance="10.0.0.1:8080", method="GET", status="200"}
value: 12345
ts: 1696000000
存储布局
series_index (
series_id BIGINT PRIMARY KEY,
metric VARCHAR(128),
label_hash CHAR(64), -- 标签集合的哈希,用于去重
labels JSON
)
chunks (
series_id BIGINT,
start_ts BIGINT,
end_ts BIGINT,
data BLOB -- 压缩后的时间戳与数值
)
设计要点
- 标签集合相同则复用同一
series_id,避免重复存储标签 - 数据按时间分块(如 2 小时一块),便于压缩与过期删除
- 倒排索引(标签 → series_id)支撑多维查询
5. 指标采集:拉与推
拉模式 Pull
服务端周期性地从目标的 HTTP 端点抓取指标(Prometheus 风格)。
优点:目标健康状态一目了然(抓不到即异常)、配置集中、易做服务发现。
缺点:目标必须可被访问(跨网段/防火墙困难)、短生命周期任务难覆盖、需要服务发现配合。
推模式 Push
目标主动把指标推到网关或队列。
优点:穿透网络限制、适合短任务与边缘设备、天然支持离线缓存重传。
缺点:需要额外的健康检测(推不上不代表挂了)、推送方需处理重试。
对比
| 维度 | 拉模式 | 推模式 |
|---|---|---|
| 目标可达性 | 需服务端可达目标 | 目标可达服务端即可 |
| 健康检测 | 抓取失败即异常 | 需额外机制 |
| 短任务 | 不友好 | 友好 |
| 配置管理 | 集中 | 分散 |
| 典型代表 | Prometheus | StatsD、OpenTelemetry |
混合实践
主流方案是「推拉结合」:长驻服务用拉,短任务与边缘用推,统一汇入同一时序库。这样既保留健康检测能力,又覆盖全部场景。
6. 时序存储、降采样与查询
存储引擎要点
- 列式存储:同一指标的值连续存放,压缩率高
- 时间戳差分编码:等间隔采集的时间戳差分后几乎为常量,压缩极佳
- Gorilla 类压缩:XOR 相邻浮点值,大幅降低数值存储
- 按时间分片:过期数据整块删除,无需逐条清理
降采样
原始精度保留 15 天,之后按 1 分钟、5 分钟、1 小时逐级降采样,保留更长时间。
| 层级 | 粒度 | 保留期 | 用途 |
|---|---|---|---|
| 原始 | 15 秒 | 15 天 | 排障、精确回溯 |
| 一级 | 1 分钟 | 90 天 | 趋势分析 |
| 二级 | 5 分钟 | 1 年 | 容量规划 |
| 三级 | 1 小时 | 3 年 | 长期报表 |
降采样时通常同时保留 max、min、avg、sum,避免聚合后丢失峰值信息(如 P99 延迟)。
基数控制
标签组合爆炸是时序库的头号杀手。做法:
- 禁止把用户 ID、请求 ID 等高基数字段作为标签
- 对标签数量设上限并做监控
- 高基数场景改用日志或追踪而非指标
查询语言
类 PromQL 的查询支持:按标签选择、区间聚合(rate、sum、avg、histogram_quantile)、跨序列运算。
# 示例:最近 5 分钟各实例的请求速率
rate(http_requests_total[5m])
# 示例:按状态码聚合的 P99 延迟
histogram_quantile(0.99, sum(rate(http_latency_bucket[5m])) by (le))
查询优化
- 时间范围裁剪:只读相关时间块
- 倒排索引预筛:先定位 series 再做聚合
- 查询结果缓存:仪表盘常用查询缓存数十秒
- 预聚合:热门面板预先算好,避免实时扫全量
慢查询治理
限制查询时间范围与返回点数,超限拒绝;对大范围查询强制走降采样数据。
7. 告警规则与抑制静默
规则求值
告警引擎按固定周期(如每 30 秒)对规则表达式求值,满足条件持续 N 次后触发,恢复时发恢复通知。
alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
for: 2m
labels:
severity: critical
annotations:
summary: 5xx 错误率超过 5%
触发与恢复
for持续时间避免抖动误报(瞬时毛刺不告警)- 恢复通知同样重要,避免值班人员不确定是否已恢复
抑制 Inhibition
当高层故障发生时,抑制其引发的下游告警。例如「机房网络故障」抑制该机房所有主机的「实例不可达」告警,避免告警风暴。
静默 Silence
计划内维护时临时屏蔽匹配的告警,按标签匹配、带到期时间,自动失效。
告警收敛
| 手段 | 作用 |
|---|---|
| 分组 | 同规则同类告警合并成一条 |
| 抑制 | 高优故障屏蔽衍生告警 |
| 静默 | 维护期临时屏蔽 |
| 去重 | 相同告警只发一次 |
| 降噪 | 基于历史判定是否为已知噪声 |
8. 通知分发与值班
分发链路
告警事件 → 路由(按严重级别与团队)→ 聚合(合并同类)→ 通道选择 → 送达 → 回执与升级。
值班与升级
- 轮班表定义谁在什么时段负责哪个服务
- 未在时限内认领则升级到上一级或下一班
- 电话通道用于最高优先级,短信/IM 用于中低优先级
通知渠道对比
| 渠道 | 时效 | 打扰度 | 适用级别 |
|---|---|---|---|
| 电话 | 秒级 | 极高 | P0 |
| 短信 | 秒级 | 高 | P1 |
| IM 群 | 秒级 | 中 | P2 |
| 邮件 | 分钟级 | 低 | P3/日报 |
防打扰
非工作时间的低优告警只进 IM 不打电话;同一告警合并后只发一次;提供一键认领与静默入口。多渠道触达的模板化与频控可参考 通知系统设计。
削峰与缓冲
采集洪峰用消息队列削峰填谷,具体可参考 消息队列设计。
9. 可观测性三支柱
指标、日志、追踪
- 指标(Metrics):低成本、高聚合,回答「系统整体是否正常」
- 日志(Logs):高细节、高成本,回答「具体发生了什么错误」
- 追踪(Traces):请求级全链路,回答「慢在哪一跳」
三者用统一的 trace_id 与标签体系串联,从指标发现异常 → 追踪定位链路 → 日志确认根因。
关联设计
- 指标与追踪共享服务名、实例等标签,可互相跳转
- 日志中嵌入 trace_id,追踪中可下钻到对应日志
- 告警触发时自动附带相关追踪与日志入口
采集成本权衡
指标全量采集,日志采样或分级(错误全留、调试采样),追踪按比例采样(如 1%)并对慢请求加权采样。
10. 面试常见问题
Q: 拉模式和推模式到底选哪个?
长驻服务用拉(可做健康检测、配置集中),短任务与跨网段用推,生产系统几乎都是推拉结合。
Q: 时序数据量太大怎么存?
三重手段:时序专用压缩(差分 + XOR)、按时间分块存储、分级降采样。核心是让「越老的数据粒度越粗」。
Q: 告警风暴怎么解决?
抑制 + 分组 + 静默 + 去重。一次机房级故障应只产生一条根因告警,衍生告警被抑制。
Q: 怎么减少误报?
设置 for 持续时间过滤毛刺、用多条件组合而非单阈值、基于历史基线做动态阈值、上线前用回放数据验证规则。
Q: 监控系统自己挂了怎么办?
多副本 + 独立部署 + 与被监控系统隔离(不同机房/账号),并提供轻量的黑盒拨测做兜底。
Q: 高基数标签为什么致命?
每个标签组合是一个独立 series,基数爆炸会让索引与内存线性膨胀,最终拖垮整个存储。应限制标签维度,高基数需求改用日志或追踪。
Q: 三支柱必须都上吗?
不必一步到位。中小团队先做指标 + 告警(性价比最高),再补日志,最后按需上追踪。关键是用统一标签体系为后续关联预留空间。
总结
监控与告警系统的答题主线是采集—存储—告警—通知四段:采集用推拉结合覆盖全场景,存储靠压缩与降采样控制成本,告警靠 for 抑制与静默收敛风暴,通知靠值班与升级保证有人响应。最后用「指标、日志、追踪」三支柱收口,说明它们如何用统一标签串联。能把「告警风暴」和「高基数」这两个坑讲透,就是这道题的高分点。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。