为什么你的Prometheus监控"一切正常",用户却说系统崩了?
说个真事儿。上个月凌晨3点,我被一连串Prometheus告警吵醒。睁开眼一看,手机屏幕全是红:
api_response_time_p99 > 200ms
db_connection_pool_usage > 85%
error_rate_5m > threshold
我赶紧爬起来打开Grafana大盘,结果看到的画面让我一脸懵逼:
✅ CPU: 45%
✅ Memory: 62%
✅ Network: 正常
✅ Disk I/O: 正常
✅ 所有Pod都是Running状态
就这?那你告个毛线的警?
但半小时后,客服群炸了。大量用户投诉无法登录,有些甚至说"你们是不是服务器被黑了"。我当时脑子里就一个念头:我的监控明明说一切正常啊,到底哪里出问题了?
折腾到早上6点才查出来:某个第三方OAuth服务商的API响应从平时的50ms降到了3秒,导致我们的登录流程卡死,进而引发了一系列连锁反应。
最讽刺的是什么?我们的Prometheus里根本没有监控这个第三方依赖。因为它以前从来没出过问题,所以压根没想过要加监控。
这事儿让我开始重新思考一个问题:我们花了那么多时间搞监控,到底在监控什么?
干了这么多年监控,我发现大部分人对Prometheus的理解都有个根本性的误区——以为监控能发现所有问题。
实际上呢?监控只能发现你预先知道会发生的问题。
为什么这么说?咱们看看Prometheus的工作原理:
你定义一个metric:http_requests_total
你写个PromQL规则:rate(http_requests_total{status="500"}[5m]) > 0.01
然后配置Alertmanager:触发就发钉钉/短信
看出来了吗?这套流程的前提是你得提前知道要监控什么,阈值该设多少,什么情况算异常。
这对成熟系统来说没问题。比如:
磁盘快满了 → 这是"已知故障"
内存泄漏 → 这是"经典问题"
MySQL主从延迟 → 这是"教科书案例"
但真实世界的故障呢?70%都是你从来没想到过的组合:
某个微服务的重试逻辑有bug,在特定并发下会触发雪崩
Kubernetes调度器把所有新Pod调度到同一个节点,导致热点
某个Java服务的GC暂停时间从5ms突然变成500ms,但还没到你设的阈值
某个定时任务的cron表达式写错了,从每天一次变成每秒一次
这些问题的"症状"可能在Prometheus里显示为"正常范围内的波动"。等你发现的时候,用户已经跑路了。就像下棋,监控只能防住你复盘过的棋路,但对手总会下出新的变化。
陷阱1:维度选择的盲区
大家都知道监控要看"四个黄金信号":延迟、流量、错误、饱和度。这套方法论没毛病,但问题在于——它假设系统行为可以用这四个维度完整描述。
实际上呢?有些故障根本不在这些维度上:
案例A - 数据正确性问题
去年遇到过一次,所有监控指标都正常,但用户看到的账户余额是错的。后来查出来是某个字段更新的SQL写错了,本该更新A表结果更新了B表。
P99延迟?正常 ✅
错误率?正常 ✅
QPS?正常 ✅
但数据已经脏了,Prometheus根本看不到。
案例B - 长尾效应
有次压测,看P50和P99都很漂亮。结果上线后收到投诉,说某些VIP用户总是超时。后来才发现是P99.9的请求全挂了,而那0.1%刚好是付费用户。
你的Prometheus可能只采集到P99,P99.9根本没算。结果就是:大盘数据漂亮,核心用户骂娘。
陷阱2:Cardinality爆炸的反噬
说到Prometheus,就不得不提label cardinality这个大坑。
刚开始用Prometheus的时候,我也犯过这个错误:觉得标签越详细越好,于是给metric加了一堆label:
http_requests_total{
user_id="12345", // 100万用户
endpoint="/api/xxx", // 500个接口
status="200", // 10种状态码
region="cn-beijing", // 20个地区
device_type="iOS" // 50种设备
}算一下:100万 × 500 × 10 × 20 × 50 = 50亿时间序列。
结果是什么?Prometheus直接OOM,监控系统成了故障源头。
后来我明白了一个道理:监控不是越详细越好,而是要抓住核心矛盾。就像拍照,如果每张照片都是100GB的原图,你的硬盘再大也不够用,而且根本看不过来。
现在我的原则是:核心告警不超过7个,标签不超过5个维度,时间序列控制在100万以内。宁可少而精,不要多而乱。
陷阱3:相关性不等于因果性
Prometheus最擅长的是发现相关性,但最不擅长的是找因果关系。
举个例子,你看到Grafana上"数据库CPU飙升"和"API响应变慢"同时发生,第一反应肯定是:"数据库有问题!"
但真实原因可能是:
某个测试同学误连了生产库,跑了个全表扫描
某个定时任务配置错了,从每天1次变成每秒1次
Redis缓存突然失效,查询全打到DB
某个爬虫在暴力爬取你的API
Prometheus只能告诉你"果"(CPU高了),但无法告诉你"因"(为什么高)。
就像看电影的截图:你看到主角第1分钟在笑、第5分钟在哭、第10分钟倒地了,但你不知道中间发生了什么。故障调试需要的不是截图,而是完整的录像——这就是为什么我们需要分布式追踪。
讲真,这两年"可观测性"这个词被各种厂商吹得神乎其神,搞得像是买了个Observability平台就能解决所有问题似的。
但我理解的可观测性,本质上就一句话:承认监控有局限,所以要保留足够的上下文信息,让你在出问题时能快速调试。
对比一下:
| 监控思维 | 可观测性思维 |
| "这个指标要不要加监控?" | "出问题时我能快速定位吗?" |
| 预定义指标+阈值 | 保留高精度原始数据 |
| 聚合数据(P99、平均值) | 原始事件(traces、logs) |
| 被动响应告警 | 主动探索分析 |
具体怎么做?我总结了几个实战经验:
1. 给每个请求一个TraceID
这个不用多说了,OpenTelemetry已经是标准了。关键是要端到端贯穿:从浏览器、到网关、到微服务、到数据库,全程带着TraceID。
出问题的时候,拿着TraceID在Jaeger里一搜,整条链路的耗时、依赖关系、错误点一目了然。
2. 监控要"极简主义"
SRE那本书里有句话我特别认同:核心告警不要超过5-7个。
为什么?因为告警太多等于没告警。你的团队会麻木,真正的故障反而被淹没了。
我现在的原则是:
每个告警必须对应一个Runbook(明确的处理流程)
季度内查看不到1次的指标,直接删掉
能用日志解决的,就不要加监控
3. 别忘了监控"变更"
这个经常被忽略。我们花大力气监控CPU、内存、QPS,但往往忘了最危险的因素:变更。
统计一下你们过去一年的故障,至少70%都是变更引起的:
代码部署
配置修改
基础设施升级
依赖版本变更
所以我现在的做法是:把所有变更事件都打到Prometheus的Annotations里,在Grafana上显示为垂直线。出问题的时候,一眼就能看出"是不是刚刚部署了什么"。
写这篇文章的时候,我翻了翻去年的oncall记录。印象最深的不是那些半夜被吵醒的时刻,而是那些监控系统什么都没发现,用户却已经炸了的瞬间。
监控系统就像汽车的仪表盘:它能告诉你油量、速度、转速,但它无法告诉你"为什么发动机异响",更无法告诉你"前方会不会堵车"。
监控只能发现已知问题——这不是监控的缺陷,而是复杂系统的本质。
所以我的建议是:
✅ 承认监控有局限
✅ 为系统设计"可调试性"(logs、traces、metrics三件套)
✅ 保留出问题时需要的"证据"
✅ 培养团队的调试直觉和RCA文化
最后借用Bryan Cantrill的一句话:"Debugging is iterative hypothesis testing." 监控给你假设的起点,但真正找到根因,靠的还是你对系统的理解。



