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

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

- 作者：David（道雾轩）
- 专栏：指标采集与观测（https://daiw.org/manual/observability.md）
- 最后更新：2026-07-15
- 原文：https://daiw.org/manual/observability/metric-ranges
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

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

前面几章讲了[该测什么](https://daiw.org/manual/observability/metrics-methodology)、[怎么定义](https://daiw.org/manual/observability/defining-metrics)。现在是最实际的问题：**多少算正常？** P99 是 200ms 好还是坏？CPU 到 70% 该不该慌？

先把丑话说前面。

## 没有普适数字，但有参考锚点

<Callout type="warn">
  **任何声称“P99 应该小于 X 毫秒”的说法都是耍流氓——**正确的阈值只能从你的 SLO 倒推**。** 同样是 200ms 的 P99：

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

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

  真正的答案在[本章最后一节](https://daiw.org/manual/observability/metric-ranges)：**从 SLO 倒推 + 看你自己的历史基线**。
</Callout>

## 延迟：先建立数量级直觉

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

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

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

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

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

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

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

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

<Callout type="info">
  **一条实用经验：**P99 / P50 的比值**。**

  - **比值 < 5** —— 健康。长尾可控。
  - **比值 5~10** —— 有长尾，值得看看（GC？锁？慢查询？冷缓存？）。
  - **比值 > 10** —— **一定有问题**。典型元凶：GC 停顿、锁竞争、某个坏实例、缓存穿透、连接池排队。

  这个比值比绝对值更有诊断价值，因为它**自动适应你的业务**：不管你的 P50 是 5ms 还是 500ms，**尾部不该比典型情况差一个数量级以上**。[上一章说 P50/P99 差距本身就是信号](https://daiw.org/manual/observability/red-method)，这就是量化版本。
</Callout>

## 可用性：几个九到底是多久

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

| 可用性 | 错误率 | 每月允许 | 每年允许 | 现实感 |
| --- | --- | --- | --- | --- |
| **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 分钟 | 电信级，**极贵** |

<Callout type="warn">
  **看着这张表，重新理解“我们要做到 5 个九”这句话：**每月只允许 26 秒的故障**。** 26 秒意味着：

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

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

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

## 资源：各类经验区间

<Callout type="info">
  **记住[上一章的教训](https://daiw.org/manual/observability/use-method)：**看饱和度，不是使用率**。下面的使用率数字只是“该去看饱和度了”的提示线，不是告警线。**

  | 资源 | 舒适 | 该关注 | 危险 | 真正该看的（饱和度） |
  | --- | --- | --- | --- | --- |
  | **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 的一阶导数** |

  **几条要点：**

  - **磁盘空间必须用预测，不用静态阈值** —— [上一章讲过](https://daiw.org/manual/observability/four-golden-signals)：`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 就该扩容或限流了。
</Callout>

## 直方图桶推荐

[第 1 部分说过桶选不好 histogram 就废了](https://daiw.org/manual/observability/metric-types)。给几套可直接用的：

```go
// ── 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}
```

<Callout type="warn">
  **最重要的一条：**桶必须在你的 SLO 阈值附近加密**。**

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

  正确做法是在 300ms 附近加桶：
  ```go
  // 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 阈值前后加密
  ```

  **另一个约束：桶数 × 其他标签 = [基数](https://daiw.org/manual/observability/defining-metrics)。** 每加一个桶，这个 histogram 的 series 数就 +（其他标签组合数）。所以**别撒胡椒面式地加 30 个桶**——10~15 个，重心放在你关心的区间，足够了。

  **或者直接上 [native histograms](https://daiw.org/manual/observability/three-pillars)**（Prometheus 3.x）：桶自动指数生成、精度高、基数还低——它就是来终结这个纠结的。
</Callout>

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

<Callout type="info">
  **上面所有数字都只是起点。你自己的阈值，应该这么定出来：**

  **① 从 SLO 倒推（自上而下）**
  ```
  业务要求:"下单成功率 99.9%,P99 < 500ms"
    ↓
  错误率阈值 = 0.1%
  延迟阈值   = 500ms(且桶要在 500ms 附近加密!)
    ↓
  再往下分解到每个依赖:如果下单串行调 3 个服务,
  每个的 P99 预算 ≈ 500ms / 3 ≈ 160ms(还要留余量)
  ```
  **这是唯一“正确”的方法**——阈值来自业务承诺，不是拍脑袋。

  **② 看历史基线（自下而上）**
  ```text
  # 过去 7 天,这个指标的 P99 通常是多少?
  quantile_over_time(0.99, 
    histogram_quantile(0.99, sum by(le) (rate(http_request_duration_seconds_bucket[5m])))[7d:5m]
  )
  ```
  **先测量，再定阈值。** 很多团队反过来：先拍一个阈值，然后天天被误报吵，最后把告警静音。**正确顺序是：上线指标 → 观察一两周 → 看真实分布 → 再定阈值。**

  **③ 两者对不上时，那才是真问题**
  - 如果**历史基线远好于 SLO** → 恭喜，你有[错误预算](https://daiw.org/manual/observability/slo-burn-rate)可以花（可以更激进地发布）。
  - 如果**历史基线达不到 SLO** → **别设一个天天报警的阈值**，那只会制造[告警疲劳](https://daiw.org/manual/observability/alerting-philosophy)。要么去优化系统，要么去和业务方重新谈 SLO。**告警不能解决“系统本来就不达标”的问题。**
</Callout>

## 小结与下一部分

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

第 2 部分讲完了“**该定义什么指标、怎么定义、多少算正常**”。接下来回到源码：这些指标[存进 TSDB](https://daiw.org/manual/observability/data-journey) 后，是怎么被**存得下、查得快**的？👉 第 3 部分 · TSDB：时序存储内核。

## 参考资料

- [1] Jeff Dean「Latency Numbers Every Programmer Should Know」（数量级锚点的经典来源）。
- [2] Google《SRE Book》第 3-4 章（风险与 SLO）、Web Vitals（LCP 标准）。
- [3] 关联[USE 饱和度](https://daiw.org/manual/observability/use-method)、[直方图桶设计](https://daiw.org/manual/observability/metric-types)、[SLO 与燃烧率](https://daiw.org/manual/observability/slo-burn-rate)。
