一、 那个凌晨,系统挂了30分钟我们才知道
聊监控告警之前,先讲一个让人后背发凉的真实经历。
某个周六凌晨2点,我们的数据库主库突然宕机了。自动切换脚本因为网络超时没有执行成功,系统处于"半死不活"的状态------部分用户能访问,部分用户报错。
问题是:我们直到2点37分才收到第一通报警电话。
整整37分钟,系统处于异常状态,而我们完全不知情。值班工程师在睡觉,监控大屏没有声音告警,邮件告警被淹没在垃圾邮件里。
事后复盘,我们的监控体系存在三个致命问题:
告警渠道单一:只有邮件,深夜没人看。
告警阈值不合理:数据库连接数阈值设置得太高,达到阈值时系统已经半瘫痪了。
缺乏分级告警:P0级故障和P3级警告混在一起,无法区分轻重缓急。
这次事故之后,我们决心从零搭建一套"能用、可靠、不扰民"的监控告警体系。
本文将完整复盘这套体系的建设过程,重点分享监控指标设计、告警规则配置以及值班响应机制的实战经验。
二、 可观测性的三大支柱
在开始搭建之前,需要先理解一个核心概念:可观测性(Observability) 有三个支柱。
支柱 说明 解决什么问题 典型工具
指标(Metrics) 数值型时序数据(QPS、RT、CPU、内存) "系统现在什么状态?" Prometheus
日志(Logging) 结构化/非结构化文本(错误日志、访问日志) "具体发生了什么?" ELK / Loki
链路追踪(Tracing) 请求在分布式系统中的完整路径 "哪个环节出了问题?" Jaeger / SkyWalking
这三个支柱的关系可以这样理解:
Metrics告诉你"系统出问题了"
Logging告诉你"具体是什么错误"
Tracing告诉你"问题出在哪个服务、哪个环节"
三者缺一不可。没有Metrics,你发现不了问题;没有Logging,你定位不了根因;没有Tracing,你不知道问题出在哪个链路上。
三、 技术选型:为什么选Prometheus + Grafana?
监控领域有很多成熟方案,我们做了一番对比:
对比维度 Zabbix Prometheus + Grafana 云厂商监控(如云监控)
数据模型 传统Agent采集 拉取式(Pull Model) 推送式
查询语言 有限 PromQL(强大、灵活) 有限
可视化 一般 Grafana(业界标杆) 良好
云原生支持 较弱 原生支持K8s/容器 中等
告警能力 内置 AlertManager(灵活) 内置
成本 开源免费 开源免费 按量付费
社区生态 成熟但传统 极其活跃、生态丰富 厂商绑定
学习曲线 中等 陡峭(但值得) 低
选型结论:Prometheus + Grafana + AlertManager
理由:
Prometheus的Pull Model更适合动态的容器化环境
PromQL的查询能力远超传统监控方案
Grafana的可视化能力是业界天花板
开源且社区活跃,遇到问题容易找到解决方案
四、 整体架构
text
┌─────────────────────────────────────────────────────────────────┐
│ 数据采集层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Node │ │ JVM │ │ MySQL │ │ Redis │ │ Nginx │ │
│ │Exporter │ │Exporter │ │Exporter │ │Exporter │ │Exporter │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ └───────────┴───────────┴───────────┴───────────┘ │
└─────────────────────────────────────────────────────────────────┘
│ Pull(每15秒)
▼
┌─────────────────────────────────────────────────────────────────┐
│ 存储层 │
│ Prometheus TSDB │
│ (时序数据库,默认保留15天) │
└─────────────────────────────────────────────────────────────────┘
│
├──────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────────────────────────────┐
│ 告警层 │ │ 展示层 │
│ AlertManager │────▶│ ┌─────────────────────────────────┐ │
│ ├─ 分组 │ │ │ Grafana │ │
│ ├─ 抑制 │ │ │ ├─ 系统总览大盘 │ │
│ ├─ 静默 │ │ │ ├─ 应用性能大盘 │ │
│ └─ 通知渠道 │ │ │ ├─ 数据库大盘 │ │
│ ├─ 钉钉/企微 │ │ │ ├─ 业务指标大盘 │ │
│ ├─ 电话 │ │ │ └─ 自定义告警看板 │ │
│ ├─ 短信 │ │ └─────────────────────────────────┘ │
│ └─ 邮件 │ └─────────────────────────────────────────┘
└─────────────────┘
五、 监控指标设计
监控最怕的事情是"什么都想监控,结果什么都监控不好"。需要遵循分层设计的原则,从基础设施到业务指标逐层覆盖。
5.1 基础设施层(服务器/网络)
指标分类 具体指标 告警阈值参考
CPU 使用率、负载(load average) > 80% 持续5分钟
内存 使用率、可用内存、Swap使用 > 90% 持续5分钟
磁盘 使用率、IOPS、读写延迟 使用率 > 85%
网络 入/出流量、丢包率、连接数 丢包率 > 1%
系统 文件描述符数、进程数 文件描述符 > 80%
yaml
Prometheus告警规则示例
groups:
- name: node_alerts
rules:-
alert: HighCPUUsage
expr: (100 - (avg(rate(node_cpu_seconds_total{mode="idle"}5m)) * 100)) > 80
for: 5m
labels:
severity: warning
annotations:
summary: "CPU使用率过高"
description: "主机 {{ labels.instance }} CPU使用率 {{ value }}% 持续超过5分钟"
-
alert: DiskFull
expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 15
for: 5m
labels:
severity: critical
annotations:
summary: "磁盘空间不足"
description: "主机 {{ $labels.instance }} 磁盘可用空间低于15%"
5.2 应用服务层(JVM/容器)
指标分类 具体指标 告警阈值参考
JVM堆内存 使用率、GC频率、GC耗时 Heap使用率 > 85%
JVM非堆 Metaspace、DirectBuffer Metaspace > 90%
线程 活跃线程数、阻塞线程数 线程池队列 > 1000
类加载 已加载类数、卸载类数 异常增长需关注
容器 Pod CPU/内存使用率、重启次数 重启次数 > 3次/小时
java
// Spring Boot Actuator + Micrometer 自动暴露指标
// application.yml
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
tags:
application: order-service
env: production
yaml
-
JVM告警规则
-
alert: JVMHeapUsageHigh
expr: (sum(jvm_memory_used_bytes{area="heap"}) / sum(jvm_memory_max_bytes{area="heap"})) * 100 > 85
for: 5m
labels:
severity: warning
annotations:
summary: "JVM堆内存使用率过高"
description: "服务 {{ labels.application }} 堆内存使用率 {{ value }}%"
-
alert: FrequentGC
expr: rate(jvm_gc_pause_seconds_count5m) > 5
for: 5m
labels:
severity: warning
annotations:
summary: "GC频率过高"
description: "服务 {{ $labels.application }} 过去5分钟GC次数超过5次"
5.3 业务服务层(接口/依赖)
指标分类 具体指标 告警阈值参考
请求量 QPS、TP99、TP999 环比波动 > 50%
错误率 HTTP 5xx、业务异常 错误率 > 1%
依赖服务 下游RPC RT、错误率 下游RT > 500ms
数据库 连接池使用率、慢查询 慢查询 > 5次/分钟
缓存 Redis命中率、连接数 命中率 < 80%
yaml
服务接口告警规则
-
alert: HighErrorRate
expr: (sum(rate(http_server_requests_seconds_count{status=~"5..."}2m)) / sum(rate(http_server_requests_seconds_count2m))) * 100 > 1
for: 2m
labels:
severity: critical
annotations:
summary: "接口错误率过高"
description: "服务 {{ labels.application }} 接口错误率 {{ value }}%"
-
alert: HighResponseTime
expr: histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket5m)) by (le, application)) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "接口响应时间过长"
description: "服务 {{ labels.application }} TP99响应时间 {{ value }}秒"
5.4 业务指标层(GMV/订单/用户)
这是最贴近业务价值的监控,也是CTO/老板最关心的。
指标分类 具体指标 告警阈值参考
交易 实时GMV、订单量、支付成功率 同比波动 > 30%
用户 活跃用户数、新注册数、转化率 新注册数骤降 > 50%
商品 商品浏览量、加购率、库存预警 库存 < 安全水位
营销 优惠券领取/使用率、活动参与 优惠券使用率异常高(薅羊毛)
yaml
业务指标告警规则
-
alert: GMVAnomaly
expr: sum(increase(order_gmv_total30m)) < sum(increase(order_gmv_total30m offset 1d)) * 0.7
for: 30m
labels:
severity: warning
annotations:
summary: "GMV异常下降"
description: "最近30分钟GMV较昨日同期下降超过30%"
-
alert: OrderPaymentRateDrop
expr: (sum(rate(order_created_total10m)) / sum(rate(order_paid_total10m))) > 3
for: 10m
labels:
severity: critical
annotations:
summary: "支付转化率异常下降"
description: "创建订单与支付订单比例超过3:1"
六、 告警规则设计
好的告警规则要做到"三不":
不错报:不要因为短时抖动就发告警(要有for持续时间)
不漏报:真正的问题必须告警出来(阈值要合理)
不疲劳:不要一天发几百条告警(要分级、分组、抑制)
6.1 告警严重程度分级
级别 标签 响应时间 通知渠道 典型场景
P0 critical 5分钟 电话 + 钉钉 + 短信 服务宕机、核心接口不可用
P1 warning 15分钟 钉钉 + 短信 错误率上升、RT超标
P2 info 1小时 钉钉(工作群) 磁盘使用率高、非核心功能异常
P3 none 不告警 仅记录 用于观察的指标,不触发告警
6.2 AlertManager配置
yaml
alertmanager.yml
route:
group_by: 'alertname', 'cluster'
group_wait: 10s # 同一组告警等待时间
group_interval: 10s # 同一组告警间隔
repeat_interval: 12h # 相同告警重复发送间隔
按严重程度路由
routes:
-
match:
severity: critical
receiver: p0-receiver
continue: true
-
match:
severity: warning
receiver: p1-receiver
-
match:
severity: info
receiver: p2-receiver
接收人配置
receivers:
-
name: p0-receiver
webhook_configs:
- url: 'https://your-domain/alert/critical'
send_resolved: true
dingtalk_configs: - url: 'https://oapi.dingtalk.com/robot/send?access_token=xxx'
message: |
【P0告警】{{ .GroupLabels.alertname }}
详情:{{ .CommonAnnotations.summary }}
时间:{{ .StartsAt }}
- url: 'https://your-domain/alert/critical'
-
name: p1-receiver
dingtalk_configs:
-
name: p2-receiver
email_configs:
- to: 'ops-team@company.com'
告警抑制(如果服务器宕机了,就不必再发送该服务器上的应用告警)
inhibit_rules:
- source_match:
severity: critical
target_match:
severity: warning
equal: 'instance'
6.3 告警收敛策略
告警风暴是所有运维团队的噩梦。以下是三种有效的收敛策略:
策略一:分组(Group By)
同一类告警聚合为一条发送,而不是每一条都发:
text
不加分组:10台机器磁盘告警 → 发10条消息
加分组后:10台机器磁盘告警 → 发1条消息,包含10台机器列表
策略二:抑制(Inhibit)
高阶告警触发时,抑制低阶告警:
text
场景:数据库宕机(P0)→ 自动抑制"接口超时"告警(P1)
理由:接口超时是数据库宕机的"症状",不是根因,不需要单独告警
策略三:静默(Silence)
已知问题、计划内维护期间,手动静默相关告警:
bash
在AlertManager UI中设置静默
或通过API创建
curl -X POST http://alertmanager:9093/api/v2/silences
-H "Content-Type: application/json"
-d '{
"matchers": {"name": "alertname", "value": "DiskFull"},
"startsAt": "2024-01-01T10:00:00Z",
"endsAt": "2024-01-01T12:00:00Z",
"createdBy": "ops-team",
"comment": "计划内磁盘扩容维护"
}'
七、 日志与链路追踪
7.1 日志体系(ELK/Loki)
日志是定位问题的"显微镜"。我们的日志架构:
text
应用日志(JSON格式)
↓
Filebeat(轻量日志采集器)
↓
Logstash / Loki(日志解析/聚合)
↓
Elasticsearch / 对象存储
↓
Kibana / Grafana(日志检索/可视化)
关键实践:
java
// 1. 统一日志格式(JSON结构化日志)
// logback-spring.xml
// 2. 关键字段必须包含
// { "timestamp": "2024-01-01T10:00:00Z",
// "level": "ERROR",
// "traceId": "abc-123-def",
// "service": "order-service",
// "tenantId": "tenant-001",
// "message": "数据库连接超时",
// "stackTrace": "..." }
// 3. 日志分级存储
// - ERROR/WARN → Elasticsearch(7天)
// - INFO → 对象存储(30天)
// - DEBUG → 本地文件(3天,仅开发环境)
7.2 链路追踪(SkyWalking)
分布式系统的核心痛点:一个请求经过4个服务,出问题不知道日志在哪。
java
// SkyWalking接入(通过Java Agent无侵入)
// JVM启动参数
-javaagent:/path/to/skywalking-agent.jar
-Dskywalking.agent.service_name=order-service
-Dskywalking.collector.backend_service=oap-server:11800
// 自动生成TraceId并传递到所有下游服务
// 所有日志自动包含traceId字段
TraceId + 日志 + Metrics 的关联查询:
text
在Grafana中,通过TraceId关联查询
- 从Metrics发现某接口RT异常
- 从该接口的日志中提取TraceId
- 在SkyWalking中查询该TraceId的完整调用链
- 定位到具体是哪个服务的哪个方法出了问题
八、 踩坑实录
坑1:告警阈值设置不合理
现象:CPU告警阈值设为95%,结果系统在CPU 90%时就已经响应缓慢,但告警一直没有触发。
教训:告警阈值不能只看"系统崩溃时的值",要看"用户体验开始下降时的值"。
解决方案:
结合用户体验数据(如APDEX指数)来校准阈值
阈值设置要留出缓冲空间(不要等到系统快崩溃了才告警)
坑2:告警发送频率过高
现象:有一次数据库连接池告警,每5分钟发一次,持续了4个小时,钉钉群里被刷了48条相同告警。
教训:重复告警会导致"狼来了"效应,团队成员逐渐对告警麻木。
解决方案:
AlertManager中设置 repeat_interval: 12h
同一告警在持续期间只发一次,状态变化时再发
坑3:Prometheus数据持久化问题
现象:Prometheus容器重启后,历史数据全部丢失,所有监控图表变成"断点"。
教训:Prometheus默认存储是容器内的临时存储,容器重启即清空。
解决方案:
使用持久化存储(PV/PVC)
关键指标配置远程存储(Thanos/Cortex)实现长期存储
调整 --storage.tsdb.retention.time=15d 保留最近15天数据
坑4:监控与业务日志割裂
现象:监控发现接口错误率上升,但去查日志时发现日志太多了,不知道从哪开始查。
教训:Metrics、Logging、Tracing三个体系必须打通,否则排查问题效率极低。
解决方案:
所有日志携带 traceId
Grafana中创建"统一查询视图",在一个面板中同时展示Metrics曲线和对应的日志
引入Loki(Grafana Lab出品的日志聚合系统),与Grafana原生集成
九、 最终成果
经过三轮迭代,监控体系最终覆盖了以下能力:
能力层 覆盖范围 说明
基础设施监控 50+台服务器 CPU/内存/磁盘/网络全覆盖
应用监控 15个微服务 JVM、接口、依赖服务全链路
业务监控 核心业务指标 GMV、订单、用户、转化率
日志采集 全部服务 结构化JSON日志,统一采集
链路追踪 核心链路 覆盖80%的核心接口
告警规则 80+条 P0级15条、P1级35条、P2级30条
告警准确率变化:
阶段 平均日告警数 P0级误报率 MTTR(平均修复时间)
无体系(初期) ~200条 80%+ 45分钟
基础体系 ~50条 40% 25分钟
成熟体系(当前) ~15条 <5% 8分钟
十、 总结
监控告警体系的建设需要关注三个核心维度:
维度 核心要点 常见错误
指标 分层设计(基础设施→应用→业务),每个层级关注不同的指标 只关注技术指标,忽视业务指标
告警 分级(P0/P1/P2)、分组、抑制、静默四件套 阈值不合理、告警疲劳
响应 告警→确认→定位→修复→复盘,形成闭环 只有告警没有后续动作
三条核心原则:
分层设计,从下到上:先搭好基础设施监控,再逐步向上覆盖应用和业务。没有地基的监控体系是空中楼阁。
告警要"少而精":每天发100条告警=没有告警。好的告警体系让值班工程师每天只需关注3-5条真正重要的告警。
可观测性不是一次性工程:随着系统演进,监控指标、告警阈值都需要持续调整。定期复盘告警记录,优化规则。
文末思考:
很多人觉得搭建监控告警是"运维的事",但实际上一线开发工程师才是监控的最大受益者------新版本发布后,第一个发现问题的往往是监控,而不是用户投诉。强烈建议开发团队在项目初期就同步建设监控能力,越早投入,回报越大。