服务器突然变慢、接口超时、load 飙升——绝大多数线上故障背后,都是 CPU、内存或磁盘 I/O 的某一项被打满。Linux 性能调优不是玄学,而是一套可复用的瓶颈定位流程:先建基线,再分层排查,最后用数据说话。本文用 top、vmstat、iostat、perf 等原生工具,带你从「系统很卡」快速定位到「到底是谁在吃资源」,每一步都给出可直接复制的命令与危险阈值。
一、先建基线:别一上来就 kill 进程
排查的第一原则:先看全局负载,再下钻到具体资源。很多人一看到慢就重启服务,结果故障反复。先用 uptime 看 1/5/15 分钟平均负载,它约等于「正在运行 + 等待 CPU 的进程数」。
$ uptime
14:22:01 up 38 days, 3:11, 2 users, load average: 1.05, 0.87, 0.64
# 经验线:load 长期 > 逻辑核数,说明 CPU 已饱和
$ nproc # 查看逻辑核数,例如 8 核
$ lscpu | grep -E 'CPU\(s\)|Thread|Core'
一般 load 在核数的 70% 以内算健康;持续超过 1 倍说明有排队,超过 2 倍就要警惕响应劣化。注意:load 高不一定等于 CPU 满——也可能是 I/O 等待(下文第四节)或进程抢锁。
二、CPU 瓶颈定位:找到「最忙的那个进程」
进入 top 后按 P 按 CPU 使用率排序,按 1 展开多核。重点关注 %CPU 和 %wa(iowait,若长期 >20% 多为磁盘拖后腿,非 CPU 本身)。
# 按进程看 CPU,每秒刷新,只看前 10
$ top -b -d 1 -o %CPU | head -20
# 看某个进程的线程级开销(Java/Go 多协程场景常用)
$ pidstat -t -p 13254 1
# 找「热点函数」:采样 CPU 上的指令,无需重启进程
$ perf top -p 13254
$ perf record -F 99 -p 13254 -g -- sleep 30 && perf report
上下文切换过多也会「忙而无效」。用 vmstat 1 看 cs 列,若每秒几十万次且 us 不高,往往是锁竞争或频繁短连接;pidstat -w 可定位自愿/非自愿切换最高的进程。
熟悉 top 里的 CPU 行也很有用:us 是用户态占比,sy 是内核态(syscall 过多会偏高),id 空闲,wa 等待 I/O,st(steal)是被虚拟化层抢走的时间——在云服务器上 st 持续偏高,说明宿主机超卖,调应用无济于事,只能升配或迁规格。如果 sy 高而 us 低,优先怀疑频繁的系统调用(如大量小文件读写、短连接),而不是业务代码本身。
三、内存瓶颈定位:free 不够看,要看 available
新手常误读 free 列——Linux 会主动用空闲内存做页缓存(cache/buffer),所以 free 小不代表内存紧张。真正该看的是 available:内核评估后还能分给应用的内存量。
$ free -h
total used free shared buff/cache available
Mem: 15Gi 9.2Gi 1.1Gi 420Mi 5.3Gi 4.8Gi
Swap: 2.0Gi 0.0Ki 2.0Gi
# si/so 为换入换出,长期非 0 说明已在用 Swap,性能会骤降
$ vmstat 1
procs --memory-- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa
2 0 0 1.1G 410M 5.3G 0 0 12 88 320 980 18 6 75 1
若 available 持续走低、si/so 不为 0,下一步就是 OOM——容器内常见的是 cgroup 内存上限触发,表现为进程被直接杀死。这类问题我们在《OOMKilled 排查实战:容器内存限制与 JVM 堆外》里有完整复盘,建议结合本文一起看。
顺带提两个常被忽略的点。其一,页缓存是「借来的内存」:它会在应用需要时被自动回收,所以不要因为 free 小就急着加内存。若想验证「应用真实占用」,用 cat /proc/meminfo 看 MemAvailable,或按进程 smem -t -k 排序。其二,/proc/sys/vm/drop_caches 可手动清缓存做测试(echo 3 > /proc/sys/vm/drop_caches),但生产环境千万别拿它当「释放内存」的常规手段——那只是把缓存腾出来又马上被重新填,反而增加 I/O。
四、I/O 瓶颈定位:await 与 %util 是核心
磁盘慢是「隐形杀手」,它让 CPU 在 iowait 上空转。用 iostat -x 1 看扩展统计,关键两列:%util(设备繁忙度,长期接近 100% 即饱和)和 await(单次 I/O 平均等待毫秒,远高于 svctm 说明在排队)。
$ iostat -x 1
Device r/s w/s rkB/s wkB/s await aqu-sz %util
sda 12.0 480 1500 22000 8.20 3.9 99.1
# 找「哪个进程在狂写磁盘」
$ iotop -o # 只显示有 I/O 的进程
$ lsof -p $(pgrep mysql) | grep REG | head # 看进程打开了哪些文件
磁盘空间也要随手确认:df -h 看挂载点用量,du -sh * 在可疑目录逐层定位大文件。inode 耗尽(文件数爆满)同样会让写入失败,可用 df -i 单独排查。
还有一个隐藏开关:脏页回写。Linux 会把写操作先攒在内存的「脏页」里,再批量落盘。若瞬间大量写入(如批量导入、日志刷盘),/proc/sys/vm/dirty_ratio 触顶会阻塞进程。用 cat /proc/meminfo | grep Dirty 看当前脏页量,sync 可强制落盘。顺序写被读拖慢时,多半是读请求和写请求抢同一块磁盘——此时上 SSD 或把读写分到不同盘,比调参数立竿见影。
五、速查表:三类瓶颈的一眼判定
| 资源 | 首选命令 | 关键指标 | 危险阈值 |
| CPU | top / pidstat / perf | %CPU、cs | load > 核数、cs 每秒 > 20 万 |
| 内存 | free -h / vmstat | available、si/so | available<10%、so 持续非 0 |
| I/O | iostat -x / iotop | %util、await | %util≈100%、await>15ms |
六、真实案例:一次 load 飙到 30 的排查
某天凌晨告警,一台 8 核机器 load 从 2 涨到 30,API 大面积 504。排查顺序:top 看到 %wa 高达 60%,iostat -x 显示 sda %util=100%、await=120ms——确认是磁盘 I/O 饱和而非 CPU。进一步 iotop 锁定一个日志服务在每秒写入 800MB 且无轮转。
临时措施是限流日志写入并清理旧文件,load 5 分钟内回落到 1.2。这种「I/O 打满→上游等待→502/504 雪崩」的链路,与我们在《Nginx 502/504 排查实录:从超时到连接池》里描述的超时传导如出一辙,根因往往不在网关本身,而在后端资源。
七、把调优沉淀为持续监控
手动排查救得了一次火,救不了天天火。把上面的指标用 node_exporter 暴露,接入《Prometheus + Grafana 监控面板实战》里的采集体系,对 node_load1、node_memory_MemAvailable_bytes、node_disk_io_time_seconds 设阈值告警。这样瓶颈在影响用户前就被看见,调优也从「救火」变成「巡检」。
八、调优实战中的三个常见误区
误区一:load 高就加 CPU。 前面说过 load 高可能是 I/O 或锁,盲目升配只是把账单变厚。先分清 %wa 和 cs 谁在作怪,再决定加机器还是改代码。
误区二:free 小就是内存泄漏。 多数情况是页缓存正常工作。先盯 available 趋势和 si/so,再谈泄漏。真正的泄漏往往表现为 available 缓慢单调下降、几天内逼近 0。
误区三:一次 perf 就下定论。 采样 30 秒可能正好踩中 GC 或定时任务。至少在不同时段采样 2–3 次,对比热点是否稳定,避免被瞬时抖动误导。
九、小结
Linux 性能调优的套路其实很固定:uptime 看负载 → top/pidstat 锁 CPU → free/vmstat 看内存 → iostat/iotop 查 I/O → perf 追热点函数。记住三句话——load 高先分清是 CPU 还是 I/O;内存看 available 别看 free;磁盘看 %util 和 await。把这套命令练熟,线上 80% 的「系统很卡」都能在十分钟内定位到元凶。




