Linux 性能排查实战:top、iostat 与火焰图定位瓶颈

线上服务突然变慢,第一反应往往是”重启大法”。但重启只是把问题藏起来,Linux 性能排查的真正价值,是顺着指标找到那个真正在吃资源的进程、那块被写爆的磁盘、那条被占满的连接。本文用 USE 方法(Utilization 利用率、Saturation 饱和度、Errors 错误)搭起排查坐标系,再逐个拆解 CPU、内存、磁盘 IO、网络四大子系统的核心命令与火焰图用法,并附一次 CPU 100% 的端到端复盘。

一、先建立排查坐标系:USE 方法

盲目敲命令只会淹没在指标里。Brendan Gregg 提出的 USE 方法给了一个极简框架:对每一个资源,同时看三类信号——利用率(资源忙的时间占比)、饱和度(排队等待的负载量)、错误(报错计数)。CPU 100% 只是”利用率高”这一个维度,至于它是不是瓶颈,还要结合饱和度与错误一起看。先有坐标系,再上工具才不会跑偏。

维度它在说什么典型工具
Utilization 利用率资源在一段时间内”忙”的比例top、iostat、sar
Saturation 饱和度超出处理能力的排队负载vmstat 的 r 列、iostat 的 aqu-sz
Errors 错误设备/协议层面的失败计数dmesg、ss 错误列、网卡计数

二、CPU 排查:top / vmstat / mpstat

top 是最快的入口:看 %Cpu(s) 行的 us(用户态)、sy(内核态)、id(空闲)、wa(IO 等待)。注意 wa 高往往不是 CPU 问题,而是磁盘在拖后腿,别急着给 CPU 背锅。vmstat 1 看系统级趋势:r 列是运行队列长度,长期大于 CPU 核数就代表饱和;cs 是上下文切换,异常飙升常伴随锁竞争;in 是中断数。

# 每 1 秒采样一次,重点看 r(运行队列)与 cs(上下文切换)
vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 3  0      0 812344 123456 2048900   0    0     2     8  420  980 45  8  47  0  0

mpstat -P ALL 1 把每个核拆开看,能发现”单核打满、其余围观”的伪瓶颈——这类问题加机器没用,得改代码把任务拆开并行。

三、内存排查:free / vmstat / 缓存与 swap

free -h 看总量,但更要看 available 列——它比 free 更真实,包含了可回收的 page cache。Linux 会故意把空闲内存拿去缓存文件,所以 free 偏低不等于内存紧张。真正危险的是 swap 在动:vmstat 里的 si/so(换入换出)持续非零,说明内存已不够、被迫用磁盘顶,性能会断崖式下跌。一旦看到 so 在涨,先 top 按 M 排序找吃内存的进程,再谈扩容。

四、磁盘 IO 排查:iostat / iotop

磁盘是最容易被误判的瓶颈。iostat -x 1 的关键看两列:await(单次 IO 平均等待毫秒)和 %util(设备繁忙百分比)。%util 接近 100% 说明磁盘已经饱和;如果 %util 不高但 await 很大,问题多半在阵列或远端存储,而不是本地盘。

# 看扩展统计,关注 await 与 %util
iostat -x 1
Device   r/s    w/s   rkB/s  wkB/s  await  aqu-sz  %util
sda     12.0  180.0   480.0  2400.0  18.50   1.80   92.0
指标含义警戒线
r/s, w/s每秒读写次数视盘型而定
await平均 IO 等待(毫秒)持续大于 20ms 偏慢
%util设备利用率持续大于 90% 即饱和
aqu-sz平均队列深度持续大于 1 表示在排队

io 瓶颈和数据库性能高度相关,MySQL 深度调优时我们常先排除磁盘这一层,PostgreSQL 慢查询优化同理——很多时候”慢 SQL”其实是”慢磁盘”的替罪羊。

五、网络排查:ss / netstat / sar

