常见指标的合理范围 · 多少算正常

「P99 多少算正常?」「CPU 到多少该慌?」这一章给出一批可作起点的参考锚点——延迟的数量级直觉、可用性几个九的真实含义、各类资源的经验区间、直方图桶推荐,以及最重要的:怎么用 SLO 和历史基线定出你自己的数字。

作者 David更新于 第 18 篇(共 52 篇)

常见指标的合理范围 · 多少算正常

前面几章讲了该测什么、怎么定义。现在是最实际的问题:多少算正常? P99 是 200ms 好还是坏?CPU 到 70% 该不该慌?

先把丑话说前面。

没有普适数字,但有参考锚点

任何声称“P99 应该小于 X 毫秒”的说法都是耍流氓——正确的阈值只能从你的 SLO 倒推。 同样是 200ms 的 P99:

  • 对广告竞价接口:灾难(超过 100ms 就出局了)。
  • 对用户下单接口:很好。
  • 对月度报表生成:好得离谱(它跑 5 分钟都正常)。

所以本章的数字是“锚点”不是“标准”:它们的用途是——当你完全没有概念时,给你一个数量级的起点;以及当你的数字离锚点很远时,提醒你“这里可能有问题,值得看一眼”。

真正的答案在本章最后一节:从 SLO 倒推 + 看你自己的历史基线。

延迟:先建立数量级直觉

在谈“多少毫秒”之前,你得知道各种操作本身要花多久。这些数字(数量级,现代硬件)是一切延迟判断的基础:

操作数量级记忆锚点
L1 缓存访问~1 ns—
内存访问~100 ns
本地 SSD 随机读~100 μs0.1 ms
同机房网络往返~0.5 ms
本地 SSD 顺序读 1MB~1 ms
数据库简单查询(命中索引,同机房)1~10 ms★
Redis 操作(同机房)~1 ms★
同城跨机房往返~1-2 ms
跨大区往返(如国内 ↔ 美西)~150-200 ms★ 物理极限,光速决定
磁盘寻道(机械盘)~10 ms还在用的话……

这张表的价值是让你能估算**“这个接口的延迟合理吗”。** 举例:一个接口做 3 次 DB 查询 + 2 次 Redis + 一次下游调用:

3 × 5ms(DB)+ 2 × 1ms(Redis)+ 1 × 20ms(下游)+ 自身计算 ≈ 40ms

如果实测 P50 是 45ms —— 合理,符合预期。 如果实测 P50 是 400ms —— 有问题,多出来的 350ms 花在哪了? 这时你就有了明确的追问方向(是不是 N+1 查询?是不是没命中索引?是不是有锁等待?)。

“跨大区 ~150ms 是光速决定的物理极限”这条尤其要记住:如果你的服务在北京、用户在美国,再怎么优化也不可能低于 ~150ms。这时唯一的解法是把服务部署到用户附近(CDN / 多区部署),而不是继续优化代码。认清物理极限,能省下大量做无用功的时间。

Web 服务延迟:常见的目标区间

场景P50 参考P99 参考说明
缓存命中的读接口< 10 ms< 50 ms基本就是网络 + 序列化
普通读接口(几次 DB)10~50 ms100~300 ms最常见的区间
写接口(事务)20~100 ms200~500 ms写比读慢是正常的
复杂查询 / 聚合100~500 ms1~3 s考虑异步化
前端页面首屏—< 2.5 s对应 Core Web Vitals 的 LCP “良好”标准
内部 RPC1~10 ms< 50 ms同机房

一条实用经验:P99 / P50 的比值。

  • 比值 < 5 —— 健康。长尾可控。
  • 比值 5~10 —— 有长尾,值得看看(GC?锁?慢查询?冷缓存?)。
  • 比值 > 10 —— 一定有问题。典型元凶:GC 停顿、锁竞争、某个坏实例、缓存穿透、连接池排队。

这个比值比绝对值更有诊断价值,因为它自动适应你的业务:不管你的 P50 是 5ms 还是 500ms,尾部不该比典型情况差一个数量级以上。上一章说 P50/P99 差距本身就是信号,这就是量化版本。

可用性:几个九到底是多久

错误率的锚点,最好用“允许的停机时间”来理解——因为这是能被感知的:

