Max
搜索
返回故事会

APM 如何重塑你的系统认知

116 分钟阅读1Max ZhangDevOps
APM可观测性OpenTelemetryeBPFDevOps

凌晨三点,手机亮了。

不是女朋友的消息,是 PagerDuty。

你眯着眼看了一眼告警内容:订单接口 P99 延迟超过 5 秒。五分钟前还是好好的,现在突然飙了。群里已经有同事在艾特你了:"订单下不动了,用户打电话来问。"

你翻了个身,打开电脑,开始一顿操作:

top 看看——CPU 40%,不高啊。 free -m——内存还有 8 个 G。 df -h——磁盘也没满。 netstat——连接数正常。

看起来一切正常。但用户就是下不了单。

你开始凭经验猜:是不是数据库慢了?Redis 挂了?第三方支付接口超时了?网络抖了?你一个一个试,时间一分一秒过去。等你终于等到慢查询日志刷新出来,发现一条连表 SQL 扫了全表——已经过去了二十分钟。

二十分钟。在你翻日志的这段时间里,每一秒都有用户带着火气关掉你的 App。那感觉就像你明明知道有病人在喊疼,但你的听诊器根本不够长。

你有没有想过一个问题:为什么你明明有监控,出了问题还像个瞎子?

因为你在用一套"盲人摸象"式的工具。top 告诉你 CPU 用了多少,但它不告诉你 CPU 时间花在哪个函数上。netstat 告诉你有多少连接,但它不告诉你这些连接在做什么。df 告诉你磁盘还剩多少,但它不告诉你磁盘 I/O 的真实延迟有没有在恶化。

你拿到的是散落一地的珍珠。

但你不知道它们应该串成什么样的项链。

APM 要解决的,就是这个"看不全"的问题。它不是什么银弹,也不是什么商学院词汇。它就是专门来解决你凌晨三点那种"卧槽,到底哪里出问题了"的绝望感的一套东西。

你监控的到底是什么?

先说一个核心认知:监控和可观测性不是一回事。

这件事的重要性怎么强调都不过分——因为90%的团队把这两个概念混着用,结果搭建出来的东西两边不靠。

监控是"我知道你可能会出什么问题,所以我盯着这几个指标"。CPU 超过 90% 就报警,内存快满了就报警,支付接口五分钟没响应就报警。你预设了一个故障模式,然后盯着这个模式看。这就像你去看病,医生说"我怀疑你是感冒,所以给你量体温、看喉咙、摸额头"。如果体温正常、喉咙不红、额头不烫,医生就放你走了——哪怕你其实是肠胃炎。那三个检查根本覆盖不到肚子。

可观测性是"我不知道你会出什么鬼问题,我不预判,我只需要你全身都有传感器"。任何系统行为——不管是正常的还是异常的——都会在某个维度上留下痕迹。可观测性就是让这些痕迹可以被探索、被关联、被追问。

监控是拿着清单去医院体检。可观测性是躺在核磁共振仪里——任何异常都会被拍下来,不管你有没有提前写在检查单上。

监控回答的问题是"这个是不是坏了",可观测性回答的是"我的系统正在发生什么"。

APM 的最终目标是让系统可被观测,而不仅仅是"被监控"。这两者的差距,决定了你在凌晨三点是二十分钟解决问题,还是两分钟。

我们来拆开看,APM 到底在看什么。

服务:一个请求的完整一生

先问你一个问题:你的服务现在是健康的吗?

你可能会说:当然健康啊,进程没挂,端口在监听。

那咱往深了问一句:

  • 过去五分钟里,你处理了多少请求?有多少成功,多少失败?
  • 哪些接口正在变慢?趋势是向上还是向下?
  • 慢的那些请求,时间花在什么地方了?是在等 I/O,还是在做计算?
  • 错误集中在哪些接口?是偶发的还是持续性的?
  • 请求量有没有突然的尖峰或断崖?尖峰意味着上游在疯狂重试,断崖意味着上游可能挂了。

如果你最多只能回答第一题——"处理了多少请求"——那你连"健康"这个词的定义都没搞对。健康的反义词不是"挂了",是你的系统正在用一种你不知道的方式悄悄变慢。

我以前见过一个项目。订单接口的 P50 响应时间是 200 毫秒——看起来漂亮得很。但 P99 是 12 秒。两千个请求里,有一个用户要傻等十二秒。

你想想那个用户是什么体验。

他不是程序员,不知道 200 毫秒和 12 秒的区别在技术层面意味着什么。他只知道你的 App 卡得跟屎一样。他可能在 5 秒的时候就已经退出去用竞品了,并且这辈子不会再打开第二次。然后你和老板汇报说:"我们接口平均响应时间 298 毫秒,性能很好。"

健康不是一个 boolean 值。它是一个多维的、分布式的、随时间变化的东西。有些维度是你自己不会想到要去看的——直到有一天出事了才发现"他妈的我怎么从来没关注过这个"。

那不用 APM,靠打日志行不行?

打日志当然可以。你可以自己在每个接口的入口和出口各打一条日志,记录时间戳,然后自己算耗时。如果接口里还调了其他服务,你也手动记一下。如果有一天你想看看所有接口的调用量排行,你写个脚本去解析日志。如果你想看 P99 延迟,你写个更复杂的脚本。

可以。理论上什么都能做。

但你迟早会发现,你在用错误的方式解决一个已经被解决了的问题。就像你用石头砸钉子——确实能砸进去,但你砸得手疼。更糟的是,当系统规模上来以后——一百个接口、二十个服务、每天几千万条日志——你那个日志解析脚本会成为新的性能瓶颈。

APM 用统一的探针或 SDK 自动拦截每个请求。包括进来的 HTTP 请求、出去的下游调用、数据库查询、缓存读写——全部自动记录开始时间和结束时间,自动计算耗时,自动聚合统计。你一行代码都不用写。

读到这儿,你可能在想:这不就是一个加强版的日志分析工具吗?

不是。APM 和日志分析的本质区别在于,APM 采集的是结构化时序指标,不是原始文本。日志是你事后去翻来找的一堆字符串。APM 是在请求发生的同一瞬间就把时间、成功/失败状态、调用关系写进了时序数据库。日志是"事后回想",APM 是"实时感知"。

这是质变。就像你知道你家的电表在转和你有一台实时功率监测仪的区别。电表只告诉你"这个月用了多少电",功率仪告诉你"此刻空调在烧 3.5kW,热水器刚才刚启动,冰箱在正常怠速"。你能立刻判断哪个电器有问题。

错误:你知道"出错了"这三个字有多重要、又有多废话吗

监控里最容易被误解的概念,大概就是"错误"这两个字。

我给你一个场景:你在看监控大盘,发现过去一小时有三千次错误。你是不是马上紧张了?

先别慌。回答我:这三千次都是什么错?

如果其中两千五百次是"用户余额不足"——这是正常业务逻辑,不需要半夜爬起来修。这就像你开了一家商店,顾客进来说"我想买这个但钱不够"——你说"好的欢迎下次光临"就好了,不用火急火燎去修收银机。剩下一百次是"数据库连接池耗尽"——你才需要立刻处理。剩下一百次是"第三方支付接口返回500"——这个要看你跟支付提供商的 SLA 怎么约定的。

所以错误监控的第一课:不是所有错误都需要报警。

你得给错误分个类。好的分类至少分四个维度:

第一个维度,错误来源。是自己代码的 bug,还是外部依赖挂了?如果一个接口调了三个下游服务——用户服务、库存服务、支付服务——任何一个都可能出错。你不分来源的话,三千个错误砸下来,你根本不知道是该修自己代码还是该打电话骂第三方。

第二个维度,严重程度。一个用户的数据查不出来,和一个按钮的边框颜色渲染错了,能一样吗?前者是功能阻断,用户因此无法完成核心操作。后者是视觉效果瑕疵,不影响业务。如果你把所有错误都放在同一个优先级里,你就会被噪音淹没,忽略真正的火情。

第三个维度,影响范围。是全网所有用户都挂了,还是某个特定地区的用户受影响?是全部接口不可用,还是就一个冷门功能报错?你会发现,很多看起来吓人的错误数字,其实就是某个省的某个运营商出的问题,其他用户根本不受影响。

第四个维度,时间特征。是一瞬间的错误尖刺然后恢复了,还是在持续恶化?尖刺往往是部署重启导致的短暂不可用,或者网络瞬间抖动。持续恶化意味着真有东西坏了,而且可能越来越坏。

这里面有一个微妙但极其重要的地方:错误不是孤立的,它有上下文。

你记录一个错误时,光有 "SQLException: connection timeout" 是不够的。卧槽,这条信息约等于告诉你"车坏了"但不告诉你是哪个零件。你得知道是谁在执行这个 SQL、传了什么参数、这个 SQL 之前执行了多少次、平均耗时多少、这条链路的前后几个 Span 是什么状态、是第一次超时就超了还是第十次重试才超的。

举个能让你记住的例子。

你在日志里看到一条 NullPointerException,堆栈指向某个 Service 的某一行。你拿着这个去查代码,发现确实可能为空。你加了个 if (x != null),提交,部署,以为解决了。

但如果此时你有了 APM,你会打开这个错误的 Trace,发现一个你永远不会从日志里看出来的东西:出错的请求里,用户在调用这个接口之前,先调了一个不完整的注册流程——user 对象少了一个字段,导致后续处理的时候取到 null。不是代码写得烂,是整个业务流程的边界条件没考虑周全。加空判断是堵了一个症状,但根源是上游数据不完整。

没有上下文,你看到的是一个 bug,修完以为没事了。有上下文,你看到的是业务流程设计缺陷——不修这个缺陷,同样的模式会在另一个接口的另一个字段上再次出现。

