
接到“系统慢”的工单是每个做Linux运维和服务端开发的人都躲不开的场景。用户不会给你更多线索顶多补充一句“网站转圈”“接口超时”或者“数据库卡住了”。新手最本能的反应是冲到服务器上先敲一下top看到某个进程CPU占用很高就下结论说“就是它的问题”。但真实世界的性能故障往往不是一颗CPU尖刺那么简单——磁盘I/O排队、内存换页、锁竞争、句柄耗尽、网络重传全都能够以“慢”这个字作为表象。我早期踩过最大的坑就是在“慢”面前太早进入了“优化模式”而没有先做“侦察模式”。这篇文章想分享的是我做Linux性能排查时沉淀下来的一套实战打法把一个模糊的“慢”翻译成可以量化的指标从机器级指标追到进程级证据最后落到根因修复和复盘。没有太花哨的命令基本都是在线上环境验证过的思路和工具适合Linux运维、服务端开发以及所有被“系统慢”三个字折磨过的人。1. 先把“系统慢”翻译成具体的技术指标1.1 用户的“慢”常见对应哪几类瓶颈不管用户的描述是“页面转圈”“接口超时”还是“报表出不来”从操作系统的角度看“慢”终究会落到几类核心资源上CPU计算量过大、线程频繁切换、锁竞争导致活锁内存物理内存不足触发swap进程被换入换出延迟飙升磁盘I/O设备队列排队、吞吐量饱和应用卡在读写等待上网络带宽占满、连接堆积、丢包重传整个请求路径被拖长。这几类还会交叉传染。比如CPU被打满后网络应用无法及时应答用户感知到的是“超时”swap剧烈时磁盘I/O同步变高磁盘故障导致文件同步卡顿上层依赖文件的服务也跟着慢。所以第一步从来不是猜而是先跑一组命令把可能的方向排除到只剩一两个。1.2 没有基线判断最容易误判每台服务器都有自己的“正常形态”。有的机器常年负载在5以上因为它本身就是高并发接入层有的机器常年负载不到0.5突然跳到3就已经很反常。如果不了解机器的历史表现单看绝对数字很容易被误导。我自己的习惯是日常就把同型号机器的关键指标存一份基线CPU核数、内存总量、平均负载、iowait占比、磁盘util。这不是什么高深监控哪怕每天手工记录一次都行。知道这台机器“正常的样子”是什么遇到异常时才能判断异常到底跳了多高、持续了多久。1.3 把命令按“侦察顺序”排好别东一下西一下很多新手的排查过程是随机敲命令top看两眼、free看两眼、再去看一眼日志最后什么结论也没有。我的建议是按下面的顺序走每条命令都只有一个目的看完再决定下一步先uptime看load average趋势判断问题是否正在发生再vmstat 1 5看CPU、内存、磁盘链路的整体状态若CPU有问题用top和pidstat定位进程若内存异常用free、smem、pidstat -r继续若I/O有瓶颈用iostat -x和pidstat -d找进程若怀疑网络用ss和sar -n检查连接与重传。这条链路的核心思路是先看机器再看进程最后看代码或配置。顺序错乱效率会低很多。2. 第一轮侦察uptime与load average到底在告诉你什么2.1 load average不是CPU使用率$ uptime 14:22:01 up 12 days, 6:04, 2 users, load average: 6.34, 5.12, 4.80很多人在这一步就把load average当成CPU使用率。其实它表示的是“运行队列长度”也就是过去一段时间内处于可运行状态和不可中断睡眠状态的平均进程数。注意不可中断睡眠状态通常是进程在等待磁盘I/O或其他资源所以load高不等于CPU忙也可能是大量进程卡在I/O上。比如一台8核机器load是8.0说明平均每个核有一个任务在排队已经处于饱和边缘换成16核机器load同样是8.0其实还很宽裕。所以在读load之前先确认核数用nproc一下就知道。2.2 1分钟、5分钟、15分钟怎么组合判断uptime给出的三个数分别是最近1分钟、5分钟、15分钟的平均负载。这三个数之间的大小关系比绝对值更重要1分钟明显大于5分钟和15分钟说明负载正在快速升高事情正在发生三个数都在高位并且基本持平说明这个状态已经持续了一段时间不是突发1分钟已经回落15分钟仍然很高说明刚过去一个长耗时高峰或者有一个长时间任务在收尾。这一眼就能判断问题是“正在发生”还是“已经过去”。如果已经过去就不必急着抓现场可以先看日志和监控曲线如果正在发生下一步立刻上vmstat。2.3 uptime能打住的情况和必须继续的情况有一种情况load很低但用户还是说慢。这说明瓶颈不在CPU运行队列而可能在内存交换、网络、应用锁或者远端依赖上。此时继续纠结负载没有意义应该转向free、ss、应用日志。另有一种情况load从0.5突然飙到10并且1分钟线还在往上走那基本可以确定系统某个资源出问题了。这时候不要再盯着load直接进入系统级扫描。3. 系统级扫描vmstat的一分钟把机器状态摊在桌上3.1 vmstat输出的关键列怎么读$ vmstat 1 5 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 2 0 0 1845600 12304 5671230 0 0 12 34 102 45 8 2 88 2 0 3 1 0 1832340 12304 5671340 0 0 567 10230 1350 678 12 6 61 21 0vmstat后面带两个参数表示每隔1秒输出一次连续输出5次。第一次输出的往往是开机以来的平均值参考意义不大关键是看后面的采样值。核心列如下区块列名含义procsr可运行状态进程数等CPU的队列长度procsb不可中断睡眠进程数通常代表等I/O阻塞memoryswpd已使用的swap空间单位KBmemoryfree空闲物理内存memorybuff/cache缓冲区与页缓存占用swapsi/so每秒换入/换出内存的大小iobi/bo每秒块设备读/写的大小systemin/cs中断次数、上下文切换次数cpuus/sy/id/wa/st用户态、系统态、空闲、I/O等待、被偷取时间占比3.2 r列和b列CPU排队还是I/O阻塞r列高而b列很低说明CPU已经成了瓶颈大量任务在排队等处理器b列长期不为0同时wa也很高说明进程正在等待I/O磁盘基本脱不了干系。比较有意思的是r和b同时高的场景CPU在忙于处理I/O路径上的中断和内核线程而进程又阻塞在设备等待上这时候看起来CPU也很忙磁盘也很忙像是“两头堵”。遇到这种情况不要急着优化业务代码先把I/O链路的真实压力降下来CPU占用往往也会跟着回落。3.3 si/so和wa/st是两组容易被忽略的报警信号si和so持续非零说明物理内存已经不够用系统在反复换页。这个状态比free数值低要严重得多因为换页是进程级别的“慢性死亡”响应延迟会变得非常不稳定。wa高说明I/O在拖CPU的后腿st高则是虚拟化环境特有的问题表示宿主机在抢占虚拟机的CPU时间片st超过20%时应用卡顿很可能不在自己机器内部而要去和虚拟机平台方确认资源争抢。4. 进程级定位从top到pidstat再到线程级4.1 用top之前先把筛选逻辑想清楚很多人top进去只看默认的进程列表然后挑一个CPU最高的进程开始分析。正确的做法是先想清楚当前需要验证什么假设。如果你怀疑CPU瓶颈在top界面按一下数字1看每个CPU核是否都打满按大写P按CPU排序按大写M按内存排序。如果怀疑线程层面的问题按H切换线程视图再按P排序会发现某个进程内部的单个线程CPU占用异常高。batch模式也很有用适合把历史快照留下来top -b -n 1 -o %CPU | head -30这条命令一次输出当前CPU最高的前30个进程适合写到日志文件里做事后对比。4.2 pidstat的三类报表CPU、内存、磁盘pidstat比top更适合做持续采样尤其是在问题一闪而过的场景下pidstat 1 5 pidstat -r 1 5 pidstat -d 1 5第一条看每个进程的CPU使用率-r看内存占用-d看进程的磁盘读写。实用的一点是pidstat会输出%wait列表示进程等待CPU的时间占比。如果%wait很高说明进程不是没活干而是抢不到CPU如果I/O那个维度的读写很高进程身份立刻就明确了。我线上排障时经常用pidstat -d找出“看起来CPU不高但系统很慢”的元凶因为很多进程的CPU占用不高却在疯狂写磁盘最终把整个设备的I/O队列拖垮。4.3 线程级排查怎么往下钻拿到进程PID之后如果还想再往下钻看线程级的资源消耗pidstat -t -p PID 1 5如果发现某个线程的CPU几乎打满下一步可以把它和代码关联起来。Java服务里可以用jstack导出线程栈找那个线程的tidC服务可以用perf top或gdb去看热点。工具细节跟语言相关但思路一致先定位线程号再对应到代码栈避免在进程级别反复猜测。5. 内存专项free命令的两大常见误区和真实泄漏定位5.1 free -h的每个数字代表什么$ free -h total used free shared buff/cache available Mem: 15Gi 4.1Gi 1.2Gi 300Mi 9.8Gi 10Gi Swap: 2.0Gi 0B 2.0Gi这里最容易翻车的点是used不等于真实独占用free低也不代表内存不够。Linux会把空闲物理内存尽量用作页缓存和文件缓存所以free那一行很低很正常真正需要关注的是available这一列。available的意思是“在不触发交换的前提下还能分配给新进程的内存大小”它考虑了可回收缓存。上面这个例子中free只有1.2Gi但available有10Gi内存其实是健康的。真正的危险信号不是free低而是swap持续增长、si/so非零以及available持续逼近0。5.2 从/proc/meminfo补足free看不到的细节free是一个汇总视图排查内存问题时我会再确认几个字段grep -E MemTotal|MemFree|MemAvailable|Buffers|Cached|SReclaimable|Shmem /proc/meminfo重点看Cached和SReclaimable。Cached里既包含磁盘文件缓存也可能包含tmpfs/shmem占用后者无法回收SReclaimable是内核可回收的slab内存。如果Cached巨大又长期不降可能是文件缓存正常也可能是某个路径在反复读写同一批文件如果SReclaimable异常高通常和内核对象泄漏有关比如dentry/inode缓存失控。5.3 一个不写代码也能操作的内存泄漏定位流程内存泄漏不一定要看堆栈。最朴素的判断方式是连续采样RSSpidstat -r 1 60 mem_sample.log跑一段时间后观察某个进程的RSS是否单调上涨、且系统available持续下降。正常缓存会随着回收波动泄漏则是只涨不降。进一步确认可以用cat /proc/PID/status | grep -E VmRSS|VmSize|VmSwapVmRSS是物理常驻内存VmSwap是被换出的部分。如果应用容器或服务本身不缓存数据RSS还一直涨那基本就是泄漏接下来再交给开发去查具体对象引用。这个流程最大的好处是不需要改代码、不引入额外工具只要把采样间隔拉长规律自然会显现。6. 磁盘I/O专项iostat与pidstat -d找到真正吃磁盘的进程6.1 iostat -x的关键指标哪些可信哪些要谨慎$ iostat -x 1 3 Device r/s w/s rkB/s wkB/s avgqu-sz await r_await w_await %util vda 0.00 890.00 0.00 3560.00 12.47 14.00 0.00 14.00 98.20重点关注几项%util表示设备带宽利用率avgqu-sz是设备请求队列长度await是I/O请求从进入到完成的总等待时间w_await是写请求平均等待。但有个认知要纠正%util接近100%只能说明设备“忙”不代表一定“慢”。如果设备本身是NVMe固态盘%util高但await只有1ms不到那业务基本无感如果机械盘或网络存储%util只有60%await却超过50ms那才是真正的慢设备。所以**%util和await需要组合起来看**。6.2 区分“磁盘忙”和“磁盘慢”简单给个判断经验%util很高、await也很高、avgqu-sz很大设备过载或者掉速排队严重%util很高、await很低设备在满负荷工作但响应快可能是持续高吞吐业务%util不高、await却很高设备本身响应慢或者走了网络存储、被其他租户影响avgqu-sz持续大于设备队列深度请求堆积需要扩容或优化读写模式。很多数据库场景的“慢查询”并不是SQL有问题而是磁盘await高导致每次刷脏页都卡顿。此时光看数据库慢日志没用要把iostat和数据库刷盘线程的等待时间放在同一时间轴对比。6.3 从await高到定位进程的完整链条如果iostat确认磁盘有问题下一步不要直接看业务代码先找进程pidstat -d 1 5看到某个进程的w/s和wkB/s非常高再结合文件路径确认它在写什么。比如一个日志采集进程在高峰期把日志级别从WARN调成了INFO写入量可能瞬间翻几十倍一个备份脚本在业务高峰时段启动也可能直接把磁盘打满。找到进程、看到文件路径之后修复手段往往简单得让人意外调整日志级别、避开高峰时段、限制并发数或者把大文件放到更快的盘上。7. 网络维度ss一眼识别连接堆积而不是所有慢锅都甩给服务器7.1 ss -s与几个关键TCP状态系统资源都正常但服务还是慢就要看网络连接层。$ ss -s Total: 285 (kernel 0) TCP: 51 (estab 16, closed 20, orphaned 3, synrecv 0, timewait 2)ss -s给出当前连接状态的总览。常见几个状态需要区分ESTABLISHED正常建立的连接SYN_RECV半连接堆积可能有人发起大量握手TIME_WAIT主动断开方等待关闭的连接过多会占表项CLOSE_WAIT被动断开方没有关闭socket的连接应用层不处理时越堆越多。很多文章把TIME_WAIT说得非常危险但实际上TIME_WAIT在大部分场景下是正常状态只要数量不冲破端口范围影响有限。真正要警惕的是CLOSE_WAIT只增不减这通常意味着应用代码忘了关闭连接。7.2 CLOSE_WAIT与Recv-Q堆积的真实含义如果确认CLOSE_WAIT数量很大可以进一步ss -tanp | grep CLOSE_WAIT关键在于看关联的进程。CLOSE_WAIT堆积不是内核问题而是应用层没能正确关闭socket。常见原因包括读超时没设置、线程阻塞、异常路径没有释放连接。修好代码之后CLOSE_WAIT数量才会回落。还有一个常见的网络层陷阱值得单独说Recv-Q堆积。执行ss -lnt如果某个监听端口的Recv-Q长期大于backlog说明应用accept的速度跟不上连接到达速度也就是系统层没问题、应用处理不过来而不是网络问题。7.3 判断该不该继续在OS层排查有一种“慢”不是服务器性能造成的而是外网链路、DNS解析、客户端网络、甚至用户所在地网络的问题。系统资源正常、连接状态正常、丢包率也不高但用户依然反馈慢这时候就要把排查边界划清把问题推给网络链路方或者做更细粒度的请求追踪而不是继续在服务器上盲敲命令。如果在服务器上看网络质量可以用sar -n DEV,ETCP 1 5观察重传率以及ss -ti看TCP重传计数。确有重传再深入没有的话果断换方向。8. 复盘一个“系统慢”工单从接收命令到根因落地的全过程8.1 工单背景与第一手指标快照某内部业务系统在晚高峰收到用户反馈报表页面加载极慢白屏时间接近15秒。服务器的配置是12核、32G内存虚拟化环境。我登录后的排查记录如下先看uptime$ uptime 20:31:01 up 30 days, 2:10, 3 users, load average: 18.90, 16.52, 12.311分钟负载18.95分钟16.515分钟12.3明显在持续升高。但这还不能判断是CPU还是磁盘继续vmstat$ vmstat 1 5 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 2 7 0 121100 28000 882100 0 0 1200 45000 3000 1800 20 12 10 58 0r只有2b有7wa高达58%si/so都是0。到这里基本可以判定不是CPU不够也不是内存压力而是磁盘I/O在拖后腿。8.2 把根因从磁盘队列追到日志配置继续用iostat定位设备$ iostat -x 1 3 Device r/s w/s rkB/s wkB/s avgqu-sz await w_await %util vda 2.00 32.00 16.00 128.00 0.10 2.00 2.00 3.00 vdb 0.00 3420.00 0.00 80120.00 28.60 34.00 34.00 99.90vdb的写请求每秒3420次吞吐量约80MB/savgqu-sz 28.6await 34ms%util 99.9。很明显问题全在这个数据盘上。再使用pidstat -d找到写盘进程$ pidstat -d 1 5 PID command w/s wkB/s 2714 log_agent 3180.00 76000.00日志采集进程占据了绝对大头。接着查看该进程的日志路径和配置发现业务方把某个系统日志级别从WARN调成了INFO高并发写日志导致的每分钟日志文件量是原来的几十倍日志Agent又按固定间隔批量刷盘最终把磁盘队列打穿。8.3 修复动作与后续监控处理分三步走临时把日志级别调回WARN页面响应立刻回到2秒以内调整日志Agent的批量写盘策略加大批次间隔降低写盘频率将日志数据目录从繁忙的数据盘vdb迁到另一块吞吐更高的设备并在这台机器上配置磁盘util超过85%持续5分钟即告警的规则。后续观察一周负载回落到3左右vmstat的wa降到5%以下用户反馈恢复正常。这次复盘给我最深的体会是表面上看着像应用慢、像数据库慢的问题只要一步步从系统指标往下追大都能在一个小时内定位到具体进程和配置项。其实排查慢多数时候不是命令不够而是跳级太早、假设太多。如果非要说一个最值得养成的习惯我推荐每一次线上排障都随手把命令输出和判断结论记下来。哪怕只是几行文本下次碰到类似症状翻出来对照能省下大半天时间。