凌晨两点收到告警,登录上去第一反应往往是top一把梭,看到 %Cpu 飙红就准备重启。先别急。服务器CPU 到底是被谁吃满了、是真算力不够还是被 IO 拖累,这两件事的处理方式完全不同,分不清就重启,大概率半小时后又告警。
这篇文章把"CPU 打满"这件事拆成四步:先定性,再定位进程,再钻到进程内部,最后才是止血还是扩容。每一步都给可照做的命令,也标出几个容易误判的坑。
一、先定性:你看到的"CPU 高"到底是哪一种
top第一行那行%Cpu(s)是整台机器最该先看懂的地方,它把时间切成了八块:
us:用户态时间,应用代码在跑,大部分 CPU 消耗落在这里sy:系统态时间,内核在干活(系统调用、中断、调度)ni:被降过优先级的进程占的时间id:完全空闲wa:iowait,CPU 其实闲着,在等磁盘或网络 IO 完成hi/si:硬件 / 软件中断st:steal time,只出现在虚拟机里,是宿主机把本该给你的 CPU 周期给了别的租户
关键判断就三条:
us高、id低 → 应用真的在吃算力,往下走"定位进程"那条线。wa高、us低 → 不是 CPU 的锅,是存储或网络 IO 瓶颈,top里%wa冒红就别去加 CPU,先查磁盘和慢查询。st高 → 你是虚拟机且宿主机超卖了,本地再怎么调优也绕不开这层限制,只能找厂商换规格或换宿主机。
还有一个常被误读的指标:load average(平均负载)。它显示的三个数(1 分钟 / 5 分钟 / 15 分钟)统计的是"可运行态(R)+ 不可中断睡眠态(D)"的进程数,D 状态基本就是卡在 IO 上的进程。所以一台机器 load 飙到 20、但%Cpu的id还有 70%,很可能只是磁盘慢,CPU 自己闲着。判断 CPU 是否真饱和,别只看 load,要看vmstat 1的输出:
vmstat1# procs ---- r b ---- cpu --- us sy id wa st# r b swpd free buff cache si so bi bo in cs us sy id wa st# 8 2 0 120000 0 800000 0 0 40 120 300 900 85 5 2 5 3r是运行队列长度,b是卡在 D 状态的进程数,us/sy/id/wa/st对应上面那八块。对照nproc出来的核数——r长期大于核数才是真的排队等 CPU,b长期很大则是 IO 在堵。
一个真实的误判案例:某次告警 load 冲到 15,新人直接申请扩容,结果top里%id还有 60%、%wa到了 35%——根因是一块云盘 IO 被打满,MySQL 在等磁盘,CPU 根本没饱和。机器加上去,IO 还是瓶颈,问题照旧。这就是为什么要先看完那八个数再决定动作。
还有一层容易坑人的是容器。在 Docker / K8s 容器里直接跑top或nproc,经常看到的是宿主机的核数,而不是这个容器被分配到的限额。要看容器真正能用多少 CPU,得读 cgroup 的配额文件/sys/fs/cgroup/cpu/cpu.cfs_quota_us和cpu.cfs_period_us,两者相除才是该容器允许使用的核数。监控指标也要以容器内采集的为准,否则你会以为 CPU 很闲,其实早被隔壁容器挤占了。
顺带提一句服务器cpu温度:如果机房空调出问题或风扇故障,CPU 会因为过热降频(thermal throttle),表现出来就是"没干重活却变慢",这种用sensors或带外管理的温度接口能看到,属于另一种独立的排查线,别和算力瓶颈混为一谈。
二、定位进程:top、htop 与 pidstat
定性完了,确认是us真高,下一步把"谁在吃"揪出来。
top默认就按%CPU从高到低排,进去后按P可以再强制按 CPU 排一次确认;htop更直观,按F6选CPU%排序,还能看到每个核的占用条。找到可疑 PID 后,光看%CPU不够,还要看它是单线程打满一个核(总利用率不高但那个核 100%),还是多线程铺满所有核。
要看每核明细,用mpstat -P ALL 1(来自 sysstat 包),单核 100%、其他核闲着,说明程序没做好并行,加核没用,得改代码或加进程数。
想要更干净的进程级采样,用pidstat:
pidstat-u1# 每 1 秒刷新各进程 CPU 占用pidstat-u-p12341# 只盯某个 PIDpidstat的好处是能跨采样窗口稳定输出,方便你贴进值班记录。顺带提一句,linux服务器查看cpu使用率和 linux服务器cpu使用率查看命令,网上搜出来大多是top/htop/mpstat这三板斧,记住它们各管一层就够了。想知道整机规模,如何查看服务器的内存和cpu参数可以从nproc和lscpu入手,前者看在线逻辑核数,后者看架构、型号和缓存。
三、钻进进程内部:不同语言各有各的刀
锁定到 PID 只是半路,真正要知道"它在算什么"得再往里钻,否则你只知道"MySQL 很忙",不知道它在跑哪条烂 SQL。
Java 服务:最省事的是 async-profiler,挂上就能采 CPU 火焰图:
./profiler.sh start<pid># 跑个几十秒./profiler.sh stop-fflamegraph.svg<pid>打开 svg,横着最宽的那一层函数,就是吃 CPU 的大头,照着去优化那一段比盲猜强十倍。
C / C++ / 系统层:
perf top直接看实时热点函数;要留档就用perf record -F 99 -p <pid> -g -- sleep 30再perf report,不需要改代码、不需要重启。MySQL:
SHOW PROCESSLIST;看Time、State、Info三列,Time 很大且 State 卡在Sending data或Locked的,基本就是慢查询或锁等待,再去翻慢查询日志(slow log)拿完整 SQL。PHP-FPM:开
slowlog,超时的请求会把调用栈写进去,常见于某段同步远程调用或正则回溯爆炸。Java 退出也有讲究:顺带说一句止血。对 JVM 这类进程,优先发
kill -15而不是-9——-15会触发 shutdown hook,让连接池、线程池有序关闭、把内存里的数据刷盘;-9是直接剥夺进程,没机会做清理,重启后可能要更长的时间做恢复或校验。通用兜底:
strace -p <pid> -c统计这个进程在调哪些系统调用,如果满屏是futex多半是锁竞争,满屏是read/write多半是 IO。
这一层的意义在于:服务器cpu使用率过高怎么解决,答案从来不在"重启"或"加机器",而在"到底是哪一行代码 / 哪一条 SQL"。定位不到这一层,扩容只是把问题变得更贵。
四、止血与根治:kill -9 不是万能药
临时要保住线上,有几招比直接杀进程温和:
renice +10 -p <pid>把问题进程优先级调低,让它少抢 CPU,给核心业务让路。用 systemd 的
CPUQuota=或 cgroup 给这个服务设 CPU 上限,防止它把整台机器拖死:# /etc/systemd/system/xxx.service.d/limit.conf [Service] CPUQuota=50%业务侧限流、降级非核心功能,把峰值削平。
至于kill -9,有两个坑必须知道:一是D 状态(不可中断睡眠)的进程kill -9也杀不掉,因为它在等硬件事件(比如一块卡死的 nfs 挂载),得先解决底层 IO;二是数据库、消息队列这类有状态的进程,强杀主进程可能导致数据不一致或恢复缓慢,能kill -15(SIGTERM)让它自己收尾就别用-9。
根治的方向就三类:算法或 SQL 优化、加缓存减少重复计算、把同步改成异步或分片。止血能救今晚,根治才不用来回加班。
五、该扩容时怎么选 vCPU:云服务器的 4 核是真 4 核吗
前面都是"够不够用、哪里不够",如果确认就是长期算力不足,要加机器,这里有个选型章节值得单独说。很多人纠结"云服务器4核cpu是真4核还是假4核"——准确说,云服务器的 vCPU 一般是物理 CPU 的超线程切出来的逻辑核,且同一颗物理核由多个租户共享,这点从上面top里的%st(steal time)就能感觉到:邻居一忙,你的st就涨。所以它不是你独占的物理核,但对绝大多数业务来说,按 vCPU 数规划和按物理核规划在容量上是对等的,重点看的是:
- vCPU 数量与基准性能 / 突发性能是否匹配你的常驻负载;
- 突发型实例的CPU 积分机制(闲时攒、忙时花,持续高负载会"掉速");
- 是否需要独享型 / 裸金属来规避超卖带来的
st抖动。
各家实例规格的命名和 vCPU 映射都写在官方文档里,选型前建议直接翻三家对照,而不是只看某一家的宣传:
- 阿里云 ECS 实例规格族说明:阿里云 ECS 实例规格
- 腾讯云 CVM 实例规格:腾讯云 CVM 实例类型
- 华为云 ECS 实例规格:华为云 ECS 实例规格
三家虽然命名不同(阿里云叫"实例规格族"、腾讯云叫"实例类型"、华为云也叫"实例规格"),但底层都是把物理 CPU 通过虚拟化切分成 vCPU 提供给租户。选购时真正要对比的是"这台实例能给到多少稳定算力"和对应的计费模型,而不是名字好不好听。突发型适合平时闲、偶尔冲一波的业务;独享型 / 裸金属适合对st抖动零容忍的稳态负载。
如果业务是短时、波峰波谷明显,弹性伸缩比租一台固定的 cpu服务器 更划算;如果是稳态高负载且对st敏感,独享型或裸金属更稳。另外别把 gpu服务器与cpu服务器的区别 搞混:普通 Web、数据库靠的是 CPU 通用算力,只有 AI 推理、训练、图像渲染这类并行计算才需要 GPU,两者选型维度不同,别一上来就堆 GPU。
六、收个尾
CPU 打满这件事,链路其实很清晰:先定性(us / wa / st 分清),再定位进程(top / pidstat / mpstat),再钻进程内部(火焰图 / 慢查询 / slowlog),最后才是止血或扩容。把这套走完,你解决的就不只是"这次为什么慢",而是真的知道瓶颈长什么样。
文中涉及的各家实例规格、vCPU 映射、计费与配额政策以厂商当期官方公示为准,扩容前请到对应控制台核实当期配置与价格,再决定是弹性扩容还是换独享规格。