搞性能排查这件事,最让人头疼的往往不是“服务挂了”,而是“服务看起来很健康,但就是慢”。CPU 没满、内存没满、IO 看起来也还行,监控图上全是正常曲线,可线上业务就是卡得让用户骂娘。这时候你打开终端,敲一个uptime,发现 load average 已经 20 了,才意识到问题早就藏在指标定义的夹缝里。
这份指南我想了很久,决定按 CPU、内存、IO、网络四个维度拆开讲,每个维度都配上实际排查中真正会用的命令、判读逻辑和踩坑记录。面向的是有一定 Linux 基础、但还没系统整理过性能排查思路的运维、开发和后端工程师。看完你至少能形成一套自己的排查套路,遇到线上问题不慌,知道先看什么、后看什么、什么时候该用perf什么时候该用iostat。
1. 排查前先建立基线:这些命令和准备工作值得提前做好
1.1 30 秒全局快照:六条命令看完整台机器
真正上手排查之前,我建议你先学会抓全局快照。所谓快照,就是在同一时间点,把 CPU、内存、IO、网络四类指标各看一眼,形成一张“体检单”。以后无论往哪个方向深挖,你心里都有一个全局坐标系。
我惯用的一组快速命令长这样:
uptime vmstat 1 5 top -b -n 1 | head -20 free -m iostat -x 1 3 sar -n DEV 1 3这六条命令分别对应:
uptime:看 load average(1、5、15 分钟三个值),先对整机压力有个模糊判断。vmstat 1 5:连续采样 5 次,重点关注 r、b、swpd、si/so、us/sy/wa,分别代表运行队列、阻塞进程、换入换出量和 CPU 时间分布。top:看进程维度,谁在最前面占资源。free -m:看内存总量、已用、可用和 buffer/cache。iostat -x:看每块磁盘的 IOPS、吞吐、await 和 %util。sar -n DEV:看每个网卡的流量和错误包。
这套组合拳下来,机器的整体画像基本就有了。接下来才是定位哪个维度出了问题,再钻进去细查。很多新手一上手就top,跳过了系统级指标,结果进程列表里全是自己的应用进程,看不出问题在哪,就是因为缺少全局视角。
1.2 机器基线是什么,为什么这么关键
基线就是这台机器在“健康状态”下的指标区间。比如一台 4 核 8G 的 Web 服务器,平时 CPU 使用率稳定在 30% 到 50%,load average 长期 2 到 3,内存 free 保持 1G 左右,磁盘 await 在 5ms 上下。这些数字就是它的日常指纹。
没有基线,你很难判断当前指标到底算不算异常。同样是 load average 5,对一台 2 核的旧机器来说可能是灾难,对一台 32 核的新机器来说可能只是小波动。我见过不少同事,机器性能出问题后连夜加班,查了半天发现是“正常业务高峰”,白折腾一场。所以,有条件就通过监控系统把 CPU、内存、磁盘、网卡这些基础指标收集起来,至少要保留两周以上的历史曲线,再谈性能排查。
没有监控系统的话,从今天开始每晚跑一遍sar -o把数据落到本地文件,也是给未来的自己留一条后路。
1.3 排查安全问题:别在生产环境乱动
这里我额外想加一段“自我约束”。排查性能问题时,尤其是线上环境,有两条底线要守住:
- 第一条,尽量不要在生产环境直接重启服务。重启会清掉进程的运行时长、缓存热度和现场上下文,很多问题重启之后就再也查不到了。
- 第二条,谨慎使用会改变系统运行状态的命令。比如
sysctl -w临时调参数、drop_caches清缓存、kill进程,这些都是“有副作用”的操作,最好先确认当前状态再动手。
另外,排查过程随手记录时间线和操作步骤。每执行一条命令,把输出存到文件里,后面复盘时会省太多事。我自己吃过亏,排障排到一半忘了哪条命令改了什么参数,最后只能从头再看一遍日志,那一夜的经历至今印象深刻。
2. CPU 维度:从 load 到热线程的层层定位
2.1 load average 别当 CPU 使用率看
很多刚入门的朋友看到 load average 高,第一反应就是“CPU 爆了”。其实 load average 统计的是“处于可运行状态(R)和不可中断状态(D)的进程数”,它既包含等待 CPU 的进程,也包含在等待磁盘 IO、等待锁等资源的进程。换句话说,load 高不一定是因为 CPU 跑满了,也可能是大量进程卡在 D 状态等 IO。
举个例子:某台机器在 IO 故障期间,磁盘读写全部悬停,一堆进程进入不可中断睡眠,load 能飙到几十,但你看 CPU 利用率可能只有百分之几。这时候如果按“CPU 爆了”去扩容加核,问题一点也不会缓解。判断 CPU 是否真的是瓶颈,核心指标是 vmstat 的 r 列和 mpstat 的 us/sy 占比,而不是 upsidedown 只盯 load。
2.2 CPU 排查三步走:系统 → 进程 → 线程
我在实际排查 CPU 问题时,习惯按三层去找:先系统层,再进程层,最后线程层。
第一步,系统层用vmstat 1 3,观察 us(用户态 CPU)和 sy(内核态 CPU)的分配。如果 sy 占比很高,说明系统调用、内核线程、中断处理消耗了大量 CPU,接下来要重点看是不是上下文切换过多、软中断过多或者系统调用太频繁。如果 us 高,说明用户态计算密集,需要去进程层找具体是哪个程序在算。
第二步,进程层用top -b -n 1 -o %CPU | head -20,把所有进程按 CPU 使用率排序,找出热度最高的几个进程。这里要注意,看的是进程累计 CPU 使用率而不是瞬时值,有些进程是间歇性高 CPU,容易漏掉。
第三步,线程层是关键。top显示的是进程级别,但一个进程里有几十上百个线程,想知道到底是哪一段代码在烧 CPU,就得用:
top -Hp <PID>或者
pidstat -t -p <PID> 1 5这两条命令都能按线程查看 CPU 占用。定位到具体线程后,再用jstack(Java 应用)、perf top(C/C++ 应用)或者gdb attach去看线程栈,揪出正在执行的热函数。
2.3 单核瓶颈与软中断:容易被忽略的 CPU 盲区
单核瓶颈是个很经典的坑。一台 16 核的机器,整体 CPU 使用率只有 20%,但业务却明显卡顿。原因很可能是某个单线程应用刚好被调度到了同一个核上,那个核的 CPU 使用率已经 100%,其他核却闲着。
遇到这种情况,用mpstat -P ALL 1 3可以直观看到每个 CPU 核的使用情况。如果发现某个核已经跑满,其他核很空,就去查这个进程的 CPU 亲和性(sched affinity)和线程分布。有时候内核把一个网卡的硬中断都绑到了同一个 CPU 上,也会造成单核打满。
软中断也是 CPU 维度的高频问题之一。网络流量大的时候,网卡产生的中断会消耗 CPU,尤其是多队列网卡没有正确开启 RPS/RSS 时,所有中断会集中在一个核上。平时排查的时候,看到vmstat的 in(中断数)特别高,或者mpstat -P ALL里某个核的si(softirq)占比超过 15%,就要往网卡中断方向多想一步了。
2.4 CPU 相关命令速查
| 命令 | 用途 | 关键输出 |
|---|---|---|
uptime | 查看 load average | 1、5、15 分钟负载 |
vmstat 1 5 | CPU/内存/IO 整体画像 | r/b、us/sy/wa、si/so |
mpstat -P ALL 1 3 | 每个核的 CPU 使用率 | us/sy/wa/si 各核分布 |
top -Hp <PID> | 进程内线程 CPU 排序 | 线程 ID 和 CPU 占比 |
pidstat -t -p <PID> 1 5 | 线程级统计数据 | 线程名、CPU 占比 |
perf top | 实时热点函数 | 函数名和采样占比 |
cat /proc/interrupts | 看中断分布 | 核对硬中断绑核情况 |
3. 内存维度:free 只是第一步,Swap 和 OOM 才是重心
3.1 free 怎么读:used 小不代表没风险
free -m是我见过被误读得最惨的命令之一。很多人只看第一行的 used 和 free,觉得 free 还有 1G 就放心了。但实际上 Linux 对内存的管理逻辑是“能缓存就缓存”,free 里的空闲内存本来就应该小,因为系统会把闲着的内存拿去做 page cache。真正需要关注的是 available 这一列,它代表“不需要 swap 就能直接分配给新进程的内存”估算值。
所以在排查内存时,我更关心以下三个点:
- available 是否持续降低。
- swap 是否开始被使用(
vmstat里的 si/so)。 - 有没有进程的内存占用在不断增长(内存泄漏信号)。
3.2 page cache 与 drop_caches 的取舍
很多人在内存紧张时会想到清缓存,用:
echo 3 > /proc/sys/vm/drop_caches确实有效,要么也不会被人口口相传。但我要说,生产环境慎用。page cache 存在的意义是加速对磁盘上文件的再次读取,你把缓存清了,下一次访问同一批文件就得重新走磁盘,反而带来一波 IO 延迟。我曾经在一台数据库主机上试过一次,清完缓存后紧接着一次全表扫描,直接让 IO 等待飙到天上,业务小范围抖动,教训相当深刻。
如果内存是真的被进程占满,而不是被缓存占满,那么drop_caches也救不了你。正确做法是定位到那个吃掉内存的进程,看它是不是有内存泄漏,或者是不是需要给这台机器扩容。清缓存只适合“回收磁盘缓存来应急”这样的场景,不适合当作日常调优手段。
3.3 Swap 与 swappiness 调优是怎么回事
Swap 的存在让 Linux 可以把不常用的内存页换到磁盘上,腾出物理内存给活跃进程用。但问题在于,磁盘和内存的速度差了好几个数量级,一旦系统开始频繁 swap in/out,性能会肉眼可见地恶化,vmstat 里的 si/so 会持续有非零值,这时候你敲命令都能感觉到延迟。
影响 Swap 行为的核心参数是vm.swappiness,取值范围 0 到 100,值越大内核越倾向于把匿名内存页换出。默认值通常是 60,对于数据库、缓存服务这类对内存延迟敏感的应用,我一般会调低到 10 左右,让内核优先回收 page cache 而不是把进程内存换到磁盘。
sysctl vm.swappiness=10 echo 'vm.swappiness=10' >> /etc/sysctl.conf不过调这个参数前要看业务场景。如果机器内存本身很小,应用又能容忍换页,保留默认值反而能让系统更平滑;如果内存足够充裕,却看到 swap 还在涨,那不是参数问题,是某个进程的内存占用失控了。
3.4 内存泄漏与 OOM 实战
内存泄漏是排查过最磨人的问题象限。进程 RSS 只涨不降,物理内存被慢慢吃光,最后触发 OOM Killer 把进程杀了。有一次在处理某核心服务的内存问题时,我就犯了只盯top的错误——top 里看不出每个进程的内存变化趋势,后来改用:
pidstat -r 1 5按进程维度持续采样 RSS 变化,才发现了症结:一个后台任务持有的缓存对象只增不减,每次运行完没有释放,最终把系统内存全部耗尽。定位到之后,修复代码、释放对象、观察了一段时间,RSS 曲线才恢复平稳。
真到 OOM 发生的场景,第一件事是去dmesg看内核日志:
dmesg | grep -i -E 'oom|killed process'这条命令会显示出 OOM Killer 在什么时候、杀掉了哪个进程、当时系统内存状况如何。有了这些信息,你才能判断是某个进程异常膨胀,还是整体内存规划不足,而不是急着重启服务。
4. IO 维度:iostat 怎么读,IOWait 为什么唬人
4.1 iostat 关键列:%util、await、aqu-sz
IO 相关的问题最容易被“只看 %util”带偏。iostat -x 1 3的输出里有几列需要重点理解:
%util:设备忙碌的时间百分比,表示在采样周期内,IO 请求占用磁盘的时间比例。传统机械盘上 %util 接近 100% 基本就是瓶颈了,但 SSD 和 NVMe 是并行处理的,%util 100% 不代表设备已经饱和。它更像一个“忙碌度”参考,不是绝对的瓶颈指标。await:IO 请求从进入到完成的平均耗时,包括排队时间和服务时间。svctm:设备自身的平均服务时间。aqu-sz:平均队列长度。
判断磁盘是否真的有瓶颈,我主要看 await。如果 await 远高于磁盘的正常响应时间(比如机械盘超过 50ms,SSD 超过 20ms),说明 IO 请求在排队,磁盘快撑不住了。如果 await 不高但业务还是慢,那问题多半不在磁盘,而是在文件系统、应用层锁上。
4.2 从负载到进程:iotop 和 pidstat 怎么用
iostat告诉你磁盘是瓶颈,但它不会告诉你谁在制造这些 IO。要按进程定位 IO,用:
iotop -o -P这个命令只展示正在发生 IO 的进程和线程,刷新频率默认 1 秒,能很直观地看到每个进程的磁盘读写速度。如果系统里没有 iotop,可以用:
pidstat -d 1 5它按进程维度输出每秒的读写字节数和 IOPS,结果虽然没有 iotop 那么直观,但胜在轻量、对生产环境影响小。
我曾经排查过一个奇怪现象:监控显示某台机器的磁盘 IOPS 很高,但真正跑到iotop一看,占大头的是一个日志采集 agent,它在全速读取历史日志文件。原因是 agent 配置了错误的日志路径,把整个目录扫了一遍,白白消耗了一整天的磁盘 IO。这种问题不按进程看,是永远定位不到的。
4.3 IOWait 高不一定等于 IO 慢
vmstat里的 wa 列代表 CPU 等待 IO 完成的时间占比。很多人一看 wa 高就断定磁盘瓶颈,但 wa 高其实和“CPU 在等 IO”“进程卡在 D 状态”相关,并不等同于磁盘慢。还有一种可能:大量进程在竞争同一个文件系统的锁,wa 高但磁盘利用率却不高。这时候如果只升级磁盘硬件,问题依然会再出现。
所以遇到 wa 高,我会同时看两个地方:
- iostat 的
await和aqu-sz,确认磁盘服务能力是否真的不够。 ps -eo state,pid,cmd | grep '^D',看哪些进程处于不可中断睡眠,这些进程在等什么资源。
如果磁盘指标健康、D 状态进程却很多,就要查进程是不是在做网络文件系统的 IO,或者在等待某种内核锁了,IO 子系统可能只是表象。
4.4 inode 耗尽这类隐藏问题
还有一个高频坑,是磁盘空间明明还有几十 G,但应用报“No space left on device”。这种情况十有八九是 inode 用完了。每个文件系统能创建的文件数量是有限的,由格式化时指定的 inode 数量决定。大量小文件会快速消耗 inode,尤其是缓存目录、临时目录、消息队列落盘目录。
排查命令很简单:
df -h df -idf -i输出的 USE% 就是 inode 使用率,超过 90% 时就该准备清理了。遇到 inode 耗尽,最快的临时解决方法是删掉一批存档、日志或临时文件,长期方案是重新规划文件系统,格式化时加大 inode 比例,或者在应用层定期清理文件。
5. 网络维度:从网卡到协议栈的排查链路
5.1 网络排查的基本链路:先分层,再对号入座
网络问题比其他三类更抽象,因为“慢”可能发生在链路层、网卡层、协议栈、应用层任意一环。我的排查习惯是分层递进,先看物理网卡有没有丢包,再看协议栈有没有异常,最后看应用层连接状态。
推荐的开场命令:
sar -n DEV 1 3 ethtool -S eth0 | grep -E 'rx|tx|drop|err' ss -ssar -n DEV看整体流量和错误包;ethtool -S看网卡计数器,丢包、错误、rx/tx 队列 drop 都会在这里体现;ss -s看系统当前 socket 统计,ACCEPT 队列、重传相关的计数器都在协议栈里。
5.2 网卡统计与软中断丢包
网卡层的丢包很经典。有一次线上服务间歇性延迟升高,从应用层看是 TCP 超时重传,但始终定位不到根因。后来在ethtool -S里发现 rx_missed_errors 在持续增长,才确认是网卡接收队列溢出,因为中断处理不过来,包在硬件缓冲区就被丢掉了。这种问题常见的解决方向是调整网卡 ring buffer(ethtool -G),或者开启 RSS/RPS 让多核分担中断。
值得留意的一个内核计数器是/proc/net/softnet_stat,它记录了软中断处理状态。每行代表一个 CPU 核,如果第二列的 dropped 不为 0 且在增长,说明软中断层丢过包,网络流量超过了 CPU 处理能力。这时候开启 RPS 或者调整中断亲和性,往往能立竿见影。
5.3 TCP 连接队列与重传
再往上一层,到 TCP 协议层。两个非常常见的故障点:
一个是 listen 队列溢出。应用并发高、accept 不及时,新连接就会堆积在内核的 accept queue。用:
ss -lnt看Recv-Q这一列,如果长期不为 0,说明有连接等在队列里没被应用接受,客户端就会感觉连接很慢。这时需要查应用线程数、连接处理速度,必要时调大 backlog 或者优化 accept 逻辑。
另一个是 TCP 重传。重传过多通常意味着链路有丢包,或者对端处理不过来。用:
sar -n TCP,ETCP 1 3查看retrans字段,如果重传率(retrans/outsegs)持续高于 1% 甚至更高,网络质量肯定出了问题。此时回到网卡层查丢包,再用ping -f或mtr看链路里哪一跳有问题,能一路定位到物理链路还是对端服务问题。
5.4 实用抓包技巧
排查网络问题,抓包虽然重,但没法绕过。tcpdump是必备技能,关键时刻用一两条过滤条件就能真相大白。
例如只看某个端口的握手包:
tcpdump -i eth0 -nn 'tcp port 8080 and (tcp-syn|tcp-ack)'再看有没有重传:
tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-synack) != 0'抓包文件可以直接加-w file.pcap落盘,再用工具分析。实际经验是:抓包点到为止,先看实时抓几秒能确认问题方向,不要长时间全量抓包,否则会在高流量下把自己机器也拖垮。
6. 四维交叉:真的性能问题往往是组合拳
6.1 一次“四维同高”的故障拆解
单独看每个维度的指标是一回事,真实线上问题往往是几个维度纠缠在一起。我印象很深的一次故障是这样:监控告警显示核心服务 CPU 使用率连续 10 分钟高于 90%,从 CPU 维度入手查了半天,进程热点也看了,没有异常,代码也优化过了,还不见改善。
后来把vmstat和free拼在一起看,才发现内存 available 早就见底了,Swap 区域正在疯狂换入换出。页面换进换出消耗了大量系统 CPU(sy 高),看起来是 CPU 瓶颈,根因却是内存不足。把内存扩容后,Switch 停止,CPU 自动降回正常水位。那次之后我养成了一个习惯:任何 CPU 问题先看内存和 swap,任何 IO 问题先看内存和 CPU,避免陷入单维度思维。
6.2 哪些症状容易被单维误导
我整理过几类容易踩坑的症状:
- load 高、CPU 不高:优先怀疑 D 状态进程和 IO 阻塞。
- CPU sy 高、us 不高:考虑上下文切换、系统调用频繁、软中断过多。
- IO await 高、%util 不高:多查网络文件系统、应用锁、文件系统锁。
- 内存 available 低、没有 swap:不一定是泄漏,可能是 page cache 策略或应用确实需要这么多内存。
- 网络延迟高、网卡没丢包:可能是对端服务端 accept 队列溢出,也可能是跨机房的链路抖动。
6.3 排查时的先后顺序建议
综合以上情况,我给一个相对标准的排查顺序:
- 先抓全局快照:
uptime+vmstat+free+iostat+sar -n DEV,看四个维度谁在异常。 - 根据异常维度深入:CPU 查 mpstat/perf,内存查 pidstat/smaps,IO 查 iotop/await,网络查 ss/ethtool。
- 如果单维度怎么查都查不出问题,回看“跨维度嫌疑”,比如 CPU 问题查 memory,IO 问题查 memory。
- 定位到根因后,直接针对性处理,而不是头痛医头。扩容前先确认是不是真的资源不足,还是配置、代码、锁导致的假性不足。
这套流程我用了很多年,确实能覆盖绝大多数线上性能故障。
7. 症状、命令与排查思路速查表
7.1 常见症状→检查→定位→解决速查表
| 症状 | 先检查 | 定位手段 | 常见解法 |
|---|---|---|---|
| load 高、CPU 不高 | vmstatb 列、psD 状态 | iostat、iotop | 处理磁盘/网络文件系统阻塞 |
| CPU us 高 | top、mpstat | perf top、jstack | 优化代码热点、扩容 |
| CPU sy 高 | vmstatcs 列 | strace、pidstat -w | 减少系统调用、检查上下文切换 |
| 单核 100% | mpstat -P ALL | taskset、中断亲和性 | 绑核、开启 RPS |
| available 持续降低 | pidstat -r | /proc/<pid>/smaps | 修内存泄漏、调大内存 |
| swap 频繁 | vmstatsi/so | swap高进程 | 调整 swappiness、扩容内存 |
| 磁盘 await 高 | iostat -x | iotop | 降低 IOPS、更换磁盘 |
| wa 高、磁盘正常 | psD 状态 | strace、文件系统日志 | 查应用等待什么资源 |
| 网卡丢包 | ethtool -S | mtr、ping | 调整 ring buffer、联系网络 |
| 连接队列溢出 | ss -lntRecv-Q | perf、应用日志 | 增加 accept 线程、调 backlog |
| TCP 重传高 | sar -n TCP,ETCP | 抓包分析 | 定位链路质量问题 |
7.2 把排查结果沉淀成基线与监控
排查完一次故障,如果只是修完就完事,下次很可能在同一个坑里再摔一次。我自己的习惯是:把每次故障的指标快照、根因、处理动作写进一页纸报告,然后反哺到监控系统。比如这次是内存 available 低于 15% 持续 10 分钟触发的问题,我就把监控阈值设定为 20% 持续 5 分钟报警;这次是网卡 ring buffer 溢出,我就给网卡指标加上丢包计数器监控。
最后再分享一个非常实用的小技巧:每次排障开始之前,先执行:
date; uptime; vmstat 1 3 > /tmp/perf_debug.txt把时间线和初始状态落到文件里。等排查结束,再翻回来看这份最初的快照,往往能看到一开始被忽略掉的线索。性能排查本质上是和系统“对话”的过程,指标会告诉你它哪儿疼,命令能让你看到疼的部位,而经验决定你能不能从中听出真话。