CPU 不高、负载不高,可服务器就是「一卡一卡」的
有一种服务器故障特别折磨人:top 里 CPU 使用率只有百分之二三十,load average 也不算高,但网站的响应就是时不时地抖一下——本来 200ms 的接口偶尔蹦到 3 秒。你看 CPU 看不出问题,看内存也够用,看磁盘 I/O 也不忙,仿佛一切指标都「正常」。
这类问题十有八九出在调度开销上,而不是「计算量」上。具体说,就是上下文切换(context switch)过多和中断风暴(interrupt storm)这两件事。这篇文章就教你用 vmstat、pidstat、/proc/interrupts 这套最基础、最不需要额外安装的工具,把这种「隐形卡顿」定位出来。
先建立直觉:上下文切换是什么,为什么它会拖慢系统
CPU 在任意时刻只能跑一个进程/线程。当它要从 A 任务切换到 B 任务时,必须保存 A 的寄存器、内存映射、栈指针等状态,再恢复 B 的状态,这个过程就是一次上下文切换。切换本身不干活,纯属开销,但又是多任务系统必须付出的成本。
问题在于,如果切换太频繁,CPU 就大量时间花在「切来切去」而不是「真正执行代码」上。表现就是:CPU 使用率看起来不高(因为很多时间花在系统态和调度里,甚至显示不出来),但应用响应明显变慢。这就像一个人不停地在不同任务间跳来跳去,看起来很忙,实际产出为零。
上下文切换分两类,定位时一定要分清,因为它们对应完全不同的根因:
- 自愿上下文切换(voluntary):任务主动让出 CPU,比如等 I/O、等锁、sleep。数量高通常意味着在等资源(磁盘、网络、锁),根因往往在 I/O 或并发锁竞争。
- 非自愿上下文切换(nonvoluntary / involuntary):任务被强制剥夺 CPU,比如时间片用完被抢占。数量高意味着CPU 竞争激烈——跑的任务太多,或者有高优先级任务在抢。
记住这个对应关系,定位方向就清晰了:自愿高 → 查 I/O 和锁;非自愿高 → 查 CPU 竞争和进程数。
第一站:vmstat 看全局切换速率
判断「是否真的切换过多」,最快的一招是用 vmstat 看 cs 和 in 两列。它自带在 procps 包里,几乎所有 Linux 都有:
vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 812340 45120 3912200 0 0 12 48 840 1520 8 3 88 1 0
1 0 0 810112 45120 3912400 0 0 0 36 920 1683 9 3 87 1 0关键两列:
cs:每秒上下文切换次数。in:每秒中断次数(含时钟中断)。
怎么判断「高」?没有绝对阈值,要看趋势和基线。经验参考:一台只跑 Web 服务的单核或双核小 VPS,空闲时 cs 通常几百到一两千。如果它在无事发生时也持续上万,或者业务量没变却突然从 1000 涨到 20000,那就要查了。同时注意 sy(系统态 CPU 占比),如果 sy 异常高(比如超过 30%)而 us 很低,几乎可以断定大量时间花在内核调度和中断处理上。
另一个信号是 r 列(运行队列长度):如果 r 持续大于 CPU 核数(用 nproc 看核数),说明任务在排队等 CPU,非自愿切换必然偏高。
nproc # 先确认几核,r 列要跟它比第二站:pidstat -w 找出「谁在疯狂切换」
vmstat 只告诉你全局高,不告诉你是谁。要揪出元凶,用 sysstat 包里的 pidstat -w(-w 表示报告 task switching):
# 先装工具(Debian/Ubuntu)
apt-get install -y sysstat
# 每 2 秒采样一次,共 5 次,显示自愿/非自愿切换
pidstat -w -p ALL 2 5输出里有两列最关键:cswch/s(自愿切换/秒)和 nvcswch/s(非自愿切换/秒)。
- 如果某个进程
nvcswch/s极高(比如几万),说明它一直在被抢占,通常是 CPU 竞争严重,或者该进程数开太多(典型如 PHP-FPM 的pm.max_children开得远超核数、或者开了几十个 worker 的数据库)。 - 如果某个进程
cswch/s极高而nvcswch/s不高,说明它一直在等——等磁盘、等网络、等锁。这种进程本身不占 CPU,但它拖慢了整体吞吐。
对全局一起看,还可以让 pidstat 直接汇报总量:
# 只看上下文切换总量,不按进程拆
pidstat -w 2 3一个实战经验:个人站上最常见的「自愿切换爆炸」元凶是线程池/进程池开得比 CPU 核数大得多。比如四核 VPS 上把 PHP-FPM pm.max_children 设成 200、MySQL 连接数设成 500,几百个进程/线程挤在四个核上,每个时间片都被粗暴抢占,非自愿切换飙升,结果就是「CPU 不高但慢」。把池子收到核数的 1.5 到 2 倍,往往能立竿见影。
第三站:perf 看切换到底花在哪个函数上
如果 pidstat 也没给出明确元凶(比如分散在很多小进程上),可以用 perf 做更深一层的内核剖析,看 CPU 时间到底耗在哪些内核函数:
# 采样 10 秒内所有 CPU 的内核调用栈
perf record -a -g -e cpu-clock -- sleep 10
perf report如果看到热区集中在 __schedule、finish_task_switch、smp_call_function、native_smp_send_reschedule 这类调度相关函数上,就印证了「瓶颈在调度」的判断。注意 perf 采样需要 perf_event_paranoid 权限,如果报权限错误:
cat /proc/sys/kernel/perf_event_paranoid # 默认 2 或 3,限制较多
sysctl -w kernel.perf_event_paranoid=1 # 临时放行(生产慎用,排障后调回)中断风暴:当网卡把 CPU 全抢走了
另一类「CPU 不高但卡」的元凶是中断(尤其是软中断)。当网卡收到大量小包(典型如 DDoS、CC 攻击、或者一个打满带宽的爬虫),网卡会产生海量硬中断,内核把它们转成软中断(NET_RX)来处理,这些处理会抢占应用进程的 CPU。
这时候你在 top 里会看到 si(软中断)这一列很高,而 us 很低。vmstat 的 in 列也会激增。用 mpstat(同为 sysstat 包)确认哪个 CPU 被中断吃满:
mpstat -P ALL 1 5%soft 列很高、且集中在某一个核上(因为默认网卡中断只绑一个队列到一个 CPU),基本就锁定是网卡软中断了。再看具体设备:
# 看每个中断源在各 CPU 上的计数(跑两次比差值最准)
cat /proc/interrupts
watch -n1 'grep -E "eth0|ens|enp" /proc/interrupts'如果某个网卡队列的中断计数每秒涨几万,那就是它。对应排查方向:先看是不是被攻击/被爬——
# 用 ss 看当前连接数,是不是某个 IP 建了海量连接
ss -s
ss -tan | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head确认是攻击后,用 Nginx 限流(limit_req / limit_conn)或防火墙(ipset/nftables 封 IP)挡住流量,根源解决后软中断自然回落。如果只是想缓解中断集中在单核的问题,可以开网卡多队列(RSS)或软中断负载均衡(RPS),把网络处理分摊到多个核上——但这是「分摊症状」,流量异常才是要治的本。
一个实战排查顺序(照着走就行)
遇到「CPU 不高但慢」,按这个流程走,一般两三步就能定位:
- 第 1 步:
vmstat 1 10,看cs、in、sy是否异常高,r是否超过核数。判断是「切换型问题」还是「中断型问题」。 - 第 2 步:如果是切换型,
pidstat -w -p ALL 2 5找自愿/非自愿切换最高的进程,判断是「在等(I/O、锁)」还是「被抢(CPU 竞争、进程过多)」。 - 第 3 步:如果是中断型,
mpstat -P ALL 1 5看%soft,/proc/interrupts定位网卡,ss查连接,判断是攻击、爬虫还是配置问题。 - 第 4 步:对症下药——收进程池、修慢 I/O、封异常 IP、查锁竞争。
常见根因清单
- 进程池/线程池远超核数:PHP-FPM、JDBC 连接池、Gunicorn worker,开太多导致非自愿切换爆炸。这是第一嫌疑人。
- 锁竞争严重:多线程程序里一把大锁,导致自愿切换狂增。用
pidstat -w加应用侧锁监控确认。 - 磁盘或网络 I/O 慢:进程大量 sleep 等 I/O,自愿切换高企。结合
iostat -x的await一起看。 - 被 CC/DDoS 攻击:网卡软中断吃满单核,
si飙高。查ss和访问日志。 - 定时任务开了太多并发:比如同一时刻几十个 cron 一起跑,
cs周期性地出现尖峰。 - 虚拟机/容器超售:宿主把 CPU 超分得太狠,guest 的非自愿切换虚高,这种只能找服务商或迁移。
最容易踩的三个坑
- 只看 CPU 使用率下结论:
us低不代表没压力,调度和中断的开销很多时候是「看不见的成本」。 - 把
load average当 CPU 使用率:load 里包含等待 I/O 的进程(D 状态),load 高不一定 CPU 忙,可能全是卡在磁盘上的进程。 - 一上来就 perf 深度采样:其实
vmstat+pidstat -w这两条命令就能解决八成问题,先在表层定方向,再决定要不要往下钻。
小结
「CPU 不高却很卡」是运维里的经典疑难杂症,而它的钥匙就藏在上下文切换和中断这两个容易被忽略的维度里。vmstat 看全局速率,pidstat -w 分清自愿与非自愿,mpstat 加 /proc/interrupts 抓中断风暴——这几件基础工具用熟,你就能把那些「看指标一切正常、用起来就是要命」的问题按在地上摩擦。记住那个核心判据:自愿高查等什么,非自愿高查抢什么。