在做性能分析时,工程师常常只盯着一个指标,比如 CPU 使用率。但 CPU 使用率只是系统全貌中的一个切面,它无法独立解释「为什么 QPS 突然下降?」、「为什么机器明明很空闲却响应变慢?」这些根本问题。
要真正看懂一个高并发系统,我们至少需要同时观察 CPU 使用率(usage) 、平均负载(load average) 和 上下文切换(context switch) 三个维度。它们分别反映不同层次的系统行为:
- CPU 使用率:CPU 真的在做什么
- 平均负载:有多少进程在争夺 CPU 或等待资源
- 上下文切换:调度器在为多任务付出多少额外代价
三者组合起来,形成一个完整的 CPU 画像(CPU Profile)。借助它,我们能迅速确定瓶颈到底在计算、IO、调度、锁竞争,还是资源争抢。
一、为什么必须把三个指标放到一起分析?
想象一台线上机器突然出现现象:
- QPS 下降
- 延迟上升
- top 看 CPU 使用率只有 25%
这是常见的“假空闲”状态。许多工程师看到 CPU 偏低,就以为 CPU 没问题。但真实情况往往是:
- load average 高
- cs(上下文切换)高
- iowait 高
- run queue(r)高
- 大量线程处于 D 状态
单靠 CPU 使用率,你根本判断不出来瓶颈在哪。
为了让你能建立一套完整的诊断思维,我们先用一个系统性的 CPU 三维模型做 baseline。
二、CPU 性能三维模型(核心图)
┌────────────────────────┐
│ CPU 使用率 (usage) │
│ user / system / iowait │
└─────────┬──────────────┘
│
▼
┌──────────────────────┐
│ 平均负载 (load avg) │
│ R + D 状态的队列长度 │
└─────────┬────────────┘
│
▼
┌────────────────────────────────────┐
│ 上下文切换 (context switch) │
│ voluntary / involuntary / interrupt│
└────────────────────────────────────┘
三者体现了不同层次的 CPU 运行过程:
- usage:CPU 花时间在哪
- load average:有多少任务在争夺 CPU 或资源
- context switch:调度器为了切换任务付出多少成本
将三者组合,可以推导系统的真实状态。
三、三者组合后的 12 种典型 CPU 异常画像
下面是实践中最常用的 CPU 诊断表。
画像 1:CPU 高,Load 高,CS 左右轻微上升
含义:系统繁忙但健康
像高速公路车很多但流动顺畅,说明 Nginx/Netty/Java 线程池在正常工作。
画像 2:CPU 高,Load 高,CS 极高(>50000/s)
含义:线程竞争或调度压力大
典型场景:
- synchronized 热点锁
- 线程池配置不当
- GC stop-the-world 高频
画像 3:CPU 中等,Load 高,iowait 高
含义:IO 阻塞导致的“假高负载”,而不是 CPU 真忙
数据库、磁盘、日志常导致这种情况。
画像 4:CPU 低,Load 高,D 状态多
含义:盘或远端服务严重阻塞
系统像医院里排队病人太多,根本轮不到你看病。
画像 5:CPU 一般,Load 低,CS 高
含义:大量短生命周期线程
一些 Java 项目每次任务都 new Thread,非常常见。
画像 6:Load 正常,但 %system 极高
含义:大量系统调用、网络包处理
如大规模 epoll 操作(Nginx/LVS 流量高峰)。
(更多不逐一列举,你已经能看懂图谱了)
四、一个完整实战案例:Nginx + wrk 压测如何呈现三维 CPU 行为
压测配置:
wrk -t8 -c1000 -d30s http://localhost/cpu
此接口只做一件事:
return 200 "OK";
压测结果的 CPU 画像往往如下:
1. CPU 使用率
- %system 高(大量 epoll + accept)
- %user 中等
- %iowait 基本为 0
这意味着是典型的“网络 IO 主导型服务器”。
2. 平均负载
load average 接近 CPU 核心数
例如:
load average: 7.9 8 核机器
说明:
- 高并发导致大量请求进入队列
- 这是健康的 “高负载高吞吐” 状态
3. 上下文切换
一般会显著上升:
cs: 50000~80000/s
因为:
- Nginx 是多 worker
- 每个连接会多次触发 epoll_wait、sendfile
- 调度器需要频繁在 workers 间切换
这三者组合,就是一个“典型的网络服务器在高压力下的健康画像”。
五、Java 服务侧的真实场景:一次 CPU 假瓶颈 的排查
假设你在线上遇到如下现象:
CPU 使用率:20%
Load average:15
cs:60000/s
大量线程处于 RUNNABLE
能判断什么?
- CPU 不忙(usage 不高)
- 但 load average 很高(大量线程在争抢 CPU)
- cs 高说明调度器压力大
- 结合 RUNNABLE,可推导:
线程数过多 + 调度压力导致吞吐下降
进一步你可能看到线程池配置:
corePoolSize = 200
maxPoolSize = 2000
然后线程池爆炸,调度器被压垮。
解决方式:
- 限流
- 调整线程池
- 使用 Netty/RxJava/Spring WebFlux 等事件驱动模型(减少阻塞)
这是一个在大量 Java 高并发系统里反复发生的典型问题。
六、如何在实际系统里使用三维 CPU 模型?
你可以把这套模型嵌入系统监控:
CPU 使用率: %user, %system, %iowait, steal
平均负载: load1, load5, load15
上下文切换: cs, in, r, b
线程状态: R/D/T 线程数
调度延迟: run queue 延迟
应用指标: RT, QPS, 锁等待
分析顺序建议永远是:
使用率 → 平均负载 → 上下文切换 → 线程状态 → 应用状态
因为:
- 使用率告诉你 CPU 在干什么
- 平均负载告诉你是否有人排队
- 上下文切换告诉你 CPU 是否被调度器拖累
- 线程状态告诉你应用是否异常
- QPS/RT 才告诉你客户体验