可用性错误率每月允许每年允许现实感
99%(两个九)1%7.2 小时3.65 天内部工具
99.9%(三个九)0.1%43.2 分钟8.76 小时多数在线服务的起点
99.95%0.05%21.6 分钟4.38 小时重要业务
99.99%(四个九)0.01%4.32 分钟52.6 分钟核心交易,很贵
99.999%(五个九)0.001%26 秒5.26 分钟电信级,极贵

看着这张表,重新理解“我们要做到 5 个九”这句话:每月只允许 26 秒的故障。 26 秒意味着:

  • 人肯定来不及处理 —— 从告警响到人看手机就不止 26 秒。所以五个九必须靠自动 failover,不能靠人。
  • 一次发布出问题就超标 —— 你的回滚必须在秒级完成。
  • 依赖也必须是五个九 —— 而且如果你串行依赖 3 个五个九的服务,你自己的上限就掉到 ~99.997% 了(可用性会相乘)。

每多一个九,成本大致是指数级增长的。 所以选可用性目标是个商业决策而非技术决策:多出来的那个九,值得你付出的代价吗? 大多数业务的答案是“99.9% 就够了”——第 5 部分讲 SLO 时会展开这个思路,以及“故意留出错误预算”这个反直觉但极其重要的观念。

依赖的乘法效应也要记住:你的可用性 ≤ 所有串行依赖的可用性之积。三个 99.9% 的串行依赖 → 你最高只能做到 99.7%。想提高,要么减少依赖,要么让依赖并行/可降级。

资源:各类经验区间

记住上一章的教训:看饱和度,不是使用率。下面的使用率数字只是“该去看饱和度了”的提示线,不是告警线。

资源舒适该关注危险真正该看的(饱和度)
CPU< 60%60~80%> 80% 持续load/核数 > 1、PSI > 0
内存< 70%70~85%> 85%换页 > 0、OOM 次数
磁盘空间< 70%70~85%> 85%predict_linear 预测何时满 ★
磁盘 IO< 50%50~80%> 80%await / 队列深度
连接池< 70%70~90%> 90%等待连接的线程数 / 等待时长 ★
线程池队列~0持续 > 0增长队列长度本身就是饱和度
JVM GC< 1% 时间1~5%> 5%停顿时长 P99
Kafka consumer lag~0 或平稳持续增长 ★加速增长lag 的一阶导数

几条要点:

  • 磁盘空间必须用预测,不用静态阈值 —— 上一章讲过:predict_linear(node_filesystem_avail_bytes[6h], 4*3600) < 0。85% 在 100GB 盘上还剩 15GB(可能几小时),在 10TB 盘上还剩 1.5TB(可能几周)——同一个百分比,紧迫性差几个数量级。
  • Kafka lag 看趋势不看绝对值 —— lag = 10 万但在下降,是健康的(在追上来);lag = 1000 但持续增长,是事故(消费能力 < 生产速度,迟早雪崩)。要告警的是“持续增长”,即 deriv(lag[10m]) > 0 持续一段时间。
  • 内存 > 85% 在容器里尤其危险 —— 容器内存超限是直接 OOM kill,没有优雅降级。而且 Go/Java 这类带 GC 的运行时,接近上限时 GC 会疯狂空转(CPU 飙升但内存降不下来),表现为“CPU 100% 但没干活”。
  • 线程池队列的舒适值是 ~0 —— 注意这和别的不一样:队列里有东西 = 已经在排队 = 已经在产生延迟。持续 > 0 就该扩容或限流了。

直方图桶推荐

第 1 部分说过桶选不好 histogram 就废了。给几套可直接用的:

// ── Web API 延迟(秒)—— 覆盖 5ms ~ 10s,在 100ms~1s 加密
[]float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}

// ── 内部 RPC / 缓存(秒)—— 更快,重心左移
[]float64{0.0005, 0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5}

// ── 慢作业 / 批处理(秒)—— 重心右移
[]float64{1, 5, 10, 30, 60, 300, 600, 1800, 3600}

// ── 响应体大小(字节)
[]float64{100, 1000, 10000, 100000, 1e6, 1e7}

最重要的一条:桶必须在你的 SLO 阈值附近加密。