这比简单加一行空判断有价值十倍。

日志:别让它的价值烂在文件里

我得先说句实话:90% 的团队日志策略是一坨屎。

我知道这话不好听。但你仔细想想你加入过的每一个团队,是不是都是一个模式——平时没人管日志格式,代码审核时也没人看日志行,大家心情不好的时候往代码里乱插 console.log。结果日志文件长得飞快,但真到查问题的时候发现要么没记关键字段,要么格式混乱导致 grep 都难用,要么日志里全是 "entering method X" 和 "leaving method X" 这种毫无信息量的废话。

你辛辛苦苦塞进去的几百 GB 日志,到了凌晨三点没有一条能帮你定位问题。这种感觉就像是你在自家后院埋了一百个箱子,但你没画地图,所以每一个箱子都他妈的找不到。

一个设计良好的日志体系,得满足三个条件:

统一结构化。 每条日志都应该是结构化数据,至少包含这些字段:时间戳(精确到毫秒——一秒钟可以发生很多事)、Trace ID(没有这个你根本串不起来分布式链路)、Span ID、日志级别、服务名、用户标识、请求路径、关键业务参数。JSON 格式都可以,关键是别搞自由文本——你在凌晨三点没心情去解析一段散文,你需要能被精确搜索的结构化字段。

日志分级。 这五个级别不是用来写好看的——DEBUG、INFO、WARN、ERROR、FATAL——它们各自对应一种行为策略。DEBUG 只在本地开发环境打开,上了生产就是一个黑洞,谁也别看。INFO 记录关键业务流程节点——用户下单、支付回调、退款发起——这些事情发生的时候你希望能追溯。WARN 是"虽然不影响主流程,但你应该看一眼"——比如 Redis 重试了三次才连上,或者某个缓存 key 的过期时间设得太短导致频繁重算。ERROR 需要有人处理,不是所有 ERROR 都要半夜报警,但至少要进 TODO 列表而不是被忽略。FATAL 是服务马上就要死了,你必须立刻被叫醒。

生命周期管理。 不是所有日志都需要存一辈子。DEBUG 日志可能几小时后就删了;INFO 日志保留七天——够你回溯近期问题;ERROR 日志留三十天做趋势分析;和合规相关的日志(支付、用户操作审计)可能需要多存一段时间。关键是你得主动想这件事,而不是等磁盘满了再加一块硬盘。磁盘便宜,但无意义的日志会让你的查询变慢、让索引膨胀、让告警系统迟钝。

在技术栈选择上,ELK 曾经是标准答案。我认为 ELK 就像一个瑞士军刀——什么都能做,但干什么都差一点。维护 ES 集群是出了名的疼。内存要求高,写性能对硬件敏感,集群配置够一个运维工程师研究一整年。

现在更多人开始用 Loki。Loki 的核心理念是"日志索引不该花那么多钱"。它只对元数据建索引——service name、pod name、trace ID——对日志正文不建倒排,查询时靠并行扫描完事。磁盘占用比 ES 小一个数量级,维护成本也低一个数量级。

但老实讲,技术栈只是载体。真正的难题是,你得想清楚每个模块里什么是"该记的"、什么是噪音。这需要你对业务逻辑有足够深的理解,而这不是任何工具能替你做的决定。

依赖:80% 的事故都和你没关系

这是整个 APM 里面最容易被忽视、也最值钱的一块。

你自己写的那部分代码,因为是你写的,你比较熟悉,也能控制,通常出不了大问题。你上线前测过了吧?代码审核时别人看过吧?单元测试覆盖了吧?真正让你措手不及的全在依赖上——数据库、缓存、消息队列、第三方 API、其他团队的微服务。这些东西不在你的代码仓库里,不在你的 CI 流程里,不对你的发布窗口负责。但你的服务离了它们就停摆。

有一组真实的数据:在生产环境中,超过 80% 的非预期性能问题是由外部依赖引起的。不是你自己代码的问题,是某个你控制不了的家伙出了问题。可能是支付 API 工作日午间流量大导致响应慢了,可能是 Redis 集群在做主从切换时短暂不可用,可能是你的云服务商在某个可用区出了网络故障。

所以依赖监控的价值不是"又多了一堆指标"。我用一句话说清楚:它帮你区分了"我的问题"和"别人的问题"。

凌晨三点告警响了。你的第一反应不应该是"卧槽,我这代码哪儿写错了"——你应该先确认一件事:是我的东西变了,还是外部的变了?如果你的服务代码没上线、配置没改、流量没异常,那 90% 的可能性是某个依赖出了问题。依赖监控能在几秒内帮你确认这一点——不用翻日志、不用查数据库、不用打电话叫醒同事问"你那边正常吗"。

怎么监控依赖?

不是 ping 一下看看通不通。那只是告诉你"依赖活着"——但活着的依赖可以慢、可以有间歇性错误、可以部分不可用。你得建立基线。你得知道正常情况下,数据库查询 P99 是 50 毫秒,Redis 操作 P99 是 2 毫秒,支付接口 P99 是 800 毫秒。只有当你对正常态有清晰的认识,异常来临时你才能瞬间识别——"卧槽,支付接口平时 P99 是 800,现在变成 4 秒了"。

这个认知不是靠直觉建立的。

基线的建立方式有两种:一种是静态阈值——你拍脑袋定的,"数据库查询超过 200 毫秒就报警"。问题是你怎么知道 200 是对的?工作日下午的流量和午间高峰期的能一样吗?搞静态阈值的结果就是要么误报把你烦死,要么漏报在你睡着的时候悄悄出问题。另一种是动态基线——系统根据历史数据自动学习正常模式。它知道昨晚的 CPU 也大概在这个范围,所以不会因为"跟昨天同时段比高了 5%"就报警。但它也知道这个接口的延迟在过去三十分钟里以每秒 5 毫秒的速度稳步上升——手动的阈值绝对发现不了这种"渐进式退化"。

依赖监控里一个很实用的技巧是调用拓扑图。它把你的服务和所有下游依赖画成一张有向图,每条线旁边标上调用量、错误率、平均延迟。一旦某个依赖开始泛红——延迟变高、错误变多——你的眼睛会在图上一秒定位到问题。

这就是 APM 的核心价值:把一个"到底哪里出了问题"的开放性问题,变成"这个依赖怎么了"的封闭性问题。 你的排查半径从"整个系统都有可能"收敛到"这一个点"。

这节省的不是几分钟。这节省的是你在凌晨的理智和尊严。第二天上班你还能正常跟同事沟通,而不是像丧尸一样靠咖啡续命。


分布式追踪:把散落的珍珠串起来

那个让你失去理智的周五下午

说一个真实经历。不是编的。

有一个周五下午,用户反馈下单很慢。我打开监控大盘,看到订单服务的响应时间确实涨了——从平时的 200 毫秒涨到了 3 秒。增幅是 15 倍,这不是"有点慢",这是"你已经不行了"。

我就开始查。第一步很正常——先看订单服务自己的 CPU 和内存。正常,没啥波动。第二步看它后面的 MySQL——慢查询日志里没有新东西。第三步看 Redis——命中率也没波动。第四步查它调用的上下游:上游是用户服务,下游是支付服务和库存服务。我一个一个看,用户服务挺快,支付服务正常,库存服务的响应时间显示也正常。

然后我卡住了。

每一个节点——单个看——都表现得非常正常。但用户就是收到一个超长的响应。所有节点的指标都在说"我没问题",但这堆"没问题的节点"组合在一起,就是慢。

你猜怎么着?

四个小时后我才找到真凶——问题出在用户服务和订单服务之间的那台交换机。丢包率到了 15%,导致 TCP 重传机制疯狂触发。每次重传多花几百毫秒,而用户服务和订单服务之间的请求——本来只需要 1 次往返,因为丢包,硬生生变成了 5 次。这些额外的延迟在任何一个节点的单机指标上都是"透明的"——用户服务说"我很快响应了"(它发的包确实很快出去了),订单服务说"我收到请求的耗时正常"(它记录的是从收到完整包开始的耗时)。而中间的 4 次重传,在谁的单机指标里都看不到。

如果我当时有分布式追踪,五分钟就够了。

为什么?因为分布式追踪不会给你看"每个节点的平均情况"。它给你看的是一份单次请求的完整时间账本

Trace、Span 和那根线

分布式追踪的核心概念就两个:Trace 和 Span。没有第三个。

Trace 是一次完整的用户请求从入口到结束的全部路径。用户点击"下单"按钮,这个请求穿过 API 网关、鉴权服务、订单服务、库存服务、支付服务——这条完整的调用链就是一个 Trace。一个 Trace 可以横跨十几个微服务、几十个数据库查询、上百个下游调用。

Span 是这条路径上的一个步骤。网关处理是一个 Span,订单服务处理是一个 Span,查询数据库是一个 Span,调用支付接口也是一个 Span。Span 是嵌套的——网关这个 Span 里面包含了订单服务的 Span,订单服务的 Span 里面包含了数据库查询的 Span。这个嵌套关系就是调用链的结构。

每个 Span 都记录三样东西:它的开始时间、它的结束时间、以及它的"父亲"是谁。开始和结束给了你耗时,父子关系给了你调用拓扑。这三个字段加起来,一个 Trace 就变成了一张精确到毫秒的调用时间线。

所有 Span 共享一个全局唯一的 Trace ID。这个 ID 是整个谜题的那根线。你在网关生成了一个 Trace ID(通常是一个 16 字节的随机数),随着 HTTP Header 传到下游,下游再传给它的下游,一层一层地传下去,从头传到尾。

所以当你在 APM 里搜索一个慢请求时,你点开那个 Trace,看到的不是一堆孤立的数字和表格。你看到的是这样一张时间瀑布图:

Trace 3fa2b1c
  网关接收请求.................... |5ms
    ├─ 用户认证........................|12ms
    └─ 订单处理........................|2850ms
        ├─ 校验参数....................|3ms
        ├─ 查询库存 (Redis)............|8ms
        ├─ 写入订单 (MySQL)............|45ms
        └─ 调用支付 (外部 API).........|2700ms ← 卧槽,这个

整个请求耗时 2.85 秒。其中 2.7 秒在等支付接口返回。剩下 150 毫秒是你自己的逻辑。

你不费吹灰之力就知道了:不是你的问题,是支付提供商今天下午在划水。你可以截图发给支付提供商的客服,然后回去睡觉。过程总用时:两分钟。

这就是 Trace 的力量——它把一个"分布式系统中到底谁慢了"的问题,变成了一张可以用肉眼直接扫描的时间表。

OpenTelemetry:让监控说同一种语言

讲分布式追踪一定得讲 OpenTelemetry(简称 OTel),因为它正在从根本上改变这个行业。

以前是个什么青黄不接的情况?你的服务 A 用 Jaeger 的 SDK 埋点。服务 B 用 Zipkin 的 SDK 埋点——可能是另一个团队负责的,他们有自己的技术选型。服务 C 用了商业 APM 产品的专有 Agent。三套格式、三套协议、三种不同的 Header 传播方式。你在 Grafana 里点一个 Trace ID,有一半的概率数据是断的——因为中间的某个服务不理解上游传下来的 Trace ID 格式,把它丢了。

想统一?对不起,全部重新接入。这相当于你们公司有三个部门,一个说中文,一个说英文,一个说法语,然后你要求所有人开会时必须听清每一个字。不现实。

OTel 站出来做了一件看起来简单但改变游戏规则的事:定义一套统一标准。

你不用关心后端是谁。你按 OTel 的标准暴露指标、记录日志、生成 Span。这些数据通过 OTel Collector——一个独立的中间件——转发到你想要的后端:Prometheus、Grafana、Jaeger、Datadog、随便你。

想换后端?改 Collector 的配置文件就行了。一行代码都不用动。

这从根本上解决了厂商绑定问题。OTel 是 CNCF 旗下仅次于 Kubernetes 的第二大开源项目——不是因为大家觉得"标准很酷",而是因为大家都被厂商绑定折磨过。OTel 不是后端存储,它是一个"你管采集,我管格式"的中间层。

OTel 的技术架构理解起来也不复杂。

API 层定义你在代码里调的那几个接口——获取 Tracer、创建 Span、给 Span 加属性、结束 Span。你可以手动调用这些接口做自定义埋点,也可以什么都不写,让自动探针来。

SDK 层做了具体实现——Span 如何批量化发送以避免性能损耗,采样策略如何配置以避免海量数据压垮存储,Context 如何在协程或线程间安全传递。

Collector 是一个独立进程,通常以 DaemonSet 或 Sidecar 的方式部署。它做的事很简单:接收来自各个服务的数据,做过滤(丢弃不感兴趣的)、聚合(合并重复数据)、脱敏(去掉敏感字段比如密码或身份证号)、采样(保留千分之一的 Trace)、然后转发到多个后端。

OTLP 协议是传输层的统一格式——全部数据用 ProtoBuf 序列化,比 JSON 快 3-5 倍,字节占用更少。这意味着你的 Collector 可以承受更高的吞吐量而不会成为瓶颈。

接入的简单程度可能超乎你的想象。在 Node.js 里,你甚至不需要手动创建一个 Span。OTel 的自动探针知道你在用 Express,它就自动给每个 HTTP 请求创建一个 Span。它知道你在用 mysql2,它就自动把每个数据库查询包成一个 Span。它知道你在用 redis 或者 ioredis,就自动给每个 Redis 操作包一个 Span。你只需要在启动时做一次初始化,剩下所有的事情——所有 Span 的创建、所有 Trace ID 的传播、所有 metrics 的聚合——OTel 自动替你做了。

读到这儿你可能会问:那 Trace ID 是怎么从上游传到下游的?

这是一个会被忽略但极其关键的问题。在 HTTP 里,Trace ID 通过 Header 传递。标准格式是 W3C 的 traceparent: 00-{trace-id}-{span-id}-{trace-flags}。上游在发 HTTP 请求的时候,OTel 拦截了这个请求,自动把当前的 Trace ID 塞到 Header 里。下游收到请求后,OTel 的 HTTP 拦截器读取 Header,发现已经有 Trace ID,就把自己后续生成的 Span 都挂在这个 Trace 下面,形成一个连续的追踪链。

gRPC、Kafka、RabbitMQ、SQS、Lambda——同理。每个传输协议都有对应的 Context Propagation 机制。你不需要手动传递——这是 OTel 帮你做的事。但你需要知道它在发生,因为如果你哪天遇到"Trace 断了"的问题,99% 的原因是某个环节的传播机制没配好。比如你的服务 A 使用了 HTTP 调用服务 B 但服务 B 用的是 gRPC 接收——传播协议不匹配,Trace 断在了 A 和 B 之间。


火焰图:别猜了,让代码告诉你它在干什么

从指标到代码,中间隔了一堵墙

前面讲的全是服务层面的监控——请求量、错误率、响应时间、Trac 调用链。这些告诉你"服务出了什么问题"。但有些问题,服务指标是看不出来的。

比如:CPU 占用 85%,你知道压力很大。但 CPU 到底在算什么东西?是你自己写的那个 for 循环写得跟屎一样,还是 json.Marshal 被调了太多遍?还是 Garbage Collector 在疯狂做回收?还是某个库的内部正则表达式每次都在重新编译?

你不知道。你只知道 CPU 在忙,但不知道它在为什么忙。就像你知道你家的水表转得快,但不知道是哪个水龙头没关。

这个时候你需要往下走一层,用 Profiling 来看代码。

什么是 Profiling?

Profiling 不是新的概念——C 程序员用 gprof 用了三十多年了。但在云原生时代,分布式系统的 Profiling 有了新的意义:你不仅想知道哪个函数慢,你还想知道这个慢函数被谁调用的、在什么请求场景下慢的、是因为并发引起的竞争还是单线程的效率问题。

Profiling 的采集过程是:你的程序跑了十分钟。Profiler 在这十分钟里每隔几毫秒就做一次采样——看看当前 CPU 在执行哪个函数的哪一行指令、当前内存堆里有哪些对象、当前有多少 goroutine 或线程在等待锁或等待 I/O。

十分钟后,你得到一份采样报告。

这份报告能告诉你三件事:

CPU 时间花在哪了。 你的程序在哪个函数——甚至哪一行代码——上消耗了最多的 CPU 周期。热路径一目了然。你可能会发现 60% 的 CPU 时间都花在了一个你根本没听说过的辅助函数上。

内存用在哪里了。 哪些代码路径分配了最多的内存?有没有只分配不释放的对象?有没有短生命周期的大对象分配——每次请求都创建一个 1MB 的缓冲区然后扔掉?

阻塞发生在哪。 你的服务有多少时间花在等待 I/O、等待锁、等待网络响应上?如果你的 QPS 上不去是因为所有请求都在排队等一个互斥锁,Profiling 会告诉你是哪把锁、哪个函数在持锁时间过长。

这与服务监控是不同层面的信息。服务监控告诉你"订单接口变慢了"。Profiling 告诉你变慢的原因是 for 循环里的字符串拼接太多,每次循环都产生新的临时字符串对象,导致 GC 疯狂回收。你改了一行代码——把 += 改成 strings.Builder——响应时间从 3 秒掉回 200 毫秒。

服务监控告诉你症状,Profiling 告诉你病因。

怎么"看"一份 Profiling 报告?火焰图

Profiling 采集到的原始数据没法直接看——太密、太细。一百万次采样,一次采样包含几十层调用栈——直接看数据的话你的眼睛会在第三秒放弃。

所以 Brendan Gregg(eBPF 领域的教父级人物,后面还会提到他)在 2011 年发明了火焰图。名字起得很形象——那些宽窄不一样的方块,叠在一起看起来确实像一团燃烧的火。

它的构图非常简单,但看懂之后,它就是你手里的性能分析神器。

横轴是时间占比。 不是绝对的 CPU 时间,而是比例。一个函数在横轴上占了 30% 的宽度,就意味着程序 30% 的 CPU 时间花在了这个函数及其子调用上。

纵轴是调用栈的深度。 最下面的是入口函数——main() 或者一个 HTTP handler。每往上一层,就是函数调用的一层深度。最顶端的叶子函数是实际耗时的"源头"——它们自己没再调别人,CPU 就是真正在跑它们的指令。

所以读火焰图只有一条核心规则:看谁宽。

哪个方块最宽,哪个就是最大时间消费者。宽的可能在底层(说明 I/O 或系统调用多),也可能在上层(说明业务代码有密集计算)。位置本身不告诉你原因,宽度告诉你优先级。

一个常见误区:把宽和高搞混。高——在 Y 轴高点——代表调用深度大,但不代表耗时多。有些递归函数因为设计模式需要层层传递,深度很深但每次调用都极快,在图上就是一条细长的"烟囱"——纵深大,宽度极窄。反过来,有些单层的系统调用可能占了 60% 的宽度——比如 GC 在做回收。这种信息在传统性能工具里是不可见的。

颜色没有实际含义。每一层调用栈随机分配一个暖色调,纯粹是为了让你在视觉上能区分"这是 A 分支"和"这是 B 分支"。别花时间去解读颜色——你不需要。

给你一个实战例子。你看一次火焰图,发现最宽的一块上写着 regexp.MatchString,宽度占了 40%。点进去看调用栈,发现是你自己在一个 for 循环里每次都重新编译正则表达式——经典的低级错误,新人和老手都犯过。把正则编译移到循环外,CPU 立刻降了 30%。