连接数爆了、TIME_WAIT 堆积、丢包,都归属网络维度。ss -antp 比 netstat 更快,直接看 Established 数量与是谁占着连接;sar -n DEV 1 看每块网卡的收发速率与错误包;dmesg 里能捞到网卡丢包/重置的内核日志。Web 服务层的高延迟,有时根因不在应用,而在 Nginx 的限流或连接数配置,排查链路别漏掉这一环(参见 Nginx 限流防刷实战)。

# 看所有 TCP 连接状态分布,定位 TIME_WAIT / ESTAB 堆积
ss -ant | awk '{print $1}' | sort | uniq -c
# 看网卡吞吐与错误包
sar -n DEV 1

六、进程级深度剖析:perf 与火焰图

当 top 告诉你某个进程 CPU 高,却不知道它把时间花在哪,就该上 perf。perf 是 Linux 原生的性能计数器采样器,能对运行中的进程做函数级画像,再配火焰图把调用栈可视化——哪一列最宽,哪段代码最吃 CPU,一目了然。

# 1) 全系统采样 30 秒(99Hz,带调用栈)
perf record -F 99 -a -g -- sleep 30
# 2) 导出采样为可读栈
perf script > out.perf
# 3) 用 Brendan Gregg 的 FlameGraph 工具折叠并绘图
stackcollapse-perf.pl out.perf > out.folded
flamegraph.pl out.folded > flame.svg

火焰图里越宽的框代表耗时越多;顶层宽框就是热点函数。要点是目标进程要带调试符号(保留符号表),否则火焰图只剩一堆十六进制地址,看不出函数名。这条链路和日常监控是互补的:监控告诉你”哪台机器 CPU 红了”,perf 告诉你”红的是哪行代码”。

七、实战:一次 CPU 100% 的全流程复盘

场景:某 API 服务响应从 50ms 涨到 3s,top 看到某个 worker 进程 %CPU 恒为 100。完整定位链路如下:

# 盯住某个进程的 CPU 使用(按进程下钻)
pidstat -u -p 12345 1
# 对该进程单独采样出火焰图
perf record -F 99 -p 12345 -g -- sleep 10

① 用 vmstat 1 确认 r 队列长期大于核数,且 wa≈0,确属 CPU 饱和而非 IO 拖后腿。② mpstat -P ALL 1 发现只有 1 号核打满、其余空闲——典型的单线程热点。③ pidstat 锁定该进程,确认是用户态 us 高,不是内核 sy。④ perf 火焰图显示 80% 宽度落在 json 序列化函数。⑤ 定位到一段在循环里反复 dumps 大对象的日志代码,加缓存加降采样后 CPU 回落到 15%。复盘要点:先坐标系(USE)→ 再子系统(CPU)→ 再进程 → 最后函数级,每一步都用数据说话,不靠猜。

八、把排查沉淀成可观测体系

手工排查救得了一次火,救不了天天着的火。真正的解法是把指标常态化采集:用 Prometheus + Grafana 把 CPU、内存、磁盘、网络做成持续面板,异常自动告警(参见 Prometheus + Grafana 监控面板实战);用 Loki 或 ELK 把日志集中,出问题先查历史曲线而非现敲命令(参见 Loki 与 ELK 日志收集选型落地)。指标先行,排查才有基线可比——没有基线的”变慢”,你根本不知道慢到什么程度才算异常。另外,排查前务必确认服务器本身健康:SSH 与防火墙配置正确(参见 服务器安全加固实战),否则连上去都费劲。

九、小结

Linux 性能排查不是背命令,而是建立”先坐标系、再分子系统、最后进程级下钻”的自顶向下纪律。top 与 vmstat 看全局,iostat 与 ss 看设备与连接,perf 加火焰图做函数级定位;USE 方法贯穿始终,让你每次都能拿数据代替直觉。把这套流程配上 Prometheus 与 Loki 的可观测底座,绝大多数性能问题都能在分钟级被定位,而不是靠重启碰运气。性能优化的前提,永远是先把瓶颈测出来。

上一篇 Redis 持久化踩坑:RDB 与 AOF 数据丢失复盘
下一篇 端侧大模型部署实战:2026 边缘推理落地指南