常见指标的合理范围 · 多少算正常
「P99 多少算正常?」「CPU 到多少该慌?」这一章给出一批可作起点的参考锚点——延迟的数量级直觉、可用性几个九的真实含义、各类资源的经验区间、直方图桶推荐,以及最重要的:怎么用 SLO 和历史基线定出你自己的数字。
常见指标的合理范围 · 多少算正常
前面几章讲了该测什么、怎么定义。现在是最实际的问题:多少算正常? P99 是 200ms 好还是坏?CPU 到 70% 该不该慌?
先把丑话说前面。
没有普适数字,但有参考锚点
任何声称“P99 应该小于 X 毫秒”的说法都是耍流氓——正确的阈值只能从你的 SLO 倒推。 同样是 200ms 的 P99:
- 对广告竞价接口:灾难(超过 100ms 就出局了)。
- 对用户下单接口:很好。
- 对月度报表生成:好得离谱(它跑 5 分钟都正常)。
所以本章的数字是“锚点”不是“标准”:它们的用途是——当你完全没有概念时,给你一个数量级的起点;以及当你的数字离锚点很远时,提醒你“这里可能有问题,值得看一眼”。
真正的答案在本章最后一节:从 SLO 倒推 + 看你自己的历史基线。
延迟:先建立数量级直觉
在谈“多少毫秒”之前,你得知道各种操作本身要花多久。这些数字(数量级,现代硬件)是一切延迟判断的基础:
| 操作 | 数量级 | 记忆锚点 |
|---|---|---|
| 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 | 还在用的话…… |
这张表的价值是让你能估算**“这个接口的延迟合理吗”。** 举例:一个接口做 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 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 | 同机房 |
一条实用经验: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]
)先测量,再定阈值。 很多团队反过来:先拍一个阈值,然后天天被误报吵,最后把告警静音。正确顺序是:上线指标 → 观察一两周 → 看真实分布 → 再定阈值。
③ 两者对不上时,那才是真问题
小结与下一部分
没有普适数字,只有参考锚点——同样 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:时序存储内核。