先说结论,三条:
- 现象 :「全部指标正常」这句话的真实含义只有一句------我盯着的那几个数,在这一刻、按我选的这个聚合方式、没出事。它不等于系统没事。
- 根因 :「全绿却抖」不是一个问题,是三个不同的问题混在同一块绿色仪表盘后面:没埋点的地方 (覆盖缺口)、埋了点但被聚合抹平的地方 (维度坍缩)、点了也测不对的地方(语义不等价 + 局部自治)。
- 方向:加埋点只能治第一种。第二种要下钻维度,第三种要换信号源(端到端断言 + 旁路观测)。三种不加区分,你的第一反应就一定是错的------继续加指标,绿色面积更大,盲区一寸没少。
一、先把现场压到 200 字
老赵四十七,跑长途十九年。九十码方向盘轻轻摆,副驾杯架里的水杯"哒哒"响,降到八十就没了。空车不显,重车明显。
4S 店插电脑:"无故障码。"动平衡、四轮定位、换两条前胎,一千四花完,上高速还是响。
国道边一个没电脑的修车铺。老吴拿手电照一遍,摸右前轮慢慢转,一根半米长的铁杆顶在羊角上、一头贴着耳朵听,二十分钟:「右边半轴里那个球笼,滚珠槽磨出台阶了。跑直路不显,一带方向就顶一下。」
「电脑怎么不报?」
「那个球笼上没有传感器。它坏了,也没人替它说话。」
一百七换掉,到九十码,水杯不响了。
二、素材里这句话是错的,得先拆掉
「它坏了,也没人替它说话」很动人,但如果你把这句直接搬到线上,会得出一个错误的第一反应:全绿却抖 = 我埋点不够,加埋点。
我见过太多团队就这么干:告警不响,那就再加二十个指标。仪表盘从两屏变六屏,全绿的面积更大了,问题一次没少。
因为「全绿却抖」在软件系统里有三个完全不同的成因,只有第一个是"没装传感器"。
用素材自己的话拆一遍:车上零件数以万计、传感器几百个,覆盖率是百分之一量级------这是第一种看不见。可就算你把覆盖率做到 100%,还有两件事在等着你:
- 传感器确实装了,但它报上来的是一个被平均过的数。一个传感器的读数是事实,一万个传感器的平均值是一种意见。
- 传感器测的不是你要的那个东西 。水温正常不代表没拉缸;
/healthz返回 200 不代表业务是对的。
所以严格版本是这一句:
「全绿」不是在描述系统,是在描述你的观测装置。 你要判断的从来不是"绿不绿",而是"这套观测装置在多大范围、按什么聚合、测的是不是我要的那个量"。
三种,下面一条一条拆,每种给一条能直接跑的量化判据。
三、三种「看不见」
类型一:覆盖缺口------真的没装传感器
现象:故障发生在没有任何埋点的路径上。没打点的老代码、第三方依赖内部、内核/网络层、异步补偿任务、定时任务、迁移脚本、运维手动执行的命令。
为什么是必然不是偶然 :埋点跟着业务代码走,业务代码跟着需求走。没需求的地方就没有埋点,而故障不看需求表。更扎心的是:写代码的人不会给自己的疏漏打点。
量化判据(这条今天就能跑):同一时间窗,比两个数------
- 分子:服务端埋点里看到的请求数
- 分母:网关 / sidecar / 内核层(eBPF、
tcpdump、ss)看到的请求数
text
埋点请求数 / 网关请求数 < 0.98 → 2% 以上的路径在盲区里
这不是"大概",是减法。差额部分就是你的盲区清单,而且这个差额通常稳定存在------它不是抖动,是结构。
类型二:维度坍缩------装了传感器,但被聚合抹平
这类最容易骗人,因为乱不是发生在数据里,是发生在你的聚合方式里。三个典型:
(a)平均值吃掉长尾。 10 个租户均分流量,其中 1 个租户(跑大报表)的每个请求都是 4 秒,其余 9 个租户 50ms:
| 指标 | 值 | 读者会怎么理解 |
|---|---|---|
| P50 | 50ms | 「大部分很快」 |
| 平均 | 445ms | 「还行,在 SLA 500ms 以内,绿的」 |
| P99 | 4000ms | 「有 1% 慢一点」 |
三个数讲三个故事。而仪表盘默认画的是哪一个?平均值 。真实情况是:10% 的用户,每一次请求都在等 4 秒。他们的体验在平均值里被稀释了 8.9 倍。
(b)全局分母稀释。 灰度期最典型:新版本占 1% 流量,错误率已经 10%,全局错误率只涨 0.1 个百分点------正好卡在告警阈值下面。公式简单到残忍:
全局错误率 ≈ Σ(流量占比 × 分组错误率)
某组流量占比 p,该组错误率变化 Δe,对全局的贡献只有 p × Δe 。p = 1% 时,新版本彻底烂掉(Δe = 10%)也只能让全局动 0.1%。灰度越小,越看不见。 而新版本烂掉,恰恰是你最该在看到的那件事。
(c)时间窗错位。 5 分钟粒度的指标和 1 分钟粒度的日志对不上,你会在错误的窗口里找错误的原因,然后得出"指标正常、日志报错、日志系统有问题"这种结论,白查两天。
量化判据 :对每个关键指标,每小时自动跑一次全维度 group by(instance / version / region / tenant / endpoint),比较最差分组和全局:
sql
-- 伪代码,按你的 TSDB 改写。目的:找出被全局数字盖住的那一组
SELECT metric, group_key,
quantile(0.99)(value) AS group_p99,
sum(value) AS group_traffic
FROM metrics_1m
WHERE ts > now() - interval '1 hour'
GROUP BY metric, group_key
HAVING quantile(0.99)(value) > 3 * global_p99(metric)
- 最差分组 P99 ÷ 全局 P99 > 3 → 聚合已失真,这块仪表盘不能再当结论用
- 最差分组错误率 ÷ 全局错误率 > 3 ,且该组流量占比 < 5% → 全局指标对这组结构性失明
类型三:语义不等价 + 局部自治------测了,但测的不是你要的
(a)代理变量不等于结果。 四黄金信号(延迟 / 流量 / 错误 / 饱和度)测的是"系统在动",不是"业务对了"。返回值 200、DB 连接正常、CPU 30%------而 ETL 三天前就停了,页面显示的是三天前的数据,所有实例 healthy。仪器全准,量错了东西。
(b)局部自己做了决定,账记在别人头上。 这正是素材里那句「有些局部,是自己做决定的」在软件里的样子:
- 重试 :三层各重试 3 次,最坏 27 倍 放大。上游指标只记了 1 次失败,下游被打了 27 次。重试不是不上报,是报在别人的账上。
- 连接池:自己回收、自己重建、自己预热,你只看到"连接数正常"。
- GC、异步补偿、消息重投、sidecar 健康剔除:都在主链路埋点之外自己决策,做对了不告诉你,做错了也不走你的错误率。
量化判据:
text
下游入口 QPS / 上游请求数 > 1 → 存在放大
> 3 → 必须查是哪一层的重试在叠
数据新鲜度 = now - 最新一条数据的业务时间戳
数据新鲜度 > 预期更新周期 × 2 → 告警(这一条能抓住绝大多数"全绿但用户在吃旧数据")
四、三条验法(每条都带证伪条件)
验法 A:两路对照------把耳朵贴到没装耳朵的地方
老吴不是比电脑聪明,他是把耳朵贴到了电脑没装耳朵的地方。工程上就是两套互不依赖的观测,对同一段时间:
- 内部:服务端埋点(你已有的 metrics / trace)
- 外部:网关日志、eBPF / syscall 层计数、客户端 RUM 或合成探针
记录什么 :同一 5 分钟窗内的请求数、P99、错误数,两组各一份。 判据 :请求数差 > 2%,或 P99 差 > 20% → 两组里至少有一组在说谎。先查清是哪组,再谈"系统正常"。 证伪:如果两路在连续 24 小时、跨一次发布前后都对得上(差 < 1%),那你的覆盖缺口基本不存在,这条验法对你无效,可以划掉。
验法 B:维度下钻扫描------治聚合坍缩
别靠人肉看板。写个每小时跑的脚本,对每个关键指标拉全维度分组,算"最差分组 / 全局"的比值,超阈值就出一条报告(先别做成告警,看一周再决定要不要吵醒人)。
记录什么 :指标名、分组维度、最差分组值、全局值、比值、该组流量占比。 判据 :比值 > 3 且连续 3 个周期成立 → 这条指标的仪表盘已经失真。 证伪:下钻一周后所有比值都 < 1.5 → 你的流量结构和聚合方式本来就是安全的,频率调低或停掉。
验法 C:端到端业务断言------治语义不等价
把"健康"从形容词改成断言。合成探针不只请求首页拿 200,而是走完整业务链路,检查结果对不对:
python
# 健康 ≠ 200。三个断言,缺一个都只是"它在动"
assert resp.status_code == 200
assert resp.json()["id"] == test_id # 结果对
assert now() - resp.json()["data_time"] < 2 * PERIOD # 数据新
记录什么 :HTTP 2xx 率、业务断言通过率、数据新鲜度滞后。 判据 :2xx 率 − 业务断言通过率 > 0.1% → 存在语义缺口,你的 2xx 在替失败背书。 证伪:两条曲线长期重合(差 < 0.01%)且数据滞后稳定在预期内 → 你的健康检查已经是语义级的,这条对你无效。
五、工程落地:三件能直接塞进流程的事(从弱到强)
第一件:证据锚定(最弱,但今天就能做) 告警和工单里禁止只写"指标正常"。要求贴三样:查询语句、时间窗、带维度的原始时间序列截图。能被复查的结论才叫结论,不能复查的只是感觉。几乎零成本,能挡掉一半"我觉得没问题"。
第二件:可执行验证 把"正常"写成代码:合成探针带业务断言、数据新鲜度断言、对账任务(订单数 vs 支付数 vs 发货数,差值超阈值即报)。凡是能用一次查询或一次请求判定的健康,都不要留给人的判断。
第三件:独立校验器(最强) 监控不能是被监控系统的一部分。旁路一套不受主链路影响的观测:网关侧日志、内核层计数、客户端真实用户监测。当主链路说"我很好"而旁路说"不对",信旁路。 顺带说一句:这和"让系统自己检查自己"是同一类结构问题------检查者和被检查者是同一个,结论必然偏,所以判据必须外置。
六、边界三条(这套说法什么时候不成立)
- 没有 SLO 的系统不值得做到这一步。 内部工具、低频后台,观测成本远超故障损失。日志兜底 + 人肉看,够了。
- 有一部分确实物理不可观测。 未导出的进程内部状态、被编译器优化掉的路径、加密流量里的业务语义------你只能说"在这一层看不见",不能反推"必有隐藏故障"。"测不到"不等于"有",把这句话反过来用就是阴谋论。
- 这套判断假设你有权改埋点和探针。 闭源 SaaS、第三方依赖只能用黑盒对照(验法 A 的外部那一路),结论强度下降一档,判据容差要放宽。
递刀口:如果你的系统已经按 tenant / version / instance 全维度拆分,已有端到端业务断言,还有一套独立旁路观测------那"全绿却抖"这套描述对你不成立,你尽管把它划掉。真到那一步,你用的一定是别的东西在发现问题。欢迎在评论区说说你用的是什么,我也想学。
七、最后一句
老赵说换完球笼那一刻不是高兴,是有点后怕------那个东西磨了多久,他不知道。他一直以为"电脑说没事"就是没事。
工程上对应的一句话是:"监控说没事"只等于"我的观测装置没报",不等于"系统没事"。 你能做的不是把窗口擦得更亮,是知道这扇窗朝着哪边、玻璃有多厚、窗外还有多少你没看的地方。
把「监控全绿」「看不见」「局部自治」这些平时说不清的东西说清楚,是结构认知 一直在做的事。上面这条推导,出自《仪表盘全绿,车还是在高速上抖了一下》,第一发现者:林小黑。完整推导见 gitee.com/samforce/st...。