这就是火焰图的正确用法:不是帮你发现从来没想过的位置,而是让你直观地"看到"你知道可能有问题的地方到底有多严重,从而使你排出一个正确的优化优先级。 你可能知道那个正则可能有点贵,但你没意识到它贵到占了你整台机器 40% 的算力。

工具:你的 Profiling 武器库

有些工具值得你记住名字:

autocannon:Node.js 生态里最简洁的 HTTP 压测工具。一个命令打出去,立刻告诉你这个接口的 QPS、P99 延迟、连接错误率。轻量、快速、没有学习曲线。适合"我加完这个索引了,到底变快了多少"这种快速验证。

clinic:Node.js 的一体化自动诊断工具。你运行 clinic doctor -- node app.js,它自动采样、自动分析、自动生成一个带可视化建议的 HTML 报告——告诉你是 CPU 问题还是 Event Loop 问题还是 GC 问题。对于不想手写火焰图分析的开发者来说,这东西很解渴。

0x:只做一件事——生成火焰图。0x -P 'autocannon -c 10 -d 60 http://localhost:3000' node app.js——打六十秒压力,采样,然后打开生成的 HTML 看火焰图。

Go 生态有 pprofgo-torch。Java 生态有 Async ProfilerJProfilerArthas。Python 有 py-spyAustin。无论你用什么语言,原理都一样——采样、聚合、可视化。

但还有一步比工具选择更重要:定期做 Profiling。 别等到用户投诉了才第一次打开 Profiler。性能退化不是突然一天发生的——是每个版本一点点累积的。每个 release 跑一次基准 Profiling,看一眼火焰图,对比上次——哪个方块宽了,就往哪个方向深挖。


eBPF:不给代码动刀子的监控术

你在这头,内核在那头

所有的传统监控,不管你用什么工具——Prometheus、Datadog、Zabbix、自己写的 exporter——本质上都在"用户态"干活。你的监控代码跑在应用程序的进程里,通过系统调用来向内核索要数据。CPU 用多少了、内存分配多少了、TCP 连接状态怎样了——每一次查询都要问一次内核,每问一次就有一次用户态和内核态的上下文切换。

这个开销很小——单次。但如果你是每秒 100 次的指标采集,这个开销就不再是"可以忽略"的了。更恶心的是,你在高负载下试图诊断一个性能问题——你的诊断工具本身就在给已经喘不过气的系统增加负担。

还有一个更深层的问题:有些数据,用户态根本拿不到——至少拿不到精确的。

你想知道某个网卡在过去一秒丢了多少包?你得去读 /procsysfs,但那些文件展示的是累积计数,你没法精确到微秒问你想要的时间窗口。你想知道某个 TCP 连接的三次握手用了多少微秒?用户态程序只能在握手成功之后获得一个时间戳——但 SYN 包的实际发送时间、SYN-ACK 的接收时间,都发生在内核里。你拿到的是一个"近似的估算"而不是精确测量。你想知道内核因为内存不够而杀掉的 OOM Kill 具体针对的是哪个进程?日志里可能有,但你需要开启一个特定的内核日志级别。

有没有办法绕过用户态的隔阂,直接在内核里干活,还不影响用户态程序?

有。这就是 eBPF 要解决的问题。

eBPF 的全称是 extended Berkeley Packet Filter——名字里有"包过滤"是因为它的祖先 BPF 在 1992 年就是为数据包过滤设计的。但 eBPF 做的事情早已远远超出了它的名字。eBPF 是一套运行在 Linux 内核里的虚拟机——让你可以在内核的各个事件钩子上插入你自己写的监控程序。不是挂一个外部 Agent,不是改应用代码,不是重启服务——而是直接监听内核的内部事件流。

eBPF 的运行机制

当你说"我用 eBPF 做性能监控"时,实际上发生的流程是这样的:

你的监控需求(比如"我想看所有 write() 调用的延迟分布")→ 你写了一段 eBPF 程序(用受限的 C 语言,但不是手写,通常用 BCC 或 bpftrace 的简化语法)→ 这个程序被用户态工具编译成 eBPF 字节码 → 你通过 bpf() 系统调用把这个字节码加载进内核 → 内核的验证器检查这段代码:有没有无限循环?指针访问是否安全?有没有访问不该访问的内存?→ 验证通过后,eBPF 程序被 JIT 编译成原生机器码挂载到你指定的内核事件上。

程序开始运行后,每一次触发这个事件——每次创建 TCP 连接、每次读写文件、每次分配内存——你的 eBPF 程序都会被内核调用。你在程序里可以记录时间戳、计数、抓取当前进程的堆栈信息、提取网络协议头字段——一切处理都在内核里完成,没有上下文切换。最终你把结果存在 eBPF Map 里——一个内核和用户态共享的内存映射——然后用户态工具周期性地读取这些 Map 以获取统计结果。

这一切发生的时候,你的业务代码完全无感。CPU 开销极小——每个 eBPF 程序严格限制在 100 万条指令以内,验证器保证不会跑飞。

零侵入、零重启、极低开销的内核级可观测能力。 这就是 eBPF 的核心叙事。

eBPF 的四大战场

网络监控。 这是 eBPF 最早也是最成熟的领域。你可以监控每一个 TCP 连接的完整生命周期——从 SYN 包到达、三次握手耗时(微秒级别)、到数据传输、最终 FIN 关闭。你可以精确定位丢包发生在哪——是发生在网卡驱动、还是在 TCP 协议栈的某个缓冲区满、还是被 iptables 丢弃。你可以实时看到哪个端口在同哪个 IP 频繁通信。端口扫描、DDoS 攻击——在网络包进入协议栈的那一刻,eBPF 已经在分析了,不需要等到应用层日志。

系统调用追踪。 任何程序想做什么都得通过系统调用进入内核。read()write()open()close()mmap()——这些是对内核的"服务接口"。eBPF 可以挂在你选定的系统调用上,记录参数、返回值、耗时、以及调用者的 PID 和进程名。你可以监控 open() 的文件路径参数——哪个进程在试图打开哪些文件?你可以监控 mmap() 的内存映射——突然的异常大映射可能是内存漏洞。

应用性能分析。 这是最让人兴奋的一块。不需要在代码里写任何埋点,eBPF 直接从内核层面读取每个 CPU 核上的 stack trace,按频率聚合后生成火焰图。因为是内核级别的采样,它能捕获到用户态的动态链接库、JIT 编译的代码、解释器内部——传统的采样工具做不了这个。你甚至可以用 eBPF 来追踪 JVM 或 Node.js 的 GC 行为——GC 的触发频率、每次回收的暂停时间、回收了多少内存。

安全。 eBPF 是一个非常强大的安全检测基础设施。它可以监控任何系统调用的调用者和参数——这意味着如果有一个低权限进程突然尝试 setuid(0)(试图获取 root 权限),eBPF 可以即时捕获并记录。它可以检测到一个正常进程突然开始访问它从未访问过的文件路径。它可以识别异常的 DNS 查询——比如一个被入侵的容器正在频繁查询 C2 服务器的域名。

eBPF 的工具链

你大概率不需要亲手去写受限的 C 代码。拿几个工具名字就行:

BCC(BPF Compiler Collection)目前是生态最成熟的工具集。它提供 Python 和 Lua 前端——你写几行 Python 调用它的高层 API,它帮你生成、编译、加载 eBPF 程序并读取结果。BCC 自带了 80 多个预置工具,每个都是为特定场景优化的:execsnoop 监控所有新进程的创建——你马上就能发现是不是哪个脚本在启动时偷偷 fork 了一堆子进程。tcplife 追踪每个 TCP 会话的完整生命周期。biolatency 用热力图的形式画出磁盘 I/O 延迟分布——你会惊讶地发现你的磁盘延迟分布不是一条正常的钟形曲线,而是一条有两个驼峰的双峰曲线——第一个驼峰是缓存命中,第二个驼峰是真实磁盘寻道。

bpftrace 是一个高级追踪语言。如果你用过 DTrace,bpftrace 的语法几乎是 1:1 的对应。它特别适合快速、临时的探测——"我想看看过去 30 秒里谁在调用 open(),调了多少次,每次的路径参数是什么"——一行命令加一行脚本,立刻看到结果。

Cilium 把 eBPF 的能力带到了 Kubernetes 网络层。它做的事包括替代 kube-proxy 做服务的负载均衡(基于 eBPF 的转发比 iptables 快一个数量级),为每个 Pod 制定网络策略(白名单和黑名单在网卡驱动层就执行了),以及透明地对东西流量做加密。

Pixie 是 New Relic 开源的 K8s 原生可观测平台,底层全基于 eBPF。你装一个 DaemonSet 到你的 K8s 集群里,它就自动采集所有 Pod 的请求协议、metrics、堆栈信息。不需要额外配置每个服务的监控端点。对于不想在监控搭建上花太多时间的小团队,Pixie 是一个非常吸引人的起点。

eBPF 的边界——它不是来抢 APM 饭碗的

eBPF 虽然牛,但它有几个硬伤你必须心里有数。

它只看操作系统的行为,不看业务逻辑。 eBPF 能告诉你"这个进程调用了 300 次 read()",但它不知道这 300 次读的是用户的订单数据还是商品图片。如果你想回答"为什么用户看不到自己的历史订单",你需要日志中的业务字段——user ID、订单状态、返回结果。eBPF 补不了这块空白。它不是在替代你的 APM,而是在给 APM 提供一个更底层的数据源。

它对内核版本有要求。 不是说所有 eBPF 特性都可以在 Linux 4.x 上跑。很多高级特性——比如 BTF(BPF Type Format,用于内核数据结构自描述)、ring buffer、全局函数——都需要 Linux 5.x 或更新。如果你的生产环境跑着一个三年没升级的 CentOS 7(内核 3.10),你能用到的 eBPF 能力极为有限。

