服务器 CPU 飙高时,我最怕两种操作:看到 load average 高就立刻加服务器,或不分情况直接 kill -9。前者多半没弄清原因,后者会打断正在处理的请求。
在太云接触到的云服务器、物理服务器工单里,CPU 异常很常见,原因却不相同。我按“确认指标 → 找进程和线程 → 判断业务还是异常 → 止损 → 留证据”排查,流程不炫技,胜在不容易误杀。
先确认是不是真忙
uptime 的 load average 统计运行队列和不可中断 I/O 任务,不等于 CPU 使用率。8 核机器 load 到 8 不必然异常,1 分钟、5 分钟、15 分钟的变化更能说明压力是否持续。
我会同时看 top 和 vmstat 1 5。%us、%sy、%si、%wa、%st 分别代表用户态、系统态、软中断、I/O 等待和虚拟化环境被窃取时间。计算重通常 %us 高,网络包量大则软中断突出,磁盘慢会拉高 %wa。云主机 %st 长期偏高,我会先查宿主机争用。
找到具体进程和线程
别只盯 top 的总占用,我经常同时跑:
ps -eo pid,ppid,user,%cpu,%mem,stat,etime,cmd --sort=-%cpu | head -20pidstat -u 1 5
前者看排行、父子关系和状态,后者连续采样,能抓到短暂冲高后回落的进程。状态为 D 多与 I/O 阻塞有关,先查磁盘。某线程长期占满单核,有权限时再对照 /proc/1234/task/线程ID/stack;Java 可用 jstack 1234 看线程栈。
判断是谁在消耗
- 正常业务:把 CPU 曲线和访问日志、定时任务、备份任务对上。流量上涨不该靠杀进程解决。
- 程序失控:服务重启后立刻冲高,常见于死循环、无限重试或序列化开销。PHP-FPM worker 过多也会争抢资源,盲目调大
pm.max_children只会更糟。 - 网络异常:若
%si高且连接数增长快,用ss -s、ss -antp | head -30看来源,再对照 WAF、Nginx 日志。先限速或封禁异常来源,别急着重启整套服务。 - 计划任务或入侵痕迹:检查
systemctl list-timers --all、crontab -l、/etc/cron*,再看journalctl -u sshd --since today、last和 SSH 密钥。生产机上别乱跑来路不明的一键清理脚本。
止损和后续处理
需要重启时,我会先留日志,再优雅重启。比如执行 journalctl -u nginx -n 200 > /tmp/nginx-before-restart.log,确认服务名后运行 systemctl restart nginx。能正常退出时用 kill -TERM 1234,别把 kill -9 当默认按钮。临时降优先级可用 renice -n 10 -p 1234,但这只是缓冲。
根因明确后,再通过 systemd 长期约束。在 unit 文件的 [Service] 段设置 CPUQuota=200%,改完执行 systemctl daemon-reload;再用 Restart=on-failure 处理崩溃,并给日志做好轮转。数值不能照抄,要结合业务峰值和机器核数压测。
我还会把异常时间、指标、进程、线程、处置和复发条件记进值班表。换人接手时,这些记录比一条“万能命令”有用。

