
服务器又卡了CPU满了内存告警磁盘IO也报警到底先查哪个这类问题我几乎每周都会遇到。尤其是Linux系统运维、后端、DBA的工作群里动不动就有人丢一张top截图出来然后问“是不是要加配置了”。说实话大部分时候根本不是配置不够而是没有系统性地做性能瓶颈定位。CPU、内存、磁盘IO这三类资源是Linux性能问题中最常见的“嫌疑人”但它们经常互相伪装比如内存不足会表现为磁盘IO高磁盘慢又会表现为CPU iowait高。这篇文章把我这些年排查性能问题的完整思路和命令记录下来适合刚入门Linux运维的同学也适合那些已经会敲top、但一遇到复杂问题就不知道下一步怎么查的后端和SRE从业者。耐心看完你会掌握一套能直接上手的定位方法。1. 先把排查思路理顺不要一上来就 top很多同学的习惯是机器卡了先开个top看哪个进程CPU高高了就kill或者直接重启。这样做偶尔能蒙对但遇到真正复杂的问题往往排查到一半就卡住了。我现在的习惯是先花两分钟想清楚“从哪下手”再动命令。1.1 先回答三个问题再动命令第一个问题现象是“慢”还是“不可用”慢可能只是延迟变高不可用可能是进程被OOM杀了、磁盘只读、网络链路断了。两者排查方向完全不同。第二个问题影响范围是什么是所有用户都卡还是少数请求超时是所有服务都慢还是只有数据库、只有某个Java进程慢第三个问题时间窗口是什么是刚才开始还是从昨天某个版本发布后开始还是规律性地每天高峰时段出现这三个问题决定了你要采集系统级的指标、进程级的指标还是直接看日志和变更记录。举个例子如果只有某个接口慢你盯着整机CPU高没有意义因为可能是应用线程池阻塞跟系统空闲资源没什么关系。如果整机负载很高而且业务方反馈大面积超时那才轮到top、vmstat、iostat这些命令登场。我见过太多人一上来就top -Hp分析线程栈结果发现进程本身CPU只用了3%真正的问题是磁盘IO队列排队白费了一上午。第二个点明确“慢”的三层含义。系统负载高指load average大进程CPU高指某个进程或线程占用CPU多业务延迟高可能只是应用内部锁竞争、GC停顿、数据库慢查询。这三层之间有关联但不能混为一谈。下面所有命令都是在帮你把“现象层”逐步下沉到“原因层”。1.2 准备好工具和“正常基线”性能排查需要一套趁手工具。Linux自带的procps、sysstat、perf、strace这些基本够用先列一个我常用的清单工具用途常用命令示例uptime系统负载、运行时长uptimetop/htop实时进程、CPU、内存排序top交互式按P/Mvmstat整体CPU、内存、swap、IOvmstat 1 5iostat磁盘IO设备级统计iostat -x 1 3mpstat每颗CPU核心的利用率mpstat -P ALL 1 3pidstat按进程统计CPU、内存、IO、上下文切换pidstat -d 1free内存使用总览free -hsar历史/周期系统状态采集sar -u 1 3perf热点函数分析perf top -p PIDlsof查看进程打开的文件lsof -p PID我强烈建议在业务正常的时候就把这一套命令的输出存一份“基线记录”。比如每次上线新功能前跑一轮sar、vmstat、iostat保存到某个目录。等出问题时拿当前数据和正常数据对比比对着抽象经验判断靠谱得多。很多“异常”其实只是业务量上升后的正常表现没有基线的话你根本不敢说它是问题。另外生产环境操作要留神。用perf、strace这类工具做深度采样会有性能开销时间要短、范围要小。不要因为排查问题把原本还能用的服务彻底拖垮。2. CPU瓶颈别再把负载和CPU使用率画等号CPU问题是大家最先关注的也是误判最多的。一个典型误区是把load average当作CPU占用率看到负载8就喊“CPU满了”。实际上8核机器上负载8意味着每个核心都刚好有一个任务在跑并不一定过载。如果负载12但机器是32核CPU资源其实还很宽裕。2.1 从整体到线程的“降维”排查CPU排查一定要遵循“整体→单核→进程→线程→热点函数”的顺序不然容易漏掉关键信息。第一步看uptimeuptime # 输出示例 # 14:23:45 up 12 days, 3:21, 3 users, load average: 8.50, 6.21, 4.35这里1分钟、5分钟、15分钟三个值一起看。如果1分钟远大于15分钟说明负载刚刚冲上来如果三个都很高说明已经持续一段时间了。负载来源不只是CPU还包括处于不可中断睡眠状态D state的进程也就是正在等磁盘IO的进程。所以负载高时要结合进程状态判断这一点下一节还会讲。第二步看top。重点看%Cpu(s)这行里面有us用户态、sy内核态、ni优先级调整、id空闲、wa等待IO、hi硬中断、si软中断、st被虚拟机/宿主机偷走的时间。us高说明应用在算sy高说明内核在做系统调用、进程调度、中断处理wa高说明CPU在等磁盘st高说明你是一台虚拟主机宿主机超卖了业务再优化也可能解决不了根本问题要考虑换规格或错峰。第三步用mpstat -P ALL看每核mpstat -P ALL 1 3如果整体CPU不高但某颗核心100%说明是单线程程序或者中断都集中在某个核心上。常见情况是网卡中断都在CPU0上通过调整irqbalance或者RPSReceive Packet Steering可以分摊。如果是Java/C程序的单线程热点可能得从代码层面优化而不是盲目加机器。第四步定位具体进程然后再定位线程。top按P键可以按CPU排序找到可疑进程PID后用下面的命令看线程CPUtop -Hp 12345或者用pidstat持续输出pidstat -t -p 12345 1很多Java服务会把线程名输出到线程名中所以top -Hp里能看到类似GC task thread、http-nio-8080-exec-12这样的名字直接就能判断问题出在哪类线程上。这一步做完基本就能判断是用户态计算密集还是内核态锁竞争还是GC线程异常。2.2 上下文切换与软中断的判定CPU高不只是算得多也可能是“切换得多”。vmstat 1输出里有两列很关键cscontext switch上下文切换次数/秒和in中断次数/秒。vmstat 1 5我一般这样看cs每秒几万甚至十几万说明进程/线程切换太频繁。可能是线程数过多、锁竞争严重、或者某些框架不断创建销毁线程。in很高说明中断很频繁常见是网络小包流量巨大、磁盘队列很深。us不高但sy很高往往是系统调用开销过大比如频繁读写、频繁futex锁等待可以考虑用strace -c看系统调用分布。上下文切换本身不是病而是结果。要往上层查是谁导致调度频繁。pidstat -w 1可以看到进程每秒钟的主动/被动切换次数pidstat -w 1它会输出cswch/s自愿切换和nvcswch/s非自愿切换。自愿切换通常是线程主动让出CPU在等IO或锁非自愿切换高通常是时间片耗尽被强占说明计算密集或优先级太高。查到具体进程后再去结合业务代码分析。软中断si高有过一次印象很深的坑一台nginx机器CPU的si长期10%以上业务高峰期直接飙到30%但每个worker进程CPU都不高。后来看/proc/softirqs发现NET_RX暴涨确认是网卡接收软中断。解决方法是调整网卡多队列RPS和RFS配合把包处理分散到多核si立刻下来了。所以在CPU排查时别忽略hi和si这两项。2.3 CPU排查的几条实测经验先说一个生产环境的铁律别在线上用strace -f -p跟进程。我见过有人直接对生产Java进程执行strace -f -p PID结果目标进程性能瞬间下降几十倍本来是排查慢问题变成直接搞出故障。如果确有必要先用strace -c -p PID做几秒汇总看系统调用占比再决定要不要针对性跟某一个调用。热点函数定位用perf更安全。先perf top -p PID看看哪些函数占用CPU高所有列出的符号都能看到占比。如果还需要调用链可以短时间采集perf record -F 99 -g -p 12345 -o /tmp/perf.data sleep 30 perf report -i /tmp/perf.data-F 99是每秒采样99次对CPU密集程序来说30秒就能抓住热点。需要生成火焰图的话用perf script把数据转出来配合FlameGraph脚本处理。注意采样期间会有少量性能开销尽量避开业务高峰期。另外如果判断是计算密集且单核瓶颈可以考虑taskset绑定CPU核心或使用nice调整优先级。比如taskset -pc 2,3 12345 renice -n -5 -p 12345但这些只是临时规避手段根本解法还是优化算法、减少锁、或者合理线程池大小。线程数不是越多越好一般建议CPU密集型线程数约等于核心数IO密集型可以适当多一些但每增加一个线程都带来额外上下文切换成本。3. 内存瓶颈available比已用内存更重要内存诊断最大的误区是一看到free输出的used高或者buff/cache高就开始慌然后跑去清理缓存。Linux内存设计本质上就是把空闲内存拿来当缓存这是正常行为不是内存泄漏。3.1 free、vmstat、ps的用法细节先看free -h的完整输出free -h # total used free shared buff/cache available # Mem: 31Gi 18Gi 2.1Gi 680Mi 10Gi 12Gi这里最关键的是最后一列available。它表示当前有多少内存可以给新进程使用包括部分可回收的buff/cache。used是给应用的实际使用量不含缓存free是完全没有被使用的内存。如果available还很高就不用担心“内存不够”。只有当free和available同时很低才说明真的紧张。看进程内存时别光看top里的RES和VIRT。VIRT是虚拟地址空间大小一个进程能访问几十GB虚拟空间很正常不代表真的占物理内存。RES才是常驻物理内存但RES也包括共享库的内存多个进程会重复计算。更合理的是看smem工具输出的USS和PSSsmem -t -k -pUSS是进程独有的物理内存PSS是考虑共享库分摊后的内存。尤其你开了一堆Java进程或者PHP-FPM worker它们共享同一批libc、libjvm页用RES去预估“每个进程占多少内存”会偏高PSS和USS更接近真实。初步定位内存大户用这条ps aux --sort-%mem | head -20或者top进去按M键。找到进程PID后看/proc/PID/statuscat /proc/PID/status | grep -E VmRSS|VmSize|VmSwap|VmPeakVmPeak可以看进程内存历史峰值VmSwap看它用了多少swap。如果你发现某个进程的VmRSS持续上涨而且VmPeak等于或接近当前VmRSS就要高度怀疑内存泄漏。3.2 内存泄漏和Java等进程的特殊坑排查内存泄漏不能只做一次快照要做趋势。可以用一个简单循环监控for i in $(seq 1 30); do grep VmRSS /proc/12345/status; sleep 10; done如果RSS一路涨回收不下来那就是有问题。比这个更高效的是记录业务指标中的内存占用曲线比如应用的“已用堆内内存”指标和容器内存指标对照。Java进程是内存排查的重灾区。Linux系统层面看到RSS很高不代表Java堆真有那么高。常见原因是堆内内存-Xmx设置过大直接顶到容器上限在容器环境中Java 8u191之前的版本默认不感知Cgroup限制可能申请内存超过容器limit被内核OOM Killer杀掉。你需要确认Java版本并且合理设置-Xmx留出堆外内存的空间。堆外内存/直接内存NIO的DirectByteBuffer、Netty的堆外缓存、JNI code cache、Metaspace、线程栈都算在RSS里。用Native Memory Tracking查看jcmd 12345 VM.native_memory summary前提是Java启动时加了-XX:NativeMemoryTrackingsummary。如果没加就只能靠pmap分析映射段来推断。常见C/C服务也可以用valgrind或者jemalloc的profiling功能在测试环境复现生产环境就别硬上valgrind了开销太大。还有一点很多人看到buff/cache很大就执行echo 3 /proc/sys/vm/drop_caches来清缓存。我理解你的想法但这不是解决问题的手段反而会短暂增加磁盘IO可能让业务抖动。它最多用来验证“如果页缓存被回收系统还够不够内存”。正常情况不要在生产环境随手清理缓存。真正的排队缓存在内核里会自动回收。3.3 内存回收、Swap与内核参数的平衡要判断内存是否真的不足看两个地方vmstat 1中的si和so。如果si和so持续大于0说明系统在频繁swap换入换出物理内存确实紧张。sar -B 1 3中的pgscank/s和pgsteal/s。如果页扫描回收频繁说明内存分配压力已经传导到内核回收机制了。当内存不足到危及系统时内核会调用OOM Killer杀掉进程。排查OOM首先看日志dmesg -T | grep -iE out of memory|killed process输出的最后一段会告诉你被杀的进程名、当时内存使用、以及是什么触发了检查。常见的是普通进程一次性申请了超大内存比如一次性加载全量数据到内存或者memory.maxcgroup限制太紧。内核参数里和内存关系最大的几个我简单说一下我的用法vm.swappiness默认60表示内核在回收内存时倾向于使用swap而不是回收页缓存的“积极程度”。对于内存不太紧张、但希望减少swap的机器可以调到10~20。注意这不是关闭swap只是降低匿名页被换出的优先级。vm.overcommit_memory默认0表示内核会“对内存申请做启发式判断”允许一定程度的超额分配。对数据库这类吃内存的进程如果你不希望它malloc失败崩溃可以考虑保持默认即可不要乱调。vm.min_free_kbytes保留给内存分配关键路径的空闲内存太大会浪费内存太小又可能在紧急时刻分配不到。一般用默认值除非你有充分理由。还有一个容易忽略的坑HugePages。如果系统配置了vm.nr_hugepages这部分内存会被预留虽然free显示还有大量free但进程申请不到。排查时看grep -E HugePages_Total|HugePages_Free /proc/meminfo如果HugePages_Total很大但HugePages_Free很小而业务又不需要大页可以适当调低。如果是KVM虚拟化场景宿主机页表也常伴随这个问题需要结合虚拟化平台管理。容器场景下还要注意cgroup v2路径cat /sys/fs/cgroup/memory.current cat /sys/fs/cgroup/memory.max很多容器内free看的是宿主机整体内存并不代表容器可用内存。要判断容器进程是否快触碰limit看memory.current和memory.max的比值比看free更准确。4. 磁盘IO瓶颈不能只看%util磁盘IO是最容易误判的资源。很多人把iostat里的%util100%直接当成“磁盘坏了”或者“必须扩容”但现代SSD和NVMe设备上%util的含义已经不那么直观还需要结合等待时间和队列深度综合判断。4.1 iostat关键字段怎么读看磁盘整体状况用iostat -x 1 3输出示例Device r/s w/s rkB/s wkB/s %util await r_await w_await aqu-sz vda 12.5 320.0 512.0 40960.0 99.8 156.0 2.3 210.0 18.5我一般按这个顺序读先看await这是平均IO响应时间包含排队时间和服务时间。机械盘正常在10~20msSSD应该个位数毫秒甚至更低。如果await一两百毫秒甚至几百毫秒说明队列排得很长或者设备本身有问题。再看r_await和w_await区分读写。如果读等待高可能是缓存未命中、随机读多如果写等待高要关注是不是同步刷盘fsync、日志写放大。然后看aqu-sz平均队列长度。持续大于磁盘的队列深度比如NVMe可以达到32甚至更高传统硬盘通常为32说明排队严重。%util可以看但不要单独下结论。对单盘HDD来说100%确实意味着饱和但对支持多队列的SSD只要请求足够快即使设备繁忙时间很高也不一定真的达到瓶颈。必须结合await和r/s、w/s来判断。还有一个基础动作用df -i检查inode是否耗尽。就算磁盘还有几十GBinode用完了系统也会报No space left on device这类问题排查起来非常迷惑。4.2 怎么快速找到IO大户设备级指标只能告诉你哪块盘忙不能告诉你是谁在读写。要下钻到进程pidstat -d 1这会持续输出每个进程的读写速率。另一个更直观的是iotopiotop -o -P不过当IO是异步写回时pidstat里可能看不到写入进程因为脏页回写由内核线程比如kworker来完成。这时候可以看/proc/meminfo里的Dirty字段grep -E ^Dirty /proc/meminfo如果Dirty一直很大说明写缓存堆积而磁盘刷不出去。另一个方向是查这个进程到底在写什么文件。lsof按进程列打开文件lsof -p 12345 | grep -v mem\|DEL如果是数据库进程多半能看到redo log、binlog、数据文件。如果是日志服务可能看到*.log。通过打开的文件和读写速率基本能锁定是谁在制造IO。有些场景看不到明确的进程IO但iostat很高可能是NFS或网络存储挂载的问题。网络盘卡住会让进程进入D state但IO统计不一定体现在本地块设备上。这时候检查一下mount和nfsstat确认是不是远端存储慢。这一类“本地CPU高但进程都在等IO”的问题经常被误判成代码问题其实链路在远端。4.3 存储相关的常见坑与可落地的优化磁盘IO优化先说基础项。IO调度器机械盘用mq-deadline或bfq更好NVMe/SSD基本建议none云盘一般也是none。查看cat /sys/block/vda/queue/scheduler生产环境改调度器要测试因为有的存储设备对调度器非常敏感。挂载参数noatime可以减少读操作带来的元数据更新尤其在频繁读取的场景下有效。对于数据库文件系统日志模式也很关键。比如ext4默认dataordered保证数据块先落盘安全性好但性能比datawriteback低datawriteback只记录元数据日志突发断电有概率导致文件内容不一致。所以若要改先评估业务的数据安全容忍度。RAID写惩罚这属于老生常谈但我要再强调一次。RAID5/6做随机小写每次都需要读改写一个4KB写入实际可能在磁盘上产生很多IO写放大非常严重。如果你的业务是随机小写为主机械盘RAID5/6基本不适合。遇到这类问题除了加缓存卡更重要的是调整业务落盘方式。基准测试用fio很直接。比如测4KB随机写fio --namerandwrite --ioenginelibaio --rwrandwrite --bs4k --size2G --numjobs4 --iodepth32 --direct1 --group_reporting记录iops和latency再和业务实际需要对比。不管是云盘快照、本地机械盘还是SSD性能差距能用一个标准命令测出来。最后说一个容易被忽略但非常常见的现象大量小事务频繁fsync。数据库的innodb_flush_log_at_trx_commit1和sync_binlog1意味着每次事务提交都要把日志刷到磁盘。如果业务是每秒几千个写事务磁盘w_await会很高%util也会很高但实际数据量并不大。这时换更快的盘能缓解但更合理的是数据库层做批量提交、分组提交或者评估在业务可接受的数据丢失风险下调整刷盘参数。这里的核心是“IO次数比IO量更容易拖垮磁盘”优化要往减少IO次数方向走而不是一味扩容。5. 三JB类瓶颈的“组合拳”与一次完整排查演练现在你已经分别掌握了CPU、内存、磁盘IO的诊断方法。实战中最难的不是单看某个指标而是判断谁是“元凶”、谁是“受害者”。5.1 CPU、内存、磁盘如何互相伪装这三类资源会相互影响最常见的三个伪装场景我概括一下内存不足伪装成磁盘IO问题。当物理内存不够内核持续swap磁盘写入大量换页数据。此时CPU的wa会升高iostat也能看到大量的w/s。如果你只盯着IO做优化加磁盘、调调度器基本没什么用因为根源是内存不够。判断方法free的available很低vmstat里si、so持续大于0。磁盘慢伪装成CPU问题。磁盘响应极慢时大量进程进入不可中断睡眠D stateload average被拉得很高从uptime看就像系统过载。但top里CPU使用率可能并不高反而是进程列表里D状态进程很多。判断方法vmstat的b列大于0且wa高ps -eo state,pid,cmd | grep ^D能看到等待IO的进程。CPU高引发内存紧张。当进程疯狂计算并不断申请内存系统为了满足分配会频繁回收缓存、扫描页表这在sy上也会有体现看起来是内核态CPU高实际是应用层内存分配压力。判断方法结合sar -B和pidstat看是哪个进程的CPU和RSS同时飙高。所以别急着下结论。看到单点指标高先拿另外两个维度的数据交叉验证再决定优化方向。5.2 一次真实场景的排查记录下面这个案例是我虚拟化过的典型流程但逻辑和我实际排查时一致。现象某8核云服务器业务反馈数据库写入接口偶发超时检查发现load average到达8.5接近核数。第一步uptime确认负载确实高。第二步top看全局%Cpu(s)里us只有18%sy10%wa42%idle 30%。进程列表里MySQLmysqldCPU不算特别夸张但下方有一堆处于D状态的进程。仅到这里我已经把方向从“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 5 80000 120000 0 0 0 0 2100 7800 2100 2300 18 10 30 42 0这里关键是b列持续为5左右wa稳定在40%以上bi和bo都不小。排除内存交换si/so均为0我判断当前CPU确实在忍受IO等待。第四步iostat -x 1 3结果vda %util99.9 w/s650 wkB/s8000 w_await280ms aqu-sz19写等待280ms对SSD云盘来说已经非常高了而且队列快20。到此基本锁定磁盘写方向是主要瓶颈。第五步用pidstat -d 1确认IO来自哪个进程输出显示mysqld的kB_wr/s约6000左右几乎占满所有写IO。结合lsof -p看到它正在写binlog和ib_logfile这基本就是事务提交时的redo写操作。第六步检查有没有其他干扰因素执行dmesg -T | tail -50没有出现OOM或磁盘IO错误排除硬件异常。结论是数据库的小事务写提交太频繁导致磁盘IOPS被打满。后续优化方案是数据库端改用分组提交适当合并事务降低fsync频率如果业务允许调整innodb_flush_log_at_trx_commit从1到2或0但要接受断电丢失部分日志的风险同步优化慢查询减少不必要的写放大长期看把这台机器的数据盘从普通云盘换成更高IOPS的SSD。这个案例最有价值的地方是它避免了一次“盲目扩容”即使配置翻倍如果小事务fsync的问题不解决同样会满。要先定位行为再决定买什么“药”。5.3 把性能排查变成日常习惯性能瓶颈定位不应该只在故障发生时做。我建议每个团队至少把下面三件事固化下来第一周期性采集系统状态。部署sysstat并开启让它定时记录sar数据。这样故障发生后即使没有现场抓包也可以回看过去的CPU、内存、磁盘历史曲线。配置好之后确认一下进程在跑systemctl enable --now sysstat第二建设基础监控。用node_exporter采集CPU、内存、磁盘、网络指标用Prometheus存起来Grafana展示。告警阈值可以参考持续5分钟iowait大于20%available内存低于20%磁盘await持续超过100ms或者load average连续多分钟超过核数。但阈值一定要结合业务调整没有放之四海皆准的数值。第三故障复盘时把命令输出保存下来。每次排查问题我都习惯把所有命令的输出保存到本地命名带上时间戳20250615-db-slow-top.txt、iostat.txt。复盘时再对照当时的变更记录和业务指标。这个习惯帮我节省了无数重复排查的时间。另外把常用的排查步骤写成团队文档或者运维脚本是一个低成本高收益的做法。比如一个脚本里按顺序执行uptime、top -bn1、vmstat 1 3、iostat -x 1 3、free -h、dmesg -T | tail -20一键收集现场信息。下次再有人喊“系统卡了”先让他跑一遍这个脚本而不是丢一张模糊的截图。我个人在实际操作中的体会是性能排查最怕的不是技术不高而是判断太快。看到高就重启看到高就扩容往往掩盖了真正的问题。CPU、内存、磁盘IO三者是互相纠缠的只有掌握完整工具链再加上持续的数据记录才能在高压下快速且准确地定位。最后再分享一个小技巧每次排查前先记录当前时间点、变更上下文和采样的命令序列。故障就像案件现场证据链越完整复原真相就越容易。这套方法论不仅能解决Linux系统的性能瓶颈也能让你少熬几个深夜。