它不适合长期趋势分析。 eBPF 是事件的快照。你可以用 eBPF 生成火焰图、统计延迟分布、发现热点函数——但长期的历史对比、阈值告警、趋势预测还需要 Prometheus 这样的时序数据库。

它不是万能药。 eBPF 是世界上最强大的系统可观测工具之一,但它不是所有问题的答案。如果你的线上事故,根因是"某条 SQL 在数据量变大后走了全表扫描",eBPF 能告诉你磁盘 I/O 高了,但你的 DBA 依然需要数据库的慢查询日志来发现具体的 SQL 语句并修复索引。eBPF 告诉你"哪疼",但你需要其他工具来告诉你"为什么疼"和"怎么治"。


方法论:知道看什么比有多少数据更重要

讲了这么多工具和技术,现在说一个你可能会忽略的事实:监控的质量,主要不取决于你用什么工具,而取决于你知道该看什么。

给你一个 Prometheus 实例和全套 Grafana 面板——但如果你的监控策略是"把所有能采的指标全采了,放着慢慢看",你就是在自欺欺人。

采集的数据量再多,也不可能帮你发现问题——除非你有一套清晰的分析框架来指导你的视线。

USE:分析任何资源的万能钥匙

USE 是 Brendan Gregg 提出的一套方法论——Utilization 利用率、Saturation 饱和度、Errors 错误率。

它的底层逻辑出奇地简单:任何会出问题的东西,问题就出在这三个方面。 不是四个、不是五个,就这三个方向。

用 CPU 举例:

利用率高了?可能不是问题——CPU 的设计目标就是被利用。如果它到了 100% 但请求处理速度没有下降,说明它没有饱和——它只是跑满了。利用率 100% ≠ 有问题。

但如果 CPU 的 run queue(就绪队列)开始堆积——意味着有很多线程想跑但没 CPU 可用——这就是饱和度。饱和度是高利用率叠加低效调度或锁竞争的结果。同时你开始看到 context switch 失败的计数在跳——这就是 Errors。三项全亮,问题确认。

USE 最牛的地方在于,它的三层结构可以套到任何资源上:

CPU → 利用率(CPU 占比)、饱和度(run queue 长度)、错误(指令错误、硬件中断异常)。 内存 → 利用率(已用比例)、饱和度(swap 频率、OOM 发生次数)、错误(malloc 失败计数)。 磁盘 → 利用率(I/O 使用率)、饱和度(I/O 等待队列长度)、错误(读/写错误计数、坏块)。 网络 → 利用率(带宽使用率)、饱和度(数据包队列长度、丢包率)、错误(CRC 校验错误、冲突丢包)。

你不必每次排查问题时把这四个资源全部走一遍。但当每次排查卡住时,你可以做一个 USE 检查清单式的回退——先看 CPU 有没有饱和,再看内存有没有在 swap,再看磁盘 I/O 等待是不是在涨,最后看网卡丢包率是不是异常。 五分钟,四个方向,去掉了 90% 的可能性。

USE 不告诉你"问题在哪",但帮你排除"不在哪"。 这种"缩小圈"的能力,在凌晨三点比任何工具都值钱。

RED:为微服务量身定做

RED 是 Tom Wilkie(Grafana Labs 的 VP of Product)为微服务架构提出的三个核心指标:Rate(请求速率)、Errors(错误率)、Duration(响应时长)。

这三个指标覆盖了一个面向请求的服务最核心的健康度。如果你只能看三个数字来判断一个服务"好"还是"不好",就选这三个。不需要更多,也不应该更少。

在 Prometheus 里,这三个指标分别表达为:

# 请求速率
rate(http_requests_total[5m])

# 错误率
rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m])

# P99 延迟
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

要特别提醒你一件事:统计分位数时,必须先 rate()histogram_quantile() 反过来算的结果是错的——rate 算的是每秒的增量分布,histogram_quantile 在这个分布上找分位数。如果你先分位数再 rate,相当于你在一个不完整的时间桶上做了一个错误的排序。这是 Prometheus histogram 最经典的坑——Prometheus 官方文档专门强调过,但我看过的项目里至少有一半踩过这个坑。

Google 的四大黄金信号

Google 在 SRE 这本书里把监控信号凝练为四个维度:延迟、流量、错误、饱和度。

你可能会说:这不就是 USE 的三个加上 RED 的重新排列吗?

还真不是。四大黄金信号的一个关键不同是——它们是站在用户视角定义的,而不是资源视角或服务视角。

延迟是用户最直接的感受。用户在点击一个按钮之后,他在等待。等待的每一毫秒都在积累他的不满意度。但要记住:你必须把"成功请求的延迟"和"失败请求的延迟"分开看。很多失败请求是毫秒级别返回的——被网关直接拒绝,或者在前几层就被 validation 拦截了。如果你把它们和正常请求混在一起算平均延迟,你的数据会漂亮得离谱——但屏幕上用户依然在盯着白屏发呆。因为你把"很快就失败"和"成功但很慢"混在了一起。

流量是服务承受的压力。对于 HTTP 服务,QPS 当然是最重要的。但对于一个 WebSocket 服务,并发连接数可能比 QPS 更能反映真实负载。流量是你的"压力"信号——当流量涨到一定程度时,其他信号会连带变化。

错误是明确地出现了与预期不同的路径。你的代码里需要定义清楚——try-catch 里往外抛的是错误,业务逻辑里的余额不足不是错误。不是所有 HTTP 500 都需要你半夜跳起来——你在监控里看到的错误数,需要跟"什么是真正的异常"的定义对齐。

饱和度是最容易被忽略但最关键的信号。它指的是服务所在的那个最受限资源的满度。你的数据库连接池有 20 个连接——那 20 就是硬上限。你可能离 CPU 100% 还很远,但一旦 20 个连接全被占——新请求连数据库都连不上,QPS 断崖下跌。饱和度告诉你"你离最近的悬崖还有多远"。

三层体系:不要只盯着一层看

光有单一维度的方法还不够。一个真正成熟的监控策略是分层思考,就像一个侦探不会只看指纹而不看不在场证明。

底层是资源层,用 USE。 CPU、内存、磁盘 I/O、网络带宽。这层告诉你物理世界是否正常运转。如果你的 Kubernetes Pod Limit 配太小导致容器被 OOM Kill——你不需要看到 Pod 重启就能从内存饱和度的曲线上看出来。

中间是服务层,用 RED。 每个微服务的请求量、错误率、响应时间。这层告诉你每个服务是否在正常工作。如果支付服务的错误率在飙升而其他服务的错误率正常——你已经知道该重点查谁了。

最上面是体验层,用 Apdex 和业务指标。 用户注册成功率、从下单到支付的平均链路耗时、搜索结果页的点击率。这层告诉你用户是否满意,不是老板是否满意,不是 SRE 是否满意——是你的用户是否在用完之后还想再来。

三层之间的关系不是平行的——是一层支撑一层。什么叫做支撑?如果你只看体验层——"下单成功率在下降"——你知道出事了,但不知道谁引起的。如果你有了服务层——"支付服务的错误率在涨"——你把范围缩小到支付。如果你再往下看到资源层——"支付服务所在数据库的磁盘 IOPS 用满了"——你找到了物理根源。

不要只在一层上查问题。 这是我经手几十个生产事故后,能给你的最值钱的一条实操经验。大多数事故排查被拉长的原因不是"找不到答案",而是"找错了层"——在资源层找应用层的 bug,或者在服务层找网络层的故障。


几个关键指标,你得会算

QPS:用户洪水来的时候你扛得住吗

QPS(每秒查询数)是你被问得最多的一个指标。产品经理会问:"咱们系统能扛多少 QPS?"运维会问:"现在的流量到上限的百分之多少了?"CTO 会问:"双十一预估流量多少 QPS?我们需不需要再加服务器?"

这背后有个隐含的假设——QPS 是一个常数。它不是。

大多数系统的流量不是均匀分布的。你在上班时间的早 9 点可能迎来一波潮水,然后平稳,然后在中午再一波,晚上再一波。当你做容量规划时,你应该关心的不是"全天的平均 QPS",而是"峰值时刻的 QPS"——因为你的系统不是在平均流量下崩的,是在峰值时刻崩的。

有一个经典的计算方法,用80/20 定律来做估算。

这个定律说的是:80% 的流量集中在 20% 的时间段里。如果你一天有 1000 万次请求,那不意味着每秒平均有 1000 万 / 86400 = 115 次请求。因为真实流量不是平摊的——大部分请求集中在某几个高峰时段。用 80/20 定律来估算峰值:

峰值 QPS = (日均总请求 × 80%) / (86400 × 20%)
         = (10,000,000 × 0.8) / (86400 × 0.2)
         = 8,000,000 / 17,280
         ≈ 463

所以日活 1000 万次请求,峰值时刻你可能需要扛住 463 QPS。这是单个入口的估算——所有用户请求打到 API 网关的速率。

但魔幻的是,在微服务架构里,这个 463 只是入口 QPS。一个请求进入网关后,可能触发 3 到 8 次下游调用。你的支付服务 QPS 可能是 463(和下单量 1:1),但你的数据库查询 QPS 可能是 463 × 4 = 1852(一次下单涉及多条 SQL 查询——查用户、检查库存、写订单、写日志)。你的 Redis QPS 可能是 463 × 6 = 2778(多次缓存查询——用户信息、商品信息、库存、配置、优惠券状态等)。

QPS 要按路径分开算,不能拍一个数字所有服务通用。 你在做压测时,不是对着 API 网关打流量就够了——你得知道每个下游服务实际承受的倍数,然后分别验证它们的承载能力。

