从设计到落地的完整实践
案例背景
某互联网公司(以电商平台为例)业务规模快速增长,微服务数量超过200个,日均产生日志量达50TB,运维团队频繁面临以下问题:

- 告警延迟:故障发生时,平均15分钟后才收到通知
- 告警风暴:一次故障触发300+条相关告警,人工排查困难
- 误报率高:约40%的告警无需处理,造成团队“狼来了”疲劳
- 租户隔离:多个业务线共用监控系统,需要相互隔离
为此,团队决定构建一套智能告警系统,目标是:1分钟发现、3分钟定位、10分钟恢复。
告警系统整体架构
1 告警系统在监控体系中的位置
┌─────────────────────────────────────────────────────┐
│ 告警系统整体架构 │
├─────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 数据采集层 │──▶│ 数据处理层 │──▶│ 告警判定层 │ │
│ └──────────┘ └──────────┘ └────┬─────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 通知触达层 │◀──│ 事件管理层 │◀──│ 告警去重层 │ │
│ └──────────┘ └────┬─────┘ └──────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ 告警协同/运维大盘 │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────┘
2 核心组件
| 组件 | 技术选型 | 职责 |
|---|---|---|
| 数据采集 | Prometheus + Grafana Agent | 采集指标、日志、链路数据 |
| 数据处理 | Kafka + Flink | 实时流式处理、规则匹配 |
| 告警规则引擎 | Prometheus AlertManager + 自研 | 阈值检测、智能降噪 |
| 存储 | ES + ClickHouse | 指标存储与告警历史查询 |
| 通知触达 | 自研 Notify Service | 多渠道通知、排班路由 |
| 协同 | 自研 OnCall 平台 | 告警认领、升级、复盘 |
告警生命周期管理
一个完整的告警从产生到关闭,经历以下状态流转:
┌────────────┐
│ 正常状态 │
└─────┬──────┘
│ 触发条件满足
▼
┌────────────┐
│ PENDING │◀── 等待确认期
└─────┬──────┘ (默认1分钟)
│ 超过等待期
▼
┌────────────┐
│ FIRING │──▶ 发送通知
└─────┬──────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ ACK(认领)│ │ 自动恢复 │ │ 升级为故障 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 处理中 │ │ CLOSED │ │ INCIDENT │
└────┬────┘ └─────────┘ └────┬────┘
│ │
▼ ▼
┌─────────┐ ┌─────────┐
│ RESOLVED │ │ 复盘/改进 │
└─────────┘ └─────────┘
关键设计点:
- 每个告警有
alert_id贯穿全生命周期 - 重复告警通过
fingerprint关联到同一alert_id - 告警状态变化全部通过事件总线广播
关键设计:告警降噪与收敛
这是本案例最具价值的部分,针对“告警风暴”问题,设计了四层降噪机制:
1 规则层降噪
图:告警降噪流水线
原始告警
│
▼
┌──────────────────────────────────────────┐
│ ① 相似告警聚合(指纹计算) │
│ ───────────────────────────────────── │
│ 指纹 = hash(指标名 + 主机 + 告警类型) │
│ 相同指纹的告警合并为一条 │
└──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ ② 告警风暴抑制(TopN策略) │
│ ───────────────────────────────────── │
│ 当同一服务告警 > 50条/分钟时 │
│ 仅保留 Top3,其余标记为 "被抑制" │
└──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ ③ 时间窗口延迟(Pending) │
│ ───────────────────────────────────── │
│ 指标异常持续 5 分钟才真正触发通知 │
│ 瞬时抖动不产生告警 │
└──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ ④ 告警合并通知(按通知渠道合并) │
│ ───────────────────────────────────── │
│ 同一时间段内,同一服务的多条告警 │
│ 合并为一条汇总消息发送 │
└──────────────────────────────────────────┘
2 智能去重 — 窗口内合并示例
# 伪代码:基于滑动窗口的相似告警合并
class AlertDeduplicator:
def __init__(self, window_seconds=600):
self.window = window_seconds
self.buffer = {} # {fingerprint: alert_state}
def process(self, new_alert):
fp = self._fingerprint(new_alert)
if fp in self.buffer:
existing = self.buffer[fp]
# 合并计数
existing.count += 1
existing.last_seen = new_alert.timestamp
# 保持原始告警时间不变,避免重置
existing.end_time = new_alert.timestamp
return None # 不产生新告警
else:
# 新告警,添加到缓冲区
self.buffer[fp] = AlertState(
fingerprint=fp,
first_seen=new_alert.timestamp,
count=1
)
return new_alert # 产生新告警
def _fingerprint(self, alert):
"""生成告警指纹:相同指纹的告警视为同一事件"""
raw = f"{alert.metric}:{alert.host}:{alert.alert_type}"
import hashlib
return hashlib.md5(raw.encode()).hexdigest()[:16]
3 告警级别与升级策略
告警分级模型:
┌────────────────────────────────────────────────────────────┐
│ 严重程度 │ 含义 │ 响应时间 │ 通知方式 │
├────────────────────────────────────────────────────────────┤
│ P0 │ 核心业务中断 │ 立即 │ 电话+短信+IM+邮件 │
│ P1 │ 重要功能受损 │ 5分钟 │ 短信+IM+邮件 │
│ P2 │ 非核心异常 │ 30分钟 │ IM+邮件 │
│ P3 │ 提示性信息 │ 不要求 │ 邮件(可选) │
└────────────────────────────────────────────────────────────┘
自动升级机制:
原始告警 ──▶ 15分钟未处理 ──▶ 自动升级到值班主管
│
▼
30分钟未处理 ──▶ 升级到团队负责人
│
▼
60分钟未处理 ──▶ 升级到CTO/VP
告警通知触达
1 多渠道通知架构
┌────────────────┐
│ 告警事件总线 │
└───────┬────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ SMS网关 │ │ IM机器人 │ │ 语音电话 │
└──────────┘ └──────────┘ └──────────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────┐
│ 统一通知模板引擎 │
│ - 短信模板 / IM模板 / 语音模板 │
│ - 上下文信息自动填充 │
└──────────────────────────────────┘
2 通知模板参考
告警通知消息内容设计,要求“一条消息看懂问题”:
[P0] 支付服务异常 - 订单量骤降
▸ 状态:FIRING (持续5分钟)
▸ 指标:http_request_total < 10 QPS
▸ 当前值:3 QPS | 正常值:1200 QPS | 偏差率:99.75%
▸ 影响范围:
- 支付接口成功率降至 32%
- 受影响用户:约 15,000 人
▸ 可能原因:
① 数据库连接池耗尽 (最近变更: v2.3.1 发布)
② 上游服务超时 (订单服务延迟 P99: 4.2s)
▸ 操作指引:
1. 查看实时大盘: http://dashboard/xxx
2. 查看日志: http://log.xxx/trace/xxx
3. 一键执行预案: RUNBOOK/payment_issue
▸ 处理人:张三 (电话: 138xxxx) | 值班经理:李四
3 值班排班系统
值班日历(轮询规则):
┌─────────┬─────────┬─────────┬─────────┬─────────┐
│ 周一 │ 周二 │ 周三 │ 周四 │ 周五 │
├─────────┼─────────┼─────────┼─────────┼─────────┤
│ 张三 │ 李四 │ 王五 │ 赵六 │ 轮流替补 │
│ (主) │ (主) │ (主) │ (主) │ (备) │
│ 钱七 │ 孙八 │ 周九 │ 吴十 │ 紧急替补 │
│ (备) │ (备) │ (备) │ (备) │ │
└─────────┴─────────┴─────────┴─────────┴─────────┘
节假日自动顺延 | 支持临时换班 | 支持个人偏好设置
案例实战:一次完整的告警处理过程
场景:数据库连接池耗尽
时间线:
14:00:00 - 某新版本发布后连接池配置错误
14:03:00 - 指标系统中"数据库活跃连接数"持续上升
14:05:00 - 告警触发:
"连接池使用率 > 90% 持续 2 分钟"
→ 级别 P1 → 通知值班人员(短信+电话+IM)
14:05:30 - 值班人员收到通知,通过手机查看告警详情
发现同时有 3 个关联服务出现类似告警
→ 系统自动聚合为同一个"事件"
14:07:00 - 值班人员点击"认领"按钮,开始处理
系统自动发送"处理中"状态给所有干系人
14:12:00 - 值班人员定位:数据库连接池 maxActive=50
新版本误改为 10,导致连接池快速耗尽
回滚配置 → 连接池恢复
14:15:00 - 指标恢复正常 → 告警自动关闭
系统发送"已恢复"通知
14:30:00 - 系统自动生成告警复盘报告
→ 同步到IM群 + 负责人邮箱
从告警到事件(Incident)的升级管理
当告警符合以下任一条件时,自动升级为事件(Incident):
| 条件 | 说明 |
|---|---|
| P0 告警 | 无论何时,立即升级 |
| P1 且 30分钟未解决 | 自动升级 |
| 连续2次错误修复尝试 | 算法检测到“修复未生效” |
| 影响到核心业务指标 | 如订单成功率 < 95% |
自愈能力与告警联动
1 策略与处理建议自动匹配
┌────────────────────┐
│ 告警触发 │
└─────────┬──────────┘
▼
┌────────────────────┐
│ 检索历史相似告警 │
│ - 是否有成功处理过? │
│ - 是否关联变更? │
└─────────┬──────────┘
▼
┌────────────────────┐
│ 从 Runbook 知识库 │
│ 匹配处理策略 │
└─────────┬──────────┘
▼
┌────────────────────┐
│ 自动执行可选动作: │
│ 1. 自动扩容 │
│ 2. 自动回滚 │
│ 3. 自动重启 │
│ (需授权策略允许) │
└────────────────────┘
2 告警自动化处置示例
# 自愈策略配置示例(YAML)
rules:
- name: "连接池耗尽自动处理"
condition:
metric: "db_connection_pool_usage"
threshold: 90
duration: "3m"
actions:
- name: "自动锁定异常SQL"
type: "kill_slow_queries"
params:
duration_threshold: "5s"
approval_required: false # 无需审批
- name: "自动扩容连接池"
type: "config_change"
params:
config_key: "maxActive"
new_value: "100"
auto_rollback: true # 5分钟后未恢复则回滚
approval_required: true # 需要审批
notifications:
- channel: "im"
template: "已自动执行应急预案,请关注"
实际执行效果:数据库类告警的自动恢复率从5%提升到35%。
告警系统的关键指标与效果
1 核心性能指标
| 指标 | 目标值 | 实际值 |
|---|---|---|
| 告警发现时间(MTTD) | ≤ 1分钟 | 45秒 |
| 告警响应时间(MTTA) | ≤ 5分钟 | 2分钟 |
| 告警恢复时间(MTTR) | ≤ 10分钟 | 5分钟 |
| 告警准确率(Precision) | ≥ 85% | 92% |
| 告警召回率(Recall) | ≥ 95% | 97% |
| 告警重复率 | ≤ 10% | 2% |
| 误报率(每周) | ≤ 5条 | 2条 |
2 降噪效果对比
实施前 vs 实施后(周平均数据对比)
告警总量: 1,200条 ──▶ 156条 ↓87%
有效告警: 220条 ──▶ 143条 ↑自动化识别率
误报/噪音: 980条 ──▶ 13条 ↓98.7%
人工介入次数: 140次 ──▶ 45次 ↓68%
3 冗余度与可靠性设计
告警系统自身的可用性保障:
┌────────────────────────────────────────────────────┐
│ 告警系统高可用方案 │
│ │
│ 采集高可用 规则引擎高可用 │
│ ┌──────────┐ ┌──────────┐ │
│ │ 双采集Agent│ │ 双实例运行 │ │
│ │ 独立进程 │ │ 主备切换 │ │
│ └──────────┘ └──────────┘ │
│ │
│ 消息队列高可用 通知通道备用计划 │
│ ┌──────────┐ ┌──────────┐ │
│ │ Kafka复制 │ │ 短信→IM→邮件 │ │
│ │ 多分区 │ │ 三级降级策略 │ │
│ └──────────┘ └──────────┘ │
└────────────────────────────────────────────────────┘
经验教训与最佳实践总结
1 踩过的坑
| 问题 | 后果 | 解决方案 |
|---|---|---|
| 告警阈值设置过于激进 | 频繁误报,团队麻木 | 采用动态基线 + 多维度检测 |
| 没有告警分组策略 | 一次故障 300+ 条告警 | 按服务/依赖关系分组聚合 |
| 通知策略单一(只有邮件) | 非工作时间无法及时响应 | 分级通知(电话/SMS/IM) |
| 缺少告警认领机制 | 多人处理同一告警/无人处理 | 增加值班排班 + 认领确认 |
| 没有告警关闭条件 | 故障已解决但告警一直挂着 | 告警自动恢复检测 |
2 最佳实践
-
告警必须有“可执行动作”
“每条告警消息必须注明下一步操作,否则不发送。” -
告警是手段,不是目的
- 能自动恢复的不告警 → 能脚本处理的不需要人
- 告警最终目标是被消除(通过自愈或根因修复)
-
持续优化闭环
- 周维度汇总告警数据 → 月维度输出优化报告
- 无效告警 → 关闭规则 + 标记降级
-
定期告警演练
- 每月一次故障注入测试(Chaos Engineering)
- 确保演练发现的问题能进入优化池
-
要包含业务上下文
不仅是技术指标,还要说明“影响了什么业务、多少用户、哪个区”。
技术栈选型参考
| 场景 | 推荐方案 | 备选方案 |
|---|---|---|
| 指标采集 | Prometheus | Telegraf |
| 存储 | Thanos + ClickHouse | VictoriaMetrics |
| 规则引擎 | 自研(基于Go) | Grafana Alerting;AlertManager |
| 通知触达 | 自研 Notify Service | PagerDuty;AlertManager webhook |
| 值班排班 | 自研 OnCall | Opsgenie;XWatch |
| 协同平台 | 自研 + 飞书/钉钉/企微 | Jira + Slack |
| 可视化 | Grafana + 自研 | Kibana + 自研 |
告警系统本质上是信息降噪系统,核心价值不是“发出更多告警”,而是 “让每一条发出的告警都值得被关注,并且尽可能自动完成处理”,从实际效果看,降噪、收敛、自愈和认领机制是告警系统能否真正落地的关键。