如果你的 SLO 是“P99 < 300ms”,而你的桶是 [..., 0.25, 0.5, ...],那么当 P99 落在 250ms 和 500ms 之间时,histogram_quantile 只能线性插值猜一个数——它可能告诉你 P99 = 310ms,但真实值可能是 260ms 或 490ms。你根本无法判断 SLO 有没有破。

正确做法是在 300ms 附近加桶:

// SLO = P99 < 300ms 时的桶设计
[]float64{0.05, 0.1, 0.2, 0.25, 0.3, 0.35, 0.4, 0.5, 1, 2.5, 5}
//                          ↑ SLO 阈值前后加密

另一个约束:桶数 × 其他标签 = 基数。 每加一个桶,这个 histogram 的 series 数就 +(其他标签组合数)。所以别撒胡椒面式地加 30 个桶——10~15 个,重心放在你关心的区间,足够了。

或者直接上 native histograms(Prometheus 3.x):桶自动指数生成、精度高、基数还低——它就是来终结这个纠结的。

真正的答案:从 SLO 倒推 + 看历史基线

上面所有数字都只是起点。你自己的阈值,应该这么定出来:

① 从 SLO 倒推(自上而下)

业务要求:"下单成功率 99.9%,P99 < 500ms"
  ↓
错误率阈值 = 0.1%
延迟阈值   = 500ms(且桶要在 500ms 附近加密!)
  ↓
再往下分解到每个依赖:如果下单串行调 3 个服务,
每个的 P99 预算 ≈ 500ms / 3 ≈ 160ms(还要留余量)

这是唯一“正确”的方法——阈值来自业务承诺,不是拍脑袋。

② 看历史基线(自下而上)

# 过去 7 天,这个指标的 P99 通常是多少?
quantile_over_time(0.99, 
  histogram_quantile(0.99, sum by(le) (rate(http_request_duration_seconds_bucket[5m])))[7d:5m]
)

先测量,再定阈值。 很多团队反过来:先拍一个阈值,然后天天被误报吵,最后把告警静音。正确顺序是:上线指标 → 观察一两周 → 看真实分布 → 再定阈值。

③ 两者对不上时,那才是真问题

  • 如果历史基线远好于 SLO → 恭喜,你有错误预算可以花(可以更激进地发布)。
  • 如果历史基线达不到 SLO → 别设一个天天报警的阈值,那只会制造告警疲劳。要么去优化系统,要么去和业务方重新谈 SLO。告警不能解决“系统本来就不达标”的问题。

小结与下一部分

没有普适数字,只有参考锚点——同样 200ms 的 P99,对广告竞价是灾难、对报表是好得离谱。本章给的锚点:延迟数量级直觉(DB 查询 1~10ms、Redis ~1ms、跨大区 ~150ms 是光速物理极限——认清它能省下大量无用功),用它估算接口延迟是否合理;P99/P50 比值(< 5 健康、> 10 一定有长尾问题)比绝对值更有诊断力。可用性用停机时间理解(三个九 = 每月 43 分钟、五个九 = 每月 26 秒 → 人来不及,必须自动化);注意串行依赖的可用性会相乘。资源看饱和度不看使用率(磁盘必须用 predict_linear 预测、Kafka lag 看趋势不看绝对值、线程池队列舒适值是 ~0)。直方图桶必须在 SLO 阈值附近加密——否则你根本判断不了 SLO 有没有破。真正的答案:① 从 SLO 倒推(阈值来自业务承诺)+ ② 看历史基线(先测量再定阈值,别反过来);两者对不上时才是真问题——基线达不到 SLO 时该去优化或重谈 SLO,而不是设一个天天误报的阈值。

第 2 部分讲完了“该定义什么指标、怎么定义、多少算正常”。接下来回到源码:这些指标存进 TSDB 后,是怎么被存得下、查得快的?👉 第 3 部分 · TSDB:时序存储内核。

参考资料

  • [1] Jeff Dean「Latency Numbers Every Programmer Should Know」(数量级锚点的经典来源)。
  • [2] Google《SRE Book》第 3-4 章(风险与 SLO)、Web Vitals(LCP 标准)。
  • [3] 关联USE 饱和度、直方图桶设计、SLO 与燃烧率。

本页目录