响应时间:平均值在撒谎,而且撒得很厉害

响应时间是你天天会看的一个指标。我想强调一件事,老生常谈但始终没改掉:均值是统计里最危险的数字——它会在你最需要真相的时候给你一个温柔的谎言。

看个例子。你的接口 99% 的请求在 200 毫秒内返回——快,没什么说的。但有 1% 的请求因为某种边界条件要 10 秒。

平均值是多少?

(99 × 200 + 1 × 10000) / 100 = 298 毫秒

298 毫秒。

你和老板汇报:"咱们接口平均响应时间 298 毫秒,远低于 500 毫秒的行业标准。"

报表上的数字挺漂亮。

但那 1% 的用户——一百个用户里就有一个人——等了整整十秒。十秒钟够他打开你的 App、看到菊花转、起身倒杯水、坐下再看一眼、关掉、去应用商店给一个一星。这个人的体验在你的"平均值"里完全蒸发掉了——因为 298 和 200 之间只差 98,让你觉得"还行,差距不大",但真实的分布里有一条尾巴直插到 10000 毫秒。

正确的做法是用分位数看长尾。

P50 是半数标杆——告诉你一半用户的体验是什么水平。如果你的 P50 是 200 毫秒而 P99 是 12 秒,说明你的系统在大部分时间里对大部分用户都很友好,但对少数用户极其恶劣——而后者往往是最活跃、最频繁下单的用户(轻度用户的操作路径短,触发长尾的概率低)。

P95 告诉你最慢的那 5% 在经历什么。P99 告诉你你的系统撑到极限时的行为特征——当你的系统几乎被压满时,用户的体验会从哪里开始崩坏。

Apdex:量化用户体验

Apdex 的意思是 Application Performance Index,应用性能指数。

这个指标把一个很模糊的概念——"用户体验好不好"——转化成了一个 0 到 1 之间的具体数字。为什么这是个好东西?因为如果你不能量化用户体验,你就没法比较——你不能说"上周用户满意度是 B+,这周是 B-",你不能做历史趋势,你不能对不同接口的用户体验做排序。

先设定一个阈值 T。T 是"你觉得多快才算快"。比如你定下单接口 1 秒内算"满意",那 T = 1 秒。

然后对每个请求打标:

  • 响应时间 ≤ T:满意(Satisfied)——用户几乎感觉不到等待
  • T < 响应时间 ≤ 4T:可容忍(Tolerating)——用户觉得有点慢但还能接受
  • 响应时间 > 4T:失望(Frustrated)——用户已经在心里骂了

然后套到这个公式:

Apdex = (满意数 + 可容忍数/2) / 总数

为什么"可容忍"要除以 2?因为可容忍的用户虽然没暴怒,但他们的满意度已经打折了。在计算公式里,"一个满意"和"两个可容忍"的价值相同——这是一个业界接受的经验性折中。

如果 100 个请求里 80 个在 T 内、15 个在 T 到 4T 之间、5 个超过 4T:

Apdex = (80 + 15/2) / 100 = 0.875

0.875 是一个还不错的分数。通用的横杆标准是这样的:

  • Apdex > 0.9:优秀(Excellent)
  • 0.85 ~ 0.9:良好(Good)
  • 0.7 ~ 0.85:一般(Fair),可以开始关注优化了
  • < 0.7:差(Poor),你的用户在大规模流失

Apdex 的妙处不在于它能精确指示问题——它绝对不能替代 Profiling 或 Tracing。它的价值在于把不可比较变成了可比较。你可以在同一把尺子上比较三个不同接口的"用户满意度水平",从而做出一个清晰的优化优先级排序。你可以在每次发布后用 Apdex 做一个前后对比——"这个版本上线后,支付接口的 Apdex 从 0.91 跌到了 0.82"——你不需要等到用户投诉才知道这次发布出了性能问题。


技术栈:不是越重越好

从躺平到站起来

我知道你在想什么——"前面讲了这么多概念,说到底是不是在推销某个商业 APM 产品?"

不是。

开源组合完全能搭出一套生产级的 APM 方案。我在多个项目上都跑过这套栈,不敢说零维护,但维护成本远低于商业方案且功能基本够用。这套组合拳就是:Prometheus + Loki + Tempo + Grafana + OpenTelemetry。

Prometheus

Prometheus 是整个栈的中心——时序数据库。它负责存储和查询所有"随时间变化"的数字——CPU 使用率、请求量、错误率、响应时间分位数。

Prometheus 做了几个在当年看起来非主流、但最终被证明是正确的关键选择。

第一个是 Pull 模型。不是等被监控的应用自己推送数据,而是 Prometheus 主动去每个目标的 /metrics 端点抓取。这个看似反直觉的设计有一个巧妙的副作用:如果应用挂了,Prometheus 知道它是挂了(scrape 失败)而不是"可能没数据"(push 模式下很难区分"挂掉"和"正常工作但无数据")。

第二个是多维标签。在 Prometheus 里,每条指标可以带任意数量的 label。http_requests_total{method="POST", status="200", endpoint="/orders"}——你可以用任意标签组合做过滤、聚合、关联。传统监控系统(像 Zabbix)的扁平指标模型做不到这一点——在 Zabbix 里,每个标签组合都是一个独立的指标,你没法动态地做"按 status 分组看错误率"这种灵活查询。

第三个是 PromQL。它的查询语言看起来像 SQL,但本质是一个函数式的向量处理引擎。你操作的不是单点值,而是"一组时间序列的向量"——rate() 把累积计数器变成每秒增量,avg_over_time() 做时间窗口内的均值计算,histogram_quantile() 从直方图里还原始分布分位数。所有这些操作都发生在查询端,无需在数据库中预聚合。

Loki

Loki 是用来存日志的。

Elasticsearch 的问题大家都知道——它很强大,但也很"贪婪"。为了做全文搜索,ES 对几乎每一个字段都建倒排索引,结果是存储膨胀——你的日志可能比原始大小膨胀两到十倍,查询虽然快,但大量资源消耗在了索引维护而不是查询服务上。

Loki 的哲学不同:日志不需要这么贵的索引。 它只对少量关键标签建索引(比如 service name、pod name、env),日志正文不建倒排索引。查询时通过并行扫描来实现——但扫描的是压缩后的对象存储文件,而不是逐字节扫描文本。这套策略的结果是存储成本降低约 10 倍,查询速度对日志场景完全够用。

Loki 和 Prometheus 最大的协同在于它们共享标签模型。你在 Prometheus 查到一个指标异常,Grafana 可以直接给你一个"跳转到 Loki 查看同时间窗口、同服务标签的日志"的链接。指标告诉你"出事了",一行日志告诉你"具体出了什么事"。

Tempo

Tempo 是分布式追踪的后端——负责存储那些成千上万个 Span 组成的 Complete Trace。

传统做法是 Jaeger + Cassandra 或 Elasticsearch——每个 Span 的每个字段都建索引以便搜索。这在 Trace 数据量不大的时候还行,但一旦上了规模——每秒几千个 Span、每个 Span 十几个字段——存储成本会让人痛苦。

Tempo 走了一条不同的路:你不需要索引 Trace 里的每一个字段,你只需要知道去哪找它。 Tempo 用对象存储(S3、GCS、MinIO、Azure Blob)做后端,只对 Trace ID 和少量操作名建索引。你搜一个 Trace,用 Trace ID 查——秒级返回。它在存储成本上比传统方案低一到两个数量级。

Grafana

Grafana 不只是画图工具——它现在已经是一个统一的可观测性交互终端

它可以同时连接 Prometheus(查指标)、Loki(查日志)、Tempo(查 Trace)和业务数据库(MySQL 或 PostgreSQL)。在一个 dashboard 上你可以放一个 CPU 的时序曲线图,下面跟着一个 Loki 的日志搜索面板,再下面放一个 Tempo 的 Trace 详情视图。

而且 Grafana 支持了数据链接功能——你在 Prometheus 曲线图上看到某个时间点有异常尖峰,鼠标一点,自动跳转到 Loki 搜同时间段的 error 级别日志。再在日志里看到一个 Trace ID,再一点,自动打开 Tempo 的瀑布图。从"发现异常"到"看到具体哪次调用超时"——三次点击。这是人类历史上排查分布式系统问题的最短路径。

完整的流水线

把这些拼起来,一条典型的云原生 APM 数据流是这样的:

你的服务进程
  │
  ├── OpenTelemetry SDK (自动探针)
  │    ├── 指标 → OTel Collector ──→ Prometheus
  │    ├── 日志 → OTel Collector ──→ Loki
  │    └── 追踪 → OTel Collector ──→ Tempo
  │                                        │
  └───────────────────────────────────→ Grafana (统一查询与可视化)

OTel Collector 在这个流水线里的角色极其关键——它是你对抗"数据泛滥"的唯一阀门。它接收数据后可以做:过滤(丢弃你不关心的 metrics,减少 Prometheus 的存储压力)、聚合(把 10 秒钟的请求合并成一行统计以减少网络传输)、脱敏(自动去掉敏感字段如用户的手机号或身份证号)、采样(保留 1% 的 Trace 细节用于深度分析,其他的只保留聚合统计)。如果没有 Collector 这个中间件,你的后端存储会在三个月内被数据吞没。


AIOps:当系统多到你管不过来

到了 2026 年,你的微服务可能从一开始的 3 个长到了 30 个,然后从 30 个变成了 300 个。每个服务有自己的 USE 指标、RED 指标、Apdex 曲线、依赖拓扑。300 个服务的监控数据量是人脑不可能跟上的。

你不可能手动在 300 个 dashboard 之间切换来寻找异常——就算你能,你也不可能在凌晨三点被叫醒后还能保持"冷静的全局判断力"。你的大脑在那个状态下的判断质量,跟闭着眼转轮盘区别不大。

