做 Linux 性能排查,绕不开 CPU、内存、IO、网络这四大维度。不管你是管理机房里的老物理机,还是维护云上随手开的弹性实例,也不管上面跑的是数据库、Web 服务还是消息队列,出问题时百分之九十的根因都藏在这四个方向里。
这篇内容适合两类人:一类是刚接手服务器、遇到卡顿不知道从哪下手的初级运维和开发,另一类是已经写过几年代码、但习惯“遇事不决就重启、扛不住就加配置”,缺少系统排查思路的老朋友。我会把每个维度的关键指标、常用命令、判断依据讲清楚,最后用一个真实故障复盘串起整个流程。你照着这个顺序查,至少能把“排查靠感觉”变成“排查有章法”。
1. 内容整体设计与思路拆解
1.1 为什么是这四个维度
一台 Linux 服务器本质上就是一个计算系统:CPU 负责算,内存负责存取中间过程,磁盘负责持久化,网卡负责和外界打交道。应用出任何问题,最终都会映射到这几个资源上,要么是某一种资源被打满,要么是几种资源之间互相拖累。
举几个最常见的连锁反应。内存不足时,系统会启用交换分区(swap),把内存里的数据页挪到磁盘,这时候 CPU 使用率可能不高,但iowait会突然拉高;磁盘 IO 变慢,进程在等待读写时被阻塞,内核会让出 CPU,上下文切换次数跟着飙升;网络包处理不过来,软中断占满单个 CPU 核,又会让业务响应变慢。你说不清到底是哪个环节出了问题,是因为它们本来就不是独立运行的。
所以排查思路绝不能是“感觉业务卡了就去查数据库”,而是先把 CPU、内存、IO、网络四张牌都翻出来,看谁的数据异常,再顺着异常链路往下钻。这也是我写这份手册时坚持的原则:每章先讲清指标,再给命令组合,最后讲判断逻辑,避免你背了一堆命令到现场还是不会用。
1.2 排查顺序与通用流程
我在实际处理故障时,遵循一套固定流程,看起来慢,其实最省时间。
第一步是“定现象”,弄清楚用户反馈的是响应慢、超时,还是完全连不上。第二步是“铺指标”,打开终端同时记录top、vmstat 1、iostat -x 1、sar -n DEV 1几路输出,让各维度数据在同一时间线上交叉对照。第三步是“缩范围”,从整个系统缩到进程,从进程缩到线程,再定位到具体系统调用或文件。第四步才是“动工具”,比如用perf抓调用栈、用strace跟踪进程行为。最后必须“验证”,修改完参数或代码后至少观察一个业务周期,确认问题消失才算结束。
这里我特别想强调“铺指标”的意义。很多人习惯只看一个工具,比如top里负载高了就直接怀疑 CPU,结果往下查半天,发现是磁盘 IO 拖的。如果一开始就让四维数据同时滚动,很快就能看出到底谁先异常,谁是被连累的。
1.3 工具链速览
为了不让后文陷入命令堆砌,先把我常用的工具链列个表,后面每个维度还会展开讲用法。
| 排查维度 | 常用命令 | 主要用途 |
|---|---|---|
| 全局状况 | top、uptime、vmstat 1 | 负载、CPU 总览、上下文切换、内存摘要 |
| CPU | mpstat -P ALL 1、pidstat 1、perf top | 分核利用率、进程 CPU、热点函数 |
| 内存 | free -h、cat /proc/meminfo、slabtop | 可用内存、缓存、内核内存分配 |
| IO | iostat -x 1、iotop、pidstat -d 1、fio | 磁盘吞吐/延迟/饱和度、进程 IO、基准压测 |
| 网络 | sar -n DEV 1、ip -s link、ss -lnt、ethtool -S | 网卡流量、丢包、连接队列、接口统计 |
这套工具全部是 Linux 自带或开源社区的常见命令,不需要额外授权,属于运维的基本装备。记住一个原则:先用全局命令缩小范围,再用定位命令找到进程,最后用追踪命令观察行为,而不是上来就跑strace这种重武器。
2. 实操第一维:CPU 排查的关键指标与命令
2.1 从 load average 说起:为什么负载高不代表 CPU 忙
很多人看到uptime输出的 load average 是 3.8,第一反应是“CPU 要爆了”。这个判断只对了一半。load average 在 Linux 里其实是当前处于可运行状态和不可中断睡眠状态的进程数的移动平均,单核 CPU 如果负载长期超过 1.0,才说明确实有任务排队;四核机器负载 4.0 才算满载,负载 3.8 看起来吓人,实际上 CPU 还有余量。
更要命的是,不可中断睡眠进程(D 状态)也会计入负载。当磁盘响应很慢时,大量进程陷入 D 状态等待 IO,负载照样飙高,但此时 CPU 使用率反而可能很低。判断这个问题不需要复杂工具,top第一行的us、sy、wa、id就是核心。us是用户态占用,sy是内核态占用,wa是等待 IO 的时间占比,id是空闲时间。如果看到wa很高、us不高,那 CPU 只是“陪着”磁盘一起遭罪。
我见过太多人被负载数字吓到,立刻扩容加机器,结果新机器上没有业务流量,负载自然降下来,问题却换个时间又出现。这种操作等于头痛医脚。正确的做法是先花半分钟看top里的wa和sy,再决定往 CPU 还是磁盘方向走。
2.2 用 mpstat、pidstat、perf 定位 CPU 消耗点
确认 CPU 本身繁忙之后,第一件事是搞清楚是单个核心忙,还是所有核心都忙。mpstat -P ALL 1能打印每个物理核的使用率。我处理过一类典型问题:机器是四核,但中断都压在 CPU0 上,网卡收包导致 CPU0 跑到 90%,其他三个核却闲着。这就不叫 CPU 资源不足,而叫中断分布不均,后面调整中断亲和性就行,加机器完全没用。
定位到具体进程用pidstat 1,它会持续输出每个进程的 CPU 占用。生产环境里我习惯用top -b -n 1抓快照,再用pidstat连续观察几秒,因为单次快照可能恰好落在业务波峰或波谷上。数据稳定后,把 CPU 占用最高的几个进程记下来。
如果用户态进程占用很高,下一步我一定会上perf。perf top可以实时显示热点函数,perf record -g配合perf report能看到完整的调用链。举个例子,如果热点集中在某个加锁函数上,说明并发冲突严重;如果热点在内存分配函数上,可能是有大量临时对象。这些信息是top给不了的。
注意:
perf在内核开启权限限制时需要先调整perf_event_paranoid,云上环境如果改不了,可以用perf top --call-graph dwarf,还是能看到用户态调用栈。
2.3 上下文切换与软中断:两个容易被忽略的盲区
vmstat 1输出里有个cs列,代表每秒上下文切换次数。数字大并不一定是坏事,高并发本身就是高切换,但如果切换数量在流量没变的情况下突然翻倍,就要警惕。用pidstat -w 1能看到每个进程的主动切换和被动切换数量,配合判断是进程在抢时间片,还是频繁陷入内核。
软中断是个更隐蔽的坑。高吞吐网络场景下,单个 CPU 核被软中断占满,业务代码反而分不到时间片。判断方法很简单:连续两次cat /proc/softirqs,对比哪个中断类型增长最快;再看/proc/interrupts,确认中断是不是都集中在同一个核上。解决方案常见的是打开网卡多队列并设置 RPS,把包处理分散到多个核。
CPU 维度的排查经验总结成一句话:先确认 CPU 是不是真凶,再确认是真忙还是被 IO 拖累,最后才判断是加资源还是调分布。真忙也只是表象,真正的问题在热点函数里,跑一次perf比猜快得多。
3. 实操第二维:内存排查的实用方法
3.1 先看懂 free -h:available 才可信
free -h大概是每个运维每天都会敲的命令,但我发现很多人只看used,一看内存用了 90%,立刻紧张,以为快爆了。实际情况往往不是这样。Linux 对内存的使用策略是“能用则用”,空闲内存也会被拿去当文件缓存(page cache),这部分内存是可以随时释放的。
free -h里最关键的数字是available,它表示在不触发大量 swap 的假设下,还能分配给新进程的内存。available包含部分可回收的缓存,所以在大多数场景下,available充足就意味着系统内存没到危险线。buff/cache高不要慌,free第二行已经告诉你available这个修正值了。
/proc/meminfo是比free更细的原始数据,重点看MemAvailable、Dirty、Writeback。如果Dirty数字持续很大,说明有大量脏页等着写回磁盘,这时候内存层看似没事,IO 层可能已经开始背压力了。把内存和 IO 连起来看,是排查高负载问题的关键习惯。
3.2 swap 与内存回收:判断是否真的内存不足
内存不足的典型信号是vmstat 1里的si(从 swap 读入)和so(写入 swap)长期不为 0。我见过一例,某句动态脚本每过一段时间就涨内存,触发大量 swap 写入,磁盘被拖慢,iowait飙到 70%,但 CPU 使用率很低。很多人会以为是磁盘坏了,实际上根因是内存泄漏,不清理泄漏源头,换再好的磁盘也没用。
提到 swap 就绕不开vm.swappiness参数,默认 60。它表示内核在使用 swap 的积极程度,数值越低越倾向使用内存,越高越早换出空闲页。如果机器内存充足,我会建议降到 10 左右,减少不必要的 swap 写入,但不要把业务直接放在 swap 上,那是拿磁盘延迟换内存空间,得不偿失。
回收机制里还有个细节:千万别有事没事就echo 3 > /proc/sys/vm/drop_caches。手动清缓存会短暂释放内存,但代价是后续每次读取文件都要重新访问磁盘,业务延迟反而变高。清缓存应该只在测试环境用来模拟冷启动,生产环境正确做法是让内核自己管理缓存生命周期,或者通过调整dirty_background_ratio、dirty_ratio控制回写节奏。
3.3 内存泄漏与 OOM 的排查思路
定位内存泄漏,最直观的是监控进程 RSS 的变化。ps aux --sort=-rss能帮我看当前内存占用排行,但更可靠的方法是连续采样:每隔几分钟记录/proc/<pid>/status里的VmRSS,拉一个时间序列。如果 RSS 只涨不降,或者涨到某个水位后突然消失,大概率是泄漏之后被 OOM Killer 杀了。
OOM 发生后,内核会在日志里留下记录,dmesg或journalctl -k里能找到 OOM Killer 调用信息,里面会列出当时内存占用最高的进程和它的内存分率评分(oom_score)。这些日志是最直接的证据,比猜靠谱得多。
开发侧配合的话,稳定复现问题后用内存分析工具跑一遍,比如用 Valgrind 检查非法内存访问,或者用编译器的 AddressSanitizer。有些泄漏确实只有生产环境才触发,建议在测试环境先构造接近生产的压力,大多数问题都能提前暴露。
内存排查这条线的核心认知是:cache 高不是罪,swap 频繁才是病灶;进程被杀不要急着调 oom_score,先找到它为什么吃了那么多内存。
4. 实操第三维:IO 排查的深入路径
4.1 如何区分 IO 慢和业务本身的慢
有时候登录卡、查询慢,业务方会直接说“数据库卡死了”。我的习惯是先看iostat -x 1,如果%util接近 100%,await很高,说明磁盘响应确实跟不上。但%util这个数字也要辩证看,它表示设备忙碌时间比例,不代表已接近物理极限,对 SSD 尤其如此:SSD 能并行处理队列,%util100% 时延迟可能依然很低。
看 IO 性能,重点是await(平均每次 IO 请求在队列中的等待与处理时间)、r/s、w/s、rkB/s、wkB/s。await高说明请求排队,r/s和w/s反映请求频率,吞吐量要结合这两个指标判断。比如机械盘读延迟约 10ms,SSD 只有 0.1ms 量级,同一套数字在两种盘上的结论完全不同。
还有个经典误会:iowait高不代表磁盘坏了。它只是表示 CPU 空闲等待 IO 的时间比例。要确认磁盘领域,必须看iostat的延迟和队列数据,再配合top里 D 状态进程数量。D 状态进程大量堆积,往往意味着确实有 IO 阻塞,这时候需要更细的工具。
4.2 iostat 字段速查与实战判读
| 字段 | 含义 | 参考判断 |
|---|---|---|
r/s、w/s | 每秒读/写请求数 | 突增说明访问模式变化 |
rkB/s、wkB/s | 每秒读/写吞吐量 | 接近设备上限,注意压测基线 |
await | 平均 IO 请求响应时间(含队列等待) | 机械盘超过 20ms 需要留意 |
%util | 设备忙碌时间占比 | 看延迟和队列,别单独看它 |
读请求和写请求要分开看。读多往往和业务缓存命中率有关,如果业务逻辑没问题但读请求居高不下,要检查是不是缓存层失效了;写多则要关注日志、消息队列、数据库提交频率,有可能一次小事务写了太多日志。
判断是不是应用层问题还有一个技巧:用pidstat -d 1看进程级别的读写速率,配合lsof -p <pid>找出进程打开了哪些文件。如果某个进程持续大量读写一个日志文件,很有可能是有问题的打印逻辑;如果大量读写数据库文件,则要去查慢查询和索引执行计划。
4.3 用 fio 做基准测试:给磁盘建立基线
没有基线就谈不上判断。新接手的机器,我会先用fio跑一套基础压测,摸清磁盘的真实能力,以后出问题就有对比参照。
fio -name=random_read_test \ -rw=randread \ -bs=4k \ -size=1G \ -ioengine=libaio \ -direct=1 \ -iodepth=16 \ -runtime=30 \ -time_based这个命令测试 4KB 随机读的 IOPS 和延迟,-direct=1表示绕过 page cache,测的是磁盘真实性能,-iodepth=16是模拟队列深度。结果会输出iops、lat (usec)等数字。结束之后再测一次顺序写,两组数据就够日常对比了。
注意一定不要在业务盘上直接压测。云主机的数据盘如果需要测试,建议先把业务停掉或迁移,或者用一块空闲盘做基准。我在生产环境里见过有人想“看看磁盘还行不行”,直接对正在跑数据库的盘执行 fio,结果把本就紧张的 IO 彻底打爆,这个坑确实值得强调。
4.4 定位产生 IO 的进程
找到 IO 源头有两个方向。第一个方向是进程维度:iotop按 IO 排序实时显示,pidstat -d 1只输出磁盘读写,两者都直观。第二个方向是文件维度:先用lsof +L1查看已删除但仍被进程占用的文件,这种情况常见于日志被删除、进程还持续写入,磁盘空间明明没满却一直 IO 很高。
如果还查不到,可以用strace -e trace=read,write -p <pid>观察进程系统调用。这个命令会产生巨大开销,必须在低峰期或测试环境执行,观察几秒就停。我之前用它抓出一个诡异的案例:某个进程周期性读取一个根本没人访问的几 MB 文件,根源是初始化脚本里埋了一个文件状态检查,每次执行就做一次完整读。
IO 排查最后补一句:文件系统层也可能产生非直觉浪费。比如使用日志型文件系统时,元数据更新也要写日志,小文件频繁创建删除会制造大量写放大。如果业务场景确实需要海量小文件,考虑调整文件系统的相关挂载参数或者重构存储方案,光调应用解决不了。
5. 实操第四维:网络排查的现场手法
5.1 先看网卡流量和丢包:用数据排除物理链路问题
网络排查的第一步永远是看网卡本身,别上来就 ping。sar -n DEV 1能给出每块网卡的入流量、出流量、包速率。如果入流量长时间顶着网卡带宽上限跑,说明带宽饱和,业务质变慢就不再是应用层问题,而是链路堵了。
丢包的判断用ip -s link,里面RX errors、RX dropped、TX errors会显示累计值。RX dropped持续增长,多半是网卡接收缓冲区(ring buffer)太小,包在硬件层就被丢弃;ethtool -S eth0是更细的统计接口,能区分是队列丢弃还是流量过大。
ping 测出来的延迟也不是万能的。ping 通只代表 ICMP 协议能通,TCP 层面的重传、连接建立失败,ping 根本看不出来。我见过两台机器 ping 延迟 1ms,但应用层不时超时,最后用nstat -az查 TCP 重传数,发现TcpRetransSegs不断在涨,网卡和线缆看着正常,实际是云平台底层限速导致偶发丢包重传。
5.2 连接状态与会话队列:用 ss 看清 TCP 层
ss -s能快速给出当前系统各 TCP 状态的汇总,ss -lnt看监听端口和 accept 队列。这里要特别注意,listen 状态的Send-Q表示 accept 队列最大长度,Recv-Q表示当前排队等待接受的连接数。如果Recv-Q一直非 0 且接近Send-Q,说明应用层 accept 太慢,新连接被内核积压,表现是客户端频繁连接超时。
排查时配合dmesg看有没有 "TCP: request_sock_TCP: Possible SYN flooding" 之类的提示。SYN 队列满、accept 队列满、TIME_WAIT 堆积,这三种现象的修复方式完全不同:SYN 队列满考虑net.ipv4.tcp_max_syn_backlog,accept 队列满要考虑net.core.somaxconn和应用层 close 连接的逻辑,TIME_WAIT 多则大多数情况等 60 秒自然消失,别急着开复用参数。
TIME_WAIT 状态经常被当成“有问题”。我的经验是,高并发短连接场景下 TIME_WAIT 多到几千甚至上万是正常的,它只是保证重传安全。真正要关心的是连接被主动断开的方向:大量 TIME_WAIT 出现在主动关闭方,服务端如果接收完请求后立即关闭连接,TIME_WAIT 就会堆积在服务端,这时候应优化应用逻辑,而不是盲目调小tcp_fin_timeout。
5.3 网络参数调优的几个安全实践
网络参数是最容易“凭感觉乱调”的部分。我看到不少配置文档上来就让人加 buffer、改时间戳,吓得赶紧拦下。调优必须基于观测数据,没有丢包证据就别碰丢包参数,没有重传证据就别动重传相关设置。
比较安全的起步参数是:
net.core.somaxconn = 1024 net.ipv4.tcp_max_syn_backlog = 1024 net.ipv4.tcp_fin_timeout = 30其中somaxconn影响 accept 队列,tcp_max_syn_backlog影响 SYN 半连接队列,tcp_fin_timeout控制连接关闭后 TIME_WAIT 的保留时间。修改后观察业务表现,不要一次改多个量,否则没法判断是谁起作用。
拥塞控制算法方面,Linux 默认的算法在大多数网络环境下都不差,网速再快也顶不住应用层一次发送过多数据导致的拥塞。如果需要开启更激进的现代拥塞控制算法(比如某些专门针对高带宽高延迟场景设计的实现),务必先在测试环境压测,确认不会对同链路上的其他业务造成影响。
网络排查的核心经验是:链路层、传输层、应用层分开看,用统计输出替代猜测,任何参数改动都要能回滚。
6. 实操过程与核心环节实现:一次真实故障排查复盘
6.1 故障现象与初始观察
模拟项目 X 是一套内部管理系统,部署在一台四核、8GB 内存的云主机上,数据库和业务进程共处一机。业务方反馈每天下午三点到五点响应变慢,登录要卡十几秒,峰值时段偶发超时,其他时间表现正常。
我登录后的第一件事不是查代码,而是同时开启四路观察:top看整体负载,vmstat 1看内存和上下文切换,iostat -x 1看磁盘,sar -n DEV 1看网卡。十分钟后,数据已经指向了问题方向。
6.2 四维数据交叉定位
top显示 load average 3.8,CPU 使用率us65%,wa25%。如果只看 CPU,确实不算低;但wa占了四分之一,说明 CPU 有相当时间在等 IO。
iostat -x 1显示数据盘%util突破 90%,await达到 30ms 左右。机械盘的物理极限大概就在这个量级,磁盘确实处于高度繁忙状态。
vmstat 1里si和so都是 0,说明没有明显 swap 交换。内存当前不是主要矛盾,但进程的上下文切换(cs)偏高,这是因为大量进程在等待 IO 被切换出去。
mpstat -P ALL 1显示四核都有使用,没有单核中断堆积问题。到这里,核心矛盾已经锁定:不是 CPU 不够,不是内存不足,也不是网络拥塞,而是磁盘 IO 成为瓶颈。
6.3 定位具体来源并解决
继续用pidstat -d 1观察进程级别的磁盘读写,业务进程的读请求持续高位。结合数据库慢查询日志,发现一个统计报表查询对一张千万行级别的表做了全表扫描,每次执行都会触发大量磁盘读。
修复方案分两步走。第一步是应急:给高频查询对应的表建立合适的索引,把全表扫描变成索引读取;同时调整连接池,减少无谓的重连导致额外 IO。第二步是长期优化:把统计报表的数据预计算,存到独立的汇总表中,读写分离,不再让实时查询直接压到数据盘。
调整后再次观察,%util降到 40% 以下,await回落到 5ms 左右,load average降到 0.8,下午高峰时段的超时消失。整个排查从登录到定位,用时不到半小时,其中真正花时间的不是执行命令,而是判断哪一项指标才是根因。
这个案例最有价值的地方在于:它是个典型的“三层连锁”问题——业务低效查询导致磁盘 IO 高,磁盘 IO 高又拖累了 CPU,CPU 等待磁盘才表现为“系统卡”。只往 CPU 或内存方向排查,很容易绕弯路。
7. 常见问题与排查技巧实录
7.1 高频问题速查表
| 现象 | 推荐命令 | 关键判据 |
|---|---|---|
| 系统负载飙升 | top、vmstat 1 | 先看us还是wa高 |
| CPU 占满 | mpstat -P ALL 1、pidstat 1 | 确认单核还是多核,再抓热点函数 |
| 内存频繁告警 | free -h、vmstat 1 | available是底线,si/so是危险信号 |
| 进程莫名被杀 | dmesg、journalctl -k | 查 OOM Killer 日志 |
| 磁盘龟速 | iostat -x 1、pidstat -d 1 | 对比await与%util变化 |
| 网卡大量丢包 | ip -s link、ethtool -S | 看 dropped 增长是否与流量相关 |
| 连接超时失败 | ss -lnt、dmesg | 看 accept 队列是否溢出 |
| 上下文切换剧烈 | pidstat -w 1 | 区分主动/被动切换的来源 |
这张表适合贴在办公桌上。遇到问题先找到对应行,然后按命令输出继续深入。
7.2 我踩过的几个坑
第一个坑是用top单次输出做判断。top默认是动态刷新,但很多人截图只截一帧。峰值只是一瞬间,基线才是参考。至少连续观察几十秒,或者结合监控面板的记录,才能判断 CPU 使用率是平稳还是剧烈波动。
第二个坑是在生产环境直接跑fio压测。数据盘上跑压测等于在本来就紧张的 IO 上再踩一脚,当时差点把业务压垮。从那以后我坚持:压测只用于提效验证或全新环境,生产环境优先用被动观测工具。
第三个坑是无脑执行清缓存和调内核参数。网上很多教程教人清 drop_caches、调整各种 buffer,灾时不排查,病时乱吃药,越调越乱。现在我的原则是每一项调整都要先记录原始值,再改一个量,验证完再改下一个,全程保留回滚方案。
第四个坑是忽略日志。内核日志里有 OOM 记录、网络栈异常、文件系统错误,这些通常是第一现场证据。有几次排查半天,最后发现journalctl -k早就把根因写在那里了。
7.3 留给自己的几条排查习惯
排查前先保存命令输出。top、vmstat、iostat的结果默认只在终端显示,出事时一边操作一边记录输出,方便后续回溯。可以用一行脚本把关键指标追加到文件:
while true; do echo "$(date) $(uptime) $(vmstat 1 2 | tail -1)"; sleep 10; done >> /tmp/perf.log另一个习惯是给机器建立基线。新环境接收时,用fio、sar把所有资源测一遍,把结果归档。没有基线,就很难判断“当前性能正常还是已经达到瓶颈”。
我在实际排查中,越来越习惯把四个维度当成同一时刻的并列数据来看,而不是一个一个单独查。CPU、内存、IO、网络从来就不是彼此孤立的,它们共享一条因果链。保持这个视角,很多故障的根因会在你等命令输出的时候就浮出水面。希望这份手册能帮你把“排查靠猜”彻底丢掉,改成“指标说话”。