Linux系统CPU与内存高负载排查
先说结论:Linux系统 CPU、内存高负载排查的正确顺序是"先定位、再止血、后治理"。乱重启只会掩盖问题,根因一丢,下次照样爆。
第一步:load average 决定排查方向
上来就跑 top,但 top 只是快照,会骗人。先看 load average 那一行——它是 1、5、15 分钟的平均运行队列长度。判断标准很简单:持续超过 CPU 逻辑核数就是过载。如果 15 分钟那个值还超,说明负载积压已久,不是瞬时抖动。
# 查看 CPU 逻辑核数,load average 要跟这个比
nproc
# 实时观察 load,每 3 秒刷一次
uptime
判断标准速查表:
| 现象 | 真实含义 | 下一步 |
|---|---|---|
| Load > 核数,且 CPU(id) 低 | I/O 瓶颈伪装成高负载 | 查 D 状态进程 |
| Load > 核数,us 接近 100% | 业务代码算不动了 | 锁进程 → 定位函数 |
| Load 高,sy > 30% | 系统调用/上下文切换风暴 | 查 cs、si、线程数 |
| Load 高,st 高 | 云主机被宿主机超卖 | 联系云厂商,不是你的锅 |
| Load 低但卡顿 | 大概率是磁盘满或网络问题 | 查 df 和网络 |
第二种情况:CPU 空闲但 Load 高
这是最容易误判的场景,也是排查 Linux高负载时第一个该排除的情况。逻辑很简单:load 高说明有进程在排队,但它们不是在算,是在等。
# 专门揪出 D 状态(不可中断睡眠)进程
ps -eo state,pid,cmd | grep "^D"
# 按数量排序,看哪个进程堆积最严重
ps -e -L h o state,cmd | awk '{if($1=="R"||$1=="D"){print $0}}' | sort | uniq -c | sort -k 1nr
D 状态进程的含义:进程正在等磁盘响应,这种状态下 kill 不掉也退不出,连 SIGKILL 都没用。如果大量 D 状态堆积,说明磁盘响应慢,CPU 其实在空转。
配套确认磁盘指标:
# -x 展开详细信息,重点看 %util 和 await(毫秒)
iostat -x -m 1 10
%util 接近 100 或 await 持续很高,就是磁盘扛不住了。云服务器场景可以升级云盘类型(SSD → 高性能云盘),但注意云盘最终 IOPS 受实例规格限制,规格不够也得一起升。
CPU 真高时:从进程定位到代码行
确认是 CPU 瓶颈后,按三步往下钻。
第一步:锁定进程
# 实时按 CPU 排序(等价于 top 里按 P)
top -o +%CPU
# 或者用 ps 直接输出
ps -eo pid,comm,%cpu,%mem --sort=-%cpu | head -n 10
第二步:看趋势而非瞬时值
# 每 2 秒采样一次共 10 次,看该进程的 CPU 随时间怎么变
pidstat -u 2 10
# 如果是线程级问题,加 -t 看线程
pidstat -t -p <PID> 2 10
CPU 忽高忽低,多半是定时任务或流量脉冲;持续高,才是真的代码问题。
第三步:定位到函数甚至代码行
这一步是「Linux CPU占用高定位到具体代码」的关键:
# 对目标进程采样 30 秒生成火焰图
perf record -g -p <PID> -- sleep 30
# 文字版报告,直接看排在最前的函数
perf report --stdio | head -50
按语言分开处理:
- Java:
jstack <PID>导出线程栈,搜RUNNABLE状态线程,看调用栈是否反复停在同一个方法。频繁 Full GC 也会拉高 CPU,配合 GC 日志一起看。 - Python/Go:
py-spy dump --pid <PID>或pprof直接抓火焰图。 - C/C++:
perf top -p <PID>看符号级热点。 - 怀疑是恶意进程:检查是不是挖矿程序或 Rootkit 隐藏进程,
ls -la /proc/<PID>/exe看真实路径。
内存高:available 才是真指标
内存排查第一个误区是盯着 free 看。Linux 会主动用空闲内存做 buff/cache,这是设计行为不是问题。真正该看的是 available:
# 人类可读格式,重点看 available 和 Swap used
free -h
# 每 5 秒刷新,看变化趋势
free -s 5
# 内存排序找出真凶
ps aux --sort=-%mem | head -n 6
判断标准:内存使用率超过 80% 就该警惕,尤其配合 Swap 开始使用的情况。
kswapd0 是什么鬼
如果 top 里 kswapd0 长期占高 CPU,这不是它自己的问题,而是物理内存不足的信号。它是内核负责换页的进程——内存不够时会疯狂扫描页表、做回收和换出,这些计算密集操作直接把 CPU 打满。
处理思路是先确认内存到底被谁吃了:
vmstat 1 5 # 看 si(换入)、so(换出)两列,长期非 0 说明在频繁换页
free -h
ps aux --sort=-%mem | head -n 10
si、so 持续非 0,就说明物理内存已经不够用了,得加内存或优化进程。
内存泄漏和 OOM Killer
进程占用一直涨不降是典型泄漏特征。想确认是不是被 OOM Killer 杀过:
# 查内核日志里的 OOM 记录
dmesg -T | grep -i "killed process"
还有一个有用的文件是 /proc/<PID>/oom_score,值越高越容易被 OOM 杀掉。/proc/<PID>/oom_adj 取值范围 -17 到 15,设为 -17 表示永不被 OOM 杀掉——MySQL 这类关键服务常这么配。
关于手动释放缓存:sync 后 echo 1 > /proc/sys/vm/drop_caches 确实能让 free 数字好看,但会清空页缓存导致后续 IO 暴涨,生产环境别碰。
处置:止血、治理、防复发
第一层,临时止血(业务优先)
# 优雅终止,给进程 15 秒收尾时间,比 kill -9 安全
sudo kill -15 <PID>
资源瓶颈导致的,加 swap 临时扛一下;遭 CC 攻击的,接 WAF 限流。
第二层,根因治理
- 慢 SQL:加索引、看执行计划、必要时拆表
- 正则回溯、死循环:这是代码问题,改代码
- 线程数配置过大:调小 worker/线程池
- 内核态高:开网卡多队列,把中断打散到多核
第三层,防复发
- CPU、内存、IO wait、load 都设阈值告警
- 线上跑 atop 做历史回溯,出事能查当时的曲线
- 把每次根因写进运维知识库,下次同类告警直接对号入座