这时候你需要的不再是更多数据,而是帮你消化数据的机器。这就是 AIOps 的落地场景——不是"人工智能取代运维"这种科幻故事,就是实用层面的"机器帮你预处理一遍,你只在机器搞不懂的时候被叫醒"。

异常检测

传统告警是阈值告警——"CPU 超过 90% → 报警"。

这套玩法的维护成本极其高,高到一个全职的 SRE 可能每周花两天时间去"调告警阈值"——因为阈值设低了误报太多,设高了漏报太多。而且最关键的是:阈值不能适应业务周期。 双十一当天的正常 CPU 跟平时凌晨的正常 CPU 不是同一个数字。你设一个静态阈值,要么在双十一的时候把告警通知轰炸到所有人关静音,要么在平时的时候什么都发现不了。

基于机器学习的异常检测做的事情是——学习每个指标的历史模式,然后在你真实数据偏离预测区间时报警。它自动学会了"每天中午 12 点有流量尖峰——正常,不报警","每天凌晨 3 点 CPU 微降至 5%——正常,不报警"。它甚至还学会了"CPU 和 QPS 同时涨——正常的高流量",和"CPU 在涨但 QPS 在跌——有后台任务或无限循环在消耗资源"之间的区别。这些联动模式用静态规则根本没法表达。

根因分析

告警风暴是运维最痛苦的体验。

一个支付服务挂了,可能同时触发不少于 50 条告警——来自四五个不同的监控规则("支付接口错误率 > 1%"、"支付接口 P99 > 3 秒")、来自下游的依赖告警("订单服务调用支付接口失败")、来自基础设施的连带告警("数据库连接池耗尽"——因为支付服务挂了之后所有线程在空等数据库连接)。这些告警像雪崩一样涌进你的手机,但你需要的其实就一条信息:"支付服务不可用,原因可能是短信网关超时"。

智能告警系统做的事情就是:把这些相关的告警按时间窗口和依赖拓扑自动聚合成一个"事件"。然后基于你的服务依赖图(哪个服务调用哪个,哪个依赖谁)和历史事件数据,推演出一个"最可能的根因"——排在第一位,供你优先排查。如果你排查之后发现不是——没问题,手动标为"误判"——这本身也是系统学习的信号。

注意我的用词——推演,不是结论。AIOps 给你的不是法官判决书,是刑侦现场分析报告。它说"根据经验,你得先查这个方向"。你的判断力不可替代——但你现在有了一个靠谱的启发式起点。

说点实在的

AIOps 不是魔术。它有个硬门槛叫数据质量

如果你的监控基础设施本身就是漏洞百出的——指标缺失、采集间隔不一致、时间戳对不齐、日志和 Trace 的抽样率不一样导致统计数据有偏差——那无论你拖了多高级的 ML 模型过来,出来的结果都是垃圾。人工智能解决不了信息缺失问题。AIOps 的前提是你已经把基础的可见性体系建扎实了。

另外,别指望 AIOps 系统一上线就让你所有误报消失。误报和漏报是必然的——模型需要一段在线学习期来适应你系统独特的流量特征。这个过程没法跳过也没有捷径。你不能在第一天给了它 30 天的历史数据就期望它在第一天晚上精准捕获所有异常——它会误报一些、漏报一些,这是正常的。

AIOps 不能取代运维工程师的决策能力。 它能做的和应该做的,就是在凌晨三点帮你过滤掉 90% 的噪音,把真正需要你大脑的那 10% 高亮出来。你的判断力依然无可替代——但你现在的判断有了依据,不再靠猜。


不是总结,是判断框架

看到这儿你大概已经读了不少了。现在是"合上书本之后你还记得什么"的部分。我不要给你一个总结——总结是复述,复述不能帮你在凌晨三点解决问题。

我能给你的是一套判断问题的方法。一套你可以在任何事故里直接套用的四步框架。

以后当你的系统出问题时——一定会出的,这是写软件的宿命——别慌。别一头扎进日志里漫无目的地翻。先站住,问自己这四个层次的问题:

第一层:影响面。

这问题多大?是所有人都受影响还是只有一小部分人?是全部接口不可用还是就一两个在报错?搞清影响面不是让你先写个事故报告——是让你判断你需要投入多大的精力。如果只是一个边缘功能的偶尔报错——你可能不需要凌晨三点爬起来。但如果是支付挂了——你怎么也得起来了。

第二层:收敛定位。

你的代码最近变了吗?没变。配置改了吗?没改。流量有异常尖峰吗?没有。

那 90% 的可能性——问题不在你的代码里。打开你的依赖拓扑图,看看哪个下游在泛红。是数据库延迟高了?是 Redis 命中率掉了?是第三方 API 报错了?你最大的武器就是先排除"我自己"——因为你的代码是你唯一能直接控制的东西。

第三层:分层下钻。

已经把范围缩小到某个依赖或某个服务了。现在不要只在一层上找答案。走一遍分层清单:

如果是资源类问题——拉出 USE。CPU 有没有饱和?内存有没有在 swap?磁盘 I/O 等待队列在涨吗?网卡丢包率异常吗?四个数、五分钟。

如果是代码级性能问题——打开火焰图。看最宽的是谁。是正则?是 JSON 序列化?是排序?看一眼,你知道优先级。

如果是服务链问题——抽一条慢请求的完整 Trace。看时间花在了哪一环。是数据库查询慢,是第三方 API 慢,还是你写的中间件在做不必要的重复计算。Trace 的瀑布图不会骗你。

第四层:逻辑闭环。

你找到了根因。你修了。你上线了。

然后呢?你验证了吗——不只是 P50 恢复了,P99 也恢复了吗?不只当前没问题了,过去一小时的数据显示趋势是健康的吗?

你在监控系统里加了一条规则吗——"下次这个现象再出现,立即告警"?你有没有写一份简短的事后分析——不是为了应付流程,而是给自己看——"下次这个问题再发生,我能在 30 秒内反应过来"?

APM 不给你答案。它给不了你答案。它给你的是一张地图。 地图上标了地形——哪里是山地、哪里是河流、哪里有桥。但答案——"我该怎么走到目的地"——是你在地图上用你自己的判断力走出来的。

记住:监控的核心从来不是"采集"。采集是体力活,谁都能做。监控的核心是建立认知——让你的系统在你不在场的时候,依然保持"可读"。你的系统在变大、在变复杂、在产生你设计之初没预料到的行为——这些不是错误,这些是一个复杂系统成长过程中的自然副产物。

但只要你有一套良好的观测体系,这些行为不是"意外"。它们是"待发现的事实"。

卧槽。

别让你的系统运行在一个你自己都不敢信任的黑暗里。


全景关系图:一图看全监控体系

读到这儿你可能已经有点晕了——服务监控、错误追踪、日志、分布式追踪、火焰图、eBPF,一堆概念堆在一起。这些到底谁管谁?它们之间是怎么串起来的?

下面这张图帮你把前面聊的所有东西串成一条线。从上到下是"发现问题→定位问题→分析根因→深度排查"的递进过程:

Loading diagram...

这张图的读法是从上往下:

第一层——服务监控:这是你凌晨三点被叫醒时最先看的东西。Prometheus 存着所有服务的 RED 指标(请求量、错误率、响应时间),Grafana 把它们画成曲线。这一层告诉你"出事了"——但通常不告诉你"为什么出事"。就像你家的烟雾报警器——它响了,但你还不知道是厨房烧糊了还是隔壁在烧烤。

第二层——分布式追踪:从服务监控的异常曲线往下钻,你会抽一条慢请求的 Trace。Tempo 存着完整的 Span 数据,Grafana 把它渲染成瀑布图。这一层告诉你"支付接口等了 2.7 秒,其中 2.65 秒在等第三方回调"——你把范围从"整个订单服务有问题"收敛到了"支付这个 Span"。

第三层——日志:Trace 告诉你时间花在哪了,但没告诉你传了什么参数、返回了什么错误信息。你需要日志来补这个缺口。Loki 存着结构化日志,通过 Trace ID 跟第二层的 Span 关联——点一下就能从瀑布图跳到对应的日志行。这一层告诉你"这个超时请求的用户是谁、他填的金额是多少、返回的错误码是什么"。

第四层——代码性能:如果前面三层都指向"是我自己代码慢"——CPU 占用高但不知道高在哪——你需要火焰图。这一层不属于 OTel 的覆盖范围,需要单独的 Profiling 工具。它告诉你"60% 的 CPU 时间花在了 regexp.MatchString 上"——然后你改一行代码,问题消失。

第五层——内核观测:当所有上层工具都说"我这边正常"但问题依然存在时——就像前面交换机丢包导致 TCP 重传的例子——你需要 eBPF。它直接挂在内核事件上,不看应用层逻辑,只看系统级行为。这一层帮你区分"我的代码有问题"和"操作系统/网络有问题"。

三个关键角色在图中各就各位:

  • OpenTelemetry 横跨前三层——它是数据采集的"统一语言"。没有它,你的服务监控、追踪、日志各说各话,Trace ID 传不下去,Grafana 里的数据是断的。
  • Prometheus + Grafana 是第一层的核心搭档——Prometheus 管存储,Grafana 管展示。但 Grafana 的能力不止第一层——它能同时连 Loki 和 Tempo,在一个面板上把指标、日志、Trace 串起来。
  • eBPF 不参与前三层——它不下场管业务指标和日志,它专攻第四层和第五层的底层盲区。它和 OTel 是互补关系,不是替代关系。

记住这张图的结构,下次出问题时你就知道该从哪一层开始看、往哪一层下钻、每一层能给你什么信息、给不了什么信息。


