不知道你有没有这种经历半夜被一条系统负载飙高、服务无响应的告警吵醒登陆上去一看uptime显示的负载高得吓人可top里每个进程的CPU占用看起来都低得离谱。内存还剩不少业务却一个接一个超时。你本能地想去重启服务、重启机器甚至申请扩容但第二天它又好了第三天又犯始终说不清根子在哪里。我做了很多年Linux性能排查的活从系统慢这种模糊反馈到最终精准定位到某个进程、某行代码中间走过的弯路比直路多得多。后来我慢慢摸到一套固定的排查秩序先看整体、再按CPU/内存/磁盘/网络四个维度逐个排掉最后才轮到进程级、系统调用级和内核日志级。这套流程不依赖玄学也不用一次性记住几十个参数只要把三板斧用好绝大多数系统慢都能在几十分钟内收敛到明确的根因。这篇文章我就把这套流程完整讲一遍。没有花哨的工具全是标准发行版自带的命令适合做运维、搞后端开发的你直接照着抄也适合刚接触Linux性能问题的朋友建立自己的排查框架。准备好终端和一颗不慌的心我们从最基础的三条命令开始。1. 先摸清排障思路从系统慢到哪里慢1.1 五分钟建立整体秩序先横向判断再纵向深挖接到系统慢的第一反应不要是重启一下而是先问三个问题慢的规模有多大是全站所有接口都慢还是只有某个服务慢是持续变慢还是周期性抖动这三个问题的答案决定了下面排查的走向也决定了你该从哪一层下手。我习惯把性能问题分成两类。第一类是资源耗尽型CPU算力不够、内存不足、磁盘IO饱和、网络带宽被打满这类问题指标特征非常明显用top、vmstat、iostat、ss对着看很快就能找到。第二类是业务代码或架构问题锁竞争、线程池耗尽、连接泄漏、慢SQL、调用链路上某个下游超时这类问题往往资源看起来还过得去但延迟已经高到不可接受需要深入进程内部去看。排障的通用节奏是先横向、再纵向。横向是指用uptime看负载、用top看整体资源占比、用vmstat看每秒快照先确定瓶颈在哪个资源维度。纵向是确定维度之后再递进到具体的磁盘设备、具体网卡、具体进程、具体线程、具体系统调用直到抓出元凶。整个过程就像剥洋葱层次越往下干预手段越轻越靠近真因。提示排障现场最忌讳的是没有时间线概念。命令输出的时间戳、告警触发的时间点、业务开始异常的代码发布时间这三者一对照很多看似随机的问题其实都有规律。1.2 读懂系统慢的三种典型表现具体到一台Linux服务器上慢通常只有三种外显形式认清它们能帮你快速缩小范围。第一种是CPU密集导致的服务响应慢。特征是top里us或sy列居高不下进程自己的CPU占用经常打到接近核数上限代码里通常有死循环、密集计算或频繁垃圾回收。第二种是IO密集导致的慢。特征非常独特负载很高但CPU大部分在wa上等待vmstat里b列不可中断睡眠进程持续不为零应用日志里到处是超时。第三种是等待型慢CPU和磁盘都没有明显瓶颈但请求就是慢常见原因是锁竞争、线程池排队、网络往返太高、下游依赖超时重试。这种最难排因为表面数字都正常必须靠链路追踪和进程内部观测来破案。写这篇文章时我特意把注意点放在现象→指标→命令的对应关系上。你不需要背住每一个内核计数器但一定要知道每种现象该看哪个指标、用哪条命令去量化它。1.3 动手之前的必备准备排障不是光靠命令硬拼就行的动手前花两分钟准备效率会高很多。首先确认你的终端能保持长期连接而不被中断screen或tmux是标配。其次确认有足够的权限性能排查中dmesg、strace、perf这类命令动不动就要root权限没有权限会走很多弯路。第三是把所有操作都记录到一个临时笔记里包括时间点、看到的关键输出、执行过的命令。很多问题不是当场解决不了的而是要复现、要对比没有记录就失去了对比的基准。还有一条虽然是常识但必须强调在业务高峰期尽量少用top和vmstat这样的交互式命令长时间挂着这些命令本身会消耗少量CPU在濒临崩溃的机器上会造成额外的抖动。需要持续采集的时候用top -bn1批量模式或vmstat 1 5等定时采样的方式更稳妥。2. 第一板斧三条命令完成全局粗筛2.1 uptime与load average负载数字到底在说什么命令是uptime输出长这样$ uptime 14:32:18 up 42 days, 4:11, 3 users, load average: 12.50, 9.80, 7.20末尾三个数字分别代表1分钟、5分钟、15分钟的平均负载。很多人第一次看到load average超过CPU核数就慌了其实这个数字代表的是处于可运行状态和不可中断睡眠状态的进程平均数量它不是CPU占用率也不是百分比。一个4核的机器load average长期维持在4左右说明CPU刚好忙得过来超过4就说明有进程在排队等待CPU了。判断load average是否偏高必须结合CPU核数和趋势。先看nproc拿到核数再用load除以核数。以4核为例load达到8到12时基本可以确认CPU饱和但如果load高而CPU的idle又很高情况就完全不同了——这就指向了不可中断睡眠进程也就是D状态进程常见于磁盘IO、NFS挂载、内核锁等待。我在后面的案例复盘里会详细展开这种假高负载。还有一点只看即时值没有意义要看趋势。15分钟是5、1分钟飙到12说明系统刚刚开始恶化排查要抓后者的现场如果三者都高说明问题已经持续一段时间可以先抓快照再去查历史指标。2.2 top/htop打开进程世界的第一窗口top是查看全局的第一现场。注意几个关键区顶部的load average、Tasks总数、%Cpu(s)这行里的us、sy、wa、hi、si以及进程列表里的%CPU和%MEM。我一般会按顺序做这几步操作先按数字键1看一下每颗CPU的核心占用确认是不是只有单核被逻辑打满再按P按CPU排序、按M按内存排序来回切换几次看看是哪个进程一直在前面最后按c展开完整命令行确认这个进程对应哪个业务。排查期间的修改都不要保存退出时的配置免得污染下次使用。top默认刷新间隔是3秒排障时可以手动按d改成1秒。需要把结果保留下来给别人看时不要截图直接用批量模式$ top -bn1 | head -30批量模式非常适合写成脚本或定时采集。还有个很有用的技巧top -p 1234可以只看某个进程配合-H参数能看到这个进程下所有线程的资源占用。线程级别的排查对Java、Go这类高并发应用极其重要。2.3 vmstat每秒快照里藏着瓶颈线索哪怕top已经把问题指出来了我还是会补一条vmstat因为它能一次性提供CPU、内存、IO和上下文切换等多个维度的每秒快照是纵向排查的分岔路口。$ 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 14 1024000 345200 123456 7890123 100 200 500 3000 2000 2500 3 2 80 15 0用的时候只盯几列r列是运行队列长度持续大于核数说明CPU已经排队b列是处于不可中断睡眠的进程数持续不为零说明有进程卡在IO上us/sy是用户态和内核态CPU占比sy占比过高要怀疑系统调用频繁或内核异常wa列是CPU等待IO的时间占比高企不下基本可以确定磁盘这层出问题si/so是换入换出内存两个都不应为常值出现持续换页说明内存真的不够用了。vmstat 1 5的含义是每1秒采样一次总共采样5次。命令执行完后看最后两三行的平均值比较有代表性第一行往往是从开机以来的累计均值参考价值不大。3. 第二板斧四个资源维度逐一定位3.1 内存维度free与dmesg组合判断top里看到某个进程%MEM高不代表内存的问题一定要处理真正要判断的是系统还剩多少可用内存。这个判断标准不是free字段而是available字段。用free -h看输出$ free -h total used free shared buff/cache available Mem: 15Gi 8.1Gi 1.2Gi 212Mi 6.1Gi 6.3Gifree这列是真正没被任何东西占用的物理内存buff/cache是内核用来缓存文件读写的这部分在内存压力大的时候是可以回收的所以一个正在正常运行的Linux系统里free看起来小并不代表危险。available把可回收的buff/cache也算进去它才更接近还能塞下多少新进程的真实答案。判断内存问题还有一个稳定路径当available长期低于总内存的10%并且vmstat里si/so开始出现非零值时内存肯定是真不够了。此时看谁在吃内存用ps aux --sort-%mem | head -10再配合dmesg -T | grep -i oom确认内核是否杀过进程。内存问题最怕的就是只看free数值——数值还剩一半但换页已经把系统拖垮了这种我见过太多次。3.2 磁盘IO维度iostatpidstatiotop三板斧CPU和内存排完还没找到根因八成是磁盘IO。第一命令永远是iostat -xm 1 3-x输出扩展统计-m用兆字节展示每秒1次采样3轮Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz %util vdb 100.0 800.0 5120.0 102400 0.0 1200.0 0.0 60.0 5.0 50.0 40.0 51.2 128.0 100.0看这组数据我固定只看四个值%util接近100%说明设备已经很忙w_await代表写请求平均等待时间正常机械盘在十几毫秒以内SSD应该在个位数飙到几十毫秒就是性能恶化aqu-sz是平均队列长度持续大于磁盘并发能力说明请求在排队rrqm/s和wrqm/s代表合并的请求数这两个数值高说明应用在发出大量零散IO感受一下就知道这是可以优化的方向。有一个很多人踩过的坑SSD的%util可能长期显示100%但实际上延迟依然很低、设备并没有到瓶颈。这是因为%util统计的是采样周期内设备有IO请求在持有的时间比例NVMe这类高并发设备在处理大量并行小请求时这个值天然很高。所以判断磁盘是不是真瓶颈优先看await和窗口内是否出现应用超时而不是死抠%util。用iostat判断设备有压力之后还要知道压力来自哪个进程。用pidstat -d 1能列出每个进程每秒的读写字节数和延迟用iotop能实时看到进程的IO占用排序。这三板斧下来基本就能定位出是数据库写日志、是备份脚本扫数据还是某个应用的缓冲刷得太勤快。3.3 网络维度ss、sar和内核网络参数很多系统慢的根因在网络层。但这里有个常见的误区很多人习惯先ping实际上ping只是ICMP通不通的验证与TCP连接质量的关系有限真正该看的是一眼连接状态和收发队列。首选命令是ss它是netstat在现代Linux里的替代品输出快且信息量更大。使用ss -s看汇总$ ss -s Total: 1024 (kernel 2048) TCP: 512 (estab 256, closed 200, orphaned 56, synrecv 12, timewait 128)重点关注timewait和estab的比值。正常情况下timewait会存在但不会堆成灾如果timewait占连接总数的比例明显偏高同时业务反馈新连接建立慢基本要考虑调整tcp_fin_timeout和是否开启tcp_tw_reuse。再看ss -lnt里监听端口的Recv-Q和Send-QRecv-Q持续堆积说明有连接进来了但应用没来得及accept这是典型的线程池饥饿Send-Q堆积说明对端收不过来了要怀疑下游消费能力或带宽。流量层面用sar -n DEV 1 3看网卡吞吐用sar -n TCP 1 3看TCP新建连接数。带宽打满时rxkB/s或txkB/s会接近网卡上限这时候再回头看具体是哪个进程在占用可以用nethogs或lsof -i辅助定位。3.4 CPU与进程维度pidstat之后还有perf与strace如果top中的进程CPU很高问题到进程这层还不够还要深入线程和系统调用。pidstat -u -r -d 1可以把CPU、内存、IO三个指标按进程打印出来比反复切top的排序更稳当。想看线程级别加-t参数即可。确认一个进程在忙但不知道它在忙什么的时候perf top是效率极高的选项。它会周期性采样并给出热点函数名一条命令就能看到CPU时间到底烧在哪段代码里省去了反复猜、反复加日志的痛苦。需要更深的火焰图时用perf record -g -p 进程号采集一段时间再perf report看调用栈。如果是频繁系统调用导致的高sys占用strace -c -p 进程号会统计一段时间内各系统调用的次数和耗时CtrlC停止后输出汇总。看到futex、nanosleep、poll这类调用高频出现时要往锁竞争、线程等待方向想看到大量read、write则要往IO方向想。这两个工具用好已经足够穿透绝大多数CPU层面的疑难杂症。4. 四个真实故障场景的完整复盘4.1 场景一load average飙到12CPU却闲着某次线上服务无响应我登陆时看到的第一个异常是uptime输出load average接近12但top里CPU的idle有80%以上。顺着CPU不忙但负载高这个矛盾我执行了vmstat 1 3结果b列稳定在14左右几乎没有归零的趋势同时bi列也在持续上涨。b列是D状态进程这种状态通常是进程在等待磁盘、内存交换或内核锁外力很难干预连kill都经常失效。我接着用iostat -x 1去扫所有块设备发现某块数据盘的%util为100%、await接近100毫秒磁盘明显已经处在拒绝服务状态。再用pidstat -d 1逐个进程看找到那个一直往这块盘写数据压缩存档的后台任务最终定位是它在反复读取一块已经出现坏道的分区。处理方式是把存档任务的数据目录迁移到健康的另一块盘上业务立刻恢复。这个场景教给我一个原则load average高只是结果vmstat的b列和iostat的await才是分诊依据不要看到负载高就加机器先把D状态的源头揪出来。4.2 场景二内存剩余很多却频繁OOMavailable才是真相另一个典型场景是服务进程偶发被内核杀掉系统本身还算稳定但查看监控发现内存一栏写着还剩好几个GB完全看不出问题。我当时的操作顺序是用free -h确认available其实已经见底而free字段看起来还有几个GB是因为buff/cache一直都在被回收、被占满、再回收的循环里。接着我翻了dmesg -T的输出看到一段典型的OOM记录里面明确写着内核杀掉了一个进程还标出了该进程的vss和rss。再配合ps aux --sort-%mem看内存占用排序发现是应用侧一个缓存组件把机器内存吃光了。当时业务代码里用本地内存做了大列表的缓存没有设置上限也没有配合过期清理数据量一上来就不断争夺内存。处理手段是给进程设置内存上限并在应用侧把那个无上限的缓存加上淘汰策略同时建议在监控里加一条available内存低于物理内存15%就报警的规则。从那以后我判断内存是否够用永远先把available当第一指标free只作为参考。4.3 场景三数据库响应变慢磁盘IO饱和的连锁反应一个面向用户的基础服务突然在高峰期出现大范围超时数据库CPU占用只有20%看起来没什么问题。但业务方反馈每一个SQL查询都比平时慢几倍这就很反常——数据库CPU不高慢就指向了其他地方。我用iostat -x 1连续看了30秒数据盘的w_await在峰值达到80毫秒%util长时间100%。再用iotop确认占用IO的进程正是数据库实例本身。进一步看数据库慢日志发现大量查询走了全表扫描返回的行数也不大但扫描的数据量巨大导致频繁读取数据文件把磁盘队列彻底拖垮。同时日志刷盘机制又把随机写叠加进来读写两头同时压。修复分两步先在数据库里为高频查询补上合适的索引让查询从全表扫描降到索引查找读压力立即下降再把redo日志目录移到单独的磁盘把随机写从数据盘拆出去。改动上线后磁盘await回落到了个位数。这个例子说明磁盘瓶颈有时不是上层代码直接写盘而是某个业务的查询模式间接导致的排查时要顺着谁在IO这条线一路找到末端。4.4 场景四接口大量超时TCP连接队列被塞满还有一个让我印象深刻的场景服务进程CPU、内存、磁盘全部正常但接口就是大量超时。我补了ss -s后发现timewait的数量高得异常紧接着看ss -lnt发现业务端口上的Recv-Q持续有几百个连接积压。Recv-Q积压的意思很明确TCP三次握手完成了accept队列里排着队但应用线程一直没有把它们取走处理。方向瞬间从系统资源转向应用处理能力。查业务线程数后发现这个服务的线程池被某个外部依赖的同步调用卡住了线程全部阻塞在等待对方响应上新请求进来没人处理只能堆在内核队列里。根因是下游接口在高峰期响应变慢而线上服务对下游没有配置超时熔断一个下游慢拖垮了整个上游。处理时先给这个服务的线程池扩容到原来的两倍临时扛过峰值随后在代码里给下游调用设置了超时时间并加了快速失败逻辑避免线程被无谓占满。线路全部加固之后再压测接口超时基本消失。这个case让我养成了习惯看到TCP连接堆积先看Recv-Q和Send-Q再往应用线程池和下游依赖排查而不是单纯调内核参数。5. 工具选型、速查表与一条命令抓现场5.1 工具横向对比什么场景用哪个很多新手被一堆命令搞得头晕核心原因是没有把工具和场景对上。我整理了一张常用选型表排查时对着挑就行。排查维度首选工具补充工具核心关注指标适用场景全局总览uptimetopload average、idle/wa刚接手时的第一眼CPU/任务队列vmstatmpstatr、b、us、sy、wa判断CPU还是IO瓶颈内存freeps、dmesgavailable、si/so、OOM记录排查内存不足与OOM磁盘IOiostatpidstat -d、iotop%util、await、队列长度定位磁盘饱和与IO源网络连接sssar -n TCPtimewait、Recv-Q、Send-Q连接堆积与队列堵塞进程耗时下钻perfstrace热点函数、系统调用分布CPU高但进程内难定位长时间趋势sar监控系统历史指标回放排查结束后做基线对比选型的逻辑很简单先全局后局部先系统后进程先指标后数据。不要一上来就perf record那是在还没确认问题方向时最容易浪费时间的方式。5.2 一条命令抓取排障现场快照复盘做的多之后我把最常用的采集命令做成了一个别名一条命令执行完就能拿到整个系统的现场快照非常适合故障当前时刻快速留下证据。建议你在自己的机器上也配一个alias sysdarkuptime; echo ---; vmstat 1 3; echo ---; free -h; echo ---; iostat -xm 1 3; echo ---; pidstat -u -r -d 1 3; echo ---; ss -s; echo ---; dmesg -T 2/dev/null | tail -20执行顺序有讲究uptime看整体趋势vmstat得到CPU和IO的分诊依据free -h确认内存iostat把IO落到设备pidstat把设备落到进程ss -s看网络连接最后用dmesg检查内核有没有吐OOM之类的关键日志。这套快照拿到手问题的方向一般就已经收敛到两三个候选了接下来只需要针对其中一个做更深入的观测。注意dmesg在很多系统上需要root权限普通用户执行会直接报错。如果你是非root环境把这段去掉换用journalctl -k --since 10 minutes ago的方式也能看到内核日志。5.3 排障命里的避坑清单基于上面的实战复盘我把最容易误判的几个点集中列一下你排查时对照使用。load average高不一定是CPU忙先看vmstat的b列和wa列D状态进程和IO等待同样会让负载暴涨free小不代表内存不够available才是真相buff/cache是内核的弹性资源iostat的%util对SSD要谨慎解读同时注意await才是延迟的直接体现看到进程CPU占用低不要急着排除它top默认显示的是采样周期内的平均值瞬时高峰可能被平摊掉要看更短周期的采样或配合pidstat做多次采样。网络排查时ping通了也不等于TCP没问题ss -lnt的Recv-Q才能反映应用接受连接的速度。还有一条老生常谈但不能不提排查前顺手把所有命令的版本确认一遍不同发行版之间iostat、sar的输出列名略有差异按上面这些关键字找列即可不要死记列的位置。6. 从救火到防火性能基线养成记录6.1 我的十条排障心得经验这个东西说再多不如自己踩一次。我把这些年排障最值钱的十条心得写在这里每一条都是真金白银换来的。第一接到系统慢先忍住不登录生产环境瞎搞先在监控平台上把最近半小时的趋势抓一遍知道是从什么时候开始的比知道现在多严重更重要。第二所有命令都要加时间戳输出或手动记录复盘时会发现很多当时没注意的细节。第三优先怀疑最近变了什么而不是哪里看起来不正常代码发布、配置变更、数据量增长、上游依赖抖动这四个因素覆盖了大多数故障诱因。第四用top按CPU排序看不到问题就按内存排序两个方向都没有才考虑IO和网络这是成本最低的排除路径。第五不要在一个可能没问题的维度上反复磨iostat看过两轮没有异常就果断换方向时间要花在刀刃上。第六D状态进程不是能随便kill的它卡在不可中断的内核路径上杀掉进程的前提是先解决让它卡住的外部资源否则进程状态无法被清理。第七OOM记录里写明的进程名字和内存占用是最高效的线索先信内核日志再信应用猜测。第八strace不要直接挂到高并发进程上跑超过几十秒它会让目标进程变慢一个数量级严重时直接引发雪崩务必在低峰期或灰度环境操作。第九任何性能问题都要留下证据链命令输出、时间点、改动列表三者缺一不可否则复盘根本没法往下走。第十绝大多数问题都不是单一因素而是资源临界时被一个触发点引爆所以解决完主因之后一定要顺手看看其他资源是否也接近阈值。6.2 最容易翻车的三个误判第一个误判是上下文切换高性能差。上下文切换多本身不代表有问题高并发的服务每秒几十万次切换是正常的关键要区分是自愿切换还是非自愿切换。非自愿切换持续飙升才说明CPU核数不够、线程在抢时间片。vmstat里的cs只给出总量想看更细的要用pidstat -w查看进程级别的自愿和非自愿切换次数。第二个误判是磁盘%util100就是瓶颈。前面提过SSD的并发特性会让这个指标失真真正的判断要结合await、队列深度和应用侧延迟。有些高性能SSD在%util持续100时吞吐依然稳定不需要人为纠结。第三个误判是看到TIME_WAIT多就急着调内核参数。TIME_WAIT多通常只是表面现象根因在应用频繁创建短连接没有复用连接池。把tcp_tw_reuse改大只是掩盖症状真正的修复是让应用层做连接复用。我见过太多团队把内核参数调得面目全非问题反而更严重最后还是回到代码层面才解决。记住内核参数的调整要服务于应用行为不要本末倒置。6.3 建立基线与常态化观测排查完一个问题最高价值的动作是把这个问题的正常水平记录成基线而不是满足于今天恢复了。没有基线下次再出问题时你依然无法判断当前数值是健康还是危险。我在每套环境稳定运行一周后会执行一次完整的sysdark快照把load average、空闲内存、磁盘await、TCP连接数等核心指标保存下来标记为该环境的基准值。之后每次排障都拿现场值和基准值对比偏离幅度一目了然。日常观测依赖sysstat定时采集的sa历史记录它默认每10分钟采集一次也可以改成1分钟。这些历史数据在问题结束后仍然可以通过sar -q、sar -r、sar -d回放让你在现场已经恢复、找不到证据的时候依然能定位是非常划算的投入。再进一步把这些采集指标接入统一的监控和告警体系配上可用内存低于阈值、磁盘await超过基线两倍这类告警规则很多问题在用户感知到之前就能提前处理掉。做性能排查这么多年我自己最大的心得是不要迷信复杂的工具和花哨的图表大多数系统慢问题的答案就藏在那几条基础命令的输出组合里。把uptime、vmstat、iostat、ss当成你的体温计、血压计、血氧仪熟练到能一眼看出异常再配合perf和strace做精准下钻你就能在别人还在猜的时候稳稳地把根因抓在手里。最后一句话送给你排障的本质不是看更多指标而是快速排除正确路径之外的干扰先救火再复盘最后把每一次救火都变成下一次防火的养料。