FAQ:读者困惑预判

下面这些问题,是我在跟团队聊 APM 时被问得最多的。如果你也有这些困惑——别担心,你不是一个人。

Q1: Prometheus 和 Grafana 到底怎么配合?各自管什么?

Prometheus 是数据库,Grafana 是显示器。

Prometheus 负责"存"和"查"——它把 CPU 使用率、QPS、错误率这些带时间戳的数字吞进去,等你用 PromQL 问它"过去五分钟支付接口的 P99 延迟变化趋势是什么"。Grafana 负责"画"和"看"——它连上 Prometheus 的数据源,把那些冰冷的数字变成你能秒懂的曲线图、柱状图、热力图。

没有 Grafana,Prometheus 的数据你只能对着 PromQL 查询结果干瞪眼——那是一排排数字,不是一个故事。没有 Prometheus,Grafana 就是个空壳——画布再好看,没数据往上面画。

而且 Grafana 能同时连多个数据源——Prometheus(指标)+ Loki(日志)+ Tempo(Trace)——在同一个面板上无缝切换。这是它真正逆天的地方:从"发现异常曲线"到"看具体日志"到"打开 Trace 瀑布图",全在一个界面里完成。

Q2: APM 和日志系统的边界在哪?

日志是"事后记录",APM 是"实时感知"。边界很清楚。

日志是请求处理过程中你主动写的文字描述——"用户 123 下单成功,订单号 45678,金额 99 元"。它是离散的事件快照,你需要去翻、去搜、去 grep。日志的价值在于细节——它能告诉你一个具体请求的上下文。

APM 的指标和 Span 是自动采集的结构化时序数据——每秒钟聚合多少请求、多少失败、P99 延迟多少。它不需要你去翻,它以曲线和图的形式实时呈现在 Dashboard 上。APM 的价值在于统计视图——它能告诉你整体趋势和异常模式。

最简单粗暴的区分方式:如果你想知道"过去一小时有多少个订单失败了",看 APM。如果你想知道"用户张三刚才那个失败的订单具体报了什么错",查日志。两者谁也替不了谁——什么鬼才会想用其中一个替代另一个。

Q3: 上了 APM 还需要单独做日志吗?

需要。而且别指望 APM 能替你省掉日志这件事。

APM 的 Trace 能告诉你"支付接口花了 2.7 秒,其中 2.65 秒在等第三方回调"——但它不会告诉你"回调返回的错误码是 INSUFFICIENT_FUNDS,用户卡里没钱"。它不会告诉你"这个请求的用户是新注册的,他的风控评分是 87 分,触发了人工审核流程"。这些业务上下文只在日志里。

Trace 给你一张地图——告诉你山在哪、河在哪。日志是实地考察记录——告诉你那座山的具体地形、那条河的水流速度。没有日志,你拿着 Trace 只能知道"某个 Span 慢了",但不知道慢是因为传了某个特殊参数还是第三方在那段时间正好在维护。

另外,有些东西 APM 本就覆盖不到——比如业务审计日志(谁在什么时间做了什么操作)、合规日志(支付记录、个人信息访问记录)。这些跟性能监控半毛钱关系没有,但法律要求你留存。把业务审计塞进 APM 里就像用冰箱当书架——能放,但傻逼透了。

Q4: eBPF 和传统 APM 探针有什么区别?什么时候该用哪个?

传统 APM 探针跑在应用进程里,eBPF 跑在内核里。一个懂业务,一个懂系统。

APM 探针(比如 OTel SDK 或 Java Agent)通过字节码注入或 SDK 埋点拦截请求。它的优势是懂业务——它知道这是"下单"请求、那是"查库存"调用、这个 Span 的父 Span 是哪个。它能把技术指标和业务语义无缝关联。

eBPF 不碰你的应用代码。它挂在内核事件钩子上——TCP 连接建立、系统调用触发、内存分配发生——直接在内核里记录时间戳和统计数据。它的优势是零侵入、零重启、极低开销,而且能看到应用层探针看不到的东西——TCP 握手耗时、内核调度延迟、磁盘 I/O 的真实等待时间。

什么时候用 APM 探针? 日常监控、业务级可观测、分布式追踪——你需要理解"用户下单到支付完成的完整业务链路"。99% 的场景下 APM 探针就够了。

什么时候用 eBPF? 当 APM 告诉你"服务慢了"但你不知道慢在哪一层时——是代码本身慢?是网络丢包导致 TCP 重传?是内核调度导致线程排队?是磁盘 I/O 饱和?这些问题 APM 探针看不见,因为发生在它的视野之外。eBPF 就是来补这块盲区的。

两者不是二选一——它们是一个上一下的组合拳。APM 探针告诉你"哪个业务环节出问题",eBPF 告诉你"系统底层到底卡在什么地方"。他妈的,成年人不做选择,两个都要。


写在最后:你不需要记住所有东西

这篇文章你已经读到这儿了。老实说,我不期望你记住上面提到的每一个工具名、每一个术语、每一个 PromQL 语法。

但我希望你记住一件事。

当你的系统出问题时,你最匮乏的不是数据、不是工具、不是人力。你最匮乏的是时间——从"出问题"到"定位到根因"的时间。这个时间的长度,几乎完全取决于你的系统在被观测时的"可读性"。

一个可读的系统是什么样子的?你不需要提前知道所有答案,但你有一个清晰的提问路径。你从一个高层次的异常信号出发——"下单变慢了"——然后你能逐层下钻:是哪个服务慢了?这个服务在等谁?是 CPU、内存、磁盘还是网络的问题?是代码逻辑的问题还是依赖的问题?你能顺着 Trace、日志、火焰图和 USE 指标一路走到底,每一步都有数据在说"往这看"。

这就是 APM 给你的东西。

它不是冰冷的仪表盘。它是一套让你在黑暗中还能摸到方向的热感系统。

当你的 PagerDuty 响的时候,你不再是瞎子。你有地图。你知道路该怎么走。你知道该先看谁。

卧槽,这就够了。

从一个问题到另一个问题

如果你正在考虑引入 APM,或者正计划升级现有的监控体系,我想给你最后一个实用建议:从小做起,不要一上来就想全覆盖。

很多团队在第一次接触 APM 时,容易陷入一种"现在就把所有东西都接上"的冲动——所有的微服务全部接入 OpenTelemetry SDK,所有的告警规则全部配好,所有的 Grafana 面板全部建好——然后发现整个团队被这个工作量压得喘不过气,最后放弃。

我的建议是这样的:

第一步,先接入一个服务。就一个。把它的四种核心监控做好:服务指标(请求量、错误率、响应时间)、错误追踪(带 Trace ID 的上下文错误)、分布式追踪(Trace + Span 的完整瀑布图)、依赖监控(拓扑图和基线)。把一个服务做到"完全可观测",你会在这个过程中发现你的日志格式到底差在哪里、你的错误处理到底缺了什么上下文、你的依赖调用到底有多少"隐式依赖"是之前没意识到的。

第二步,从这个服务出发,扩展到它直接调用的下游。不需要一下子把所有 30 个服务都接上。每接一个下游,你就多了一截 Trace 的延伸。当你的 Trace 能从网关贯穿到数据库时,你凌晨三点的问题排查时间会从二十分钟缩短到两分钟——不是因为工具变厉害了,而是因为你的认知覆盖率提高了。你不再需要猜"中间那个环节发生了什么"。

第三步,建立告警和 SLO。前面两步都是在"看",这一步是在"被通知"。不是等你主动去查,而是系统在问题刚出现苗头时主动告诉你。定义你的 SLO(服务水平目标)——比如"订单接口的 Apdex 必须保持在 0.95 以上"——然后基于这个 SLO 配置告警。不是基于 CPU 或内存的静态阈值,而是基于用户体验是否达标。

第三步是最难的,因为它逼着你回答一个问题:你的用户到底在乎什么?你的 Apdex 阈值设多少是对的?你的错误预算是多少?这些问题没有标准答案,只有你和你的团队在反复摸索中建立的共识。

但这一步也是最值得做的。因为当你在凌晨三点被叫起来的时候,你不会收到一条"CPU 超过 80%"的消息——这种东西你白天看见了也不会理。你会收到一条"订单接口 Apdex 在过去 15 分钟里从 0.95 跌到了 0.78,影响用户比例 32%"的消息。这才是值得你从床上爬起来的告警。


最后最后

我写这篇文章的时候,一直在想一个问题:为什么监控这个话题总是让人犯困?

后来我想明白了。大部分监控文章都在讲"怎么做"——怎么配 Prometheus、怎么画 Grafana 面板、怎么写 PromQL。这些是说明书,不是文章。

我想写的是"为什么"——为什么你需要这些工具,为什么你需要这种思维方式。

当你的系统从 3 个服务长到 30 个的时候,你最需要的不是更多的监控面板。你最需要的是换一种看世界的方式。从"盯着每个服务"变成"盯着每条请求的旅程"。从"我是盲人我在摸象"变成"我有追踪链,我有上下文,我有地图"。

这是一种认知升级。比工具更重要。

我把它分享给你,是因为我相信每一个在后半夜被叫起来过的工程师,都值得拥有一条更清晰的路。

走到这条路的终点,你还是要爬起来。但你知道该往哪儿走。

这就够了。

这篇文章从一开始就没有打算教你配 Prometheus 的 YAML 或写 PromQL 的语法。这些你在官方文档和模板里五分钟就能找到。我想告诉你的只有一件事:当你凌晨三点被叫起来的时候,你需要的不是十个互相不说话的监控面板,而是一套能让你在六十秒内看到完整故事的工具链和思维框架。愿你永远不会再盯着四个 top free df netstat 窗口猜测世界的模样。

读者来信

0/1000

暂无来信,期待你的分享。