尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Linux性能瓶颈定位:CPU、内存与磁盘IO诊断实战

Linux性能瓶颈定位:CPU、内存与磁盘IO诊断实战 做Linux运维和开发这些年我最怕的不是系统直接宕机而是那种“明明没挂但所有业务都在卡”的状态。CPU居高不下只是最明显的信号更多时候负载看着不高、内存显示还有几十G、磁盘也没满可接口就是慢得像蜗牛。这种问题之所以难搞是因为CPU、内存、磁盘IO这三个子系统互相牵连你单独看哪一个都不觉得异常合在一起就是“系统很吃力”。这篇文章就围绕Linux系统性能瓶颈定位这件事把CPU、内存、磁盘IO三大核心子系统的诊断方法完整拆开从命令输出到指标含义、从定位思路到处置手段都按实战经验来讲。我尽量不堆砌教科书理论字字都来自一线的排查记录适合正在被“系统到底慢在哪”折磨的运维、后端开发以及所有想系统掌握Linux性能排查方法的人。1. 诊断前先建立全局观1.1 性能瓶颈定位的三步法我刚入行那会儿一遇到性能问题就喜欢直接按load average和CPU使用率判断结果经常被“假象”带偏。后来踩的坑多了总结出一套三步走的思路现在遇到任何性能问题都先按这个顺序走。第一步是确认现象。先回答几个问题是整体反应变慢还是某个接口变慢是偶发性变慢还是持续性变慢是最近变更后才开始的还是一直如此这几个问题看似简单却能帮你过滤掉一大半无关信息。比如业务方说“系统慢”但你一查CPU和IO都正常那问题可能根本不在本机而在于数据库的连接数满了、或者依赖的下游服务超时这就是典型的排查方向错误。第二步是观察全局指标。用uptime、vmstat、free、iostat这组命令先看机器整体水位而不是一上来就盯进程。因为性能问题往往是多个子系统共同导致的只看进程容易被局部数据迷惑。全局指标能帮你快速判断瓶颈的方向是CPU忙不过来还是等IO还是swap在疯狂换页。第三步是收敛范围。确定了方向后再用pidstat、perf、iostat -x这类工具精确到进程、到线程、到盘、到文件。这一步是真正的定位过程前两步做的越扎实这一步就越快。很多老手最快能在五分钟内定位问题靠的就是前两步判断精准第三步根本不需要反复试错。1.2 工具清单每一条命令解决什么问题Linux自带的性能工具其实已经非常够用不需要一上来就装各种花哨的监控平台。我常用的工具就这十几个每一个我都试着用大白话给你讲清楚它到底能解决什么问题。uptime看机器过去1分钟、5分钟、15分钟的平均负载。这是最粗粒度的“红绿灯”。top交互式查看全局CPU、内存概况按CPU或内存排序看进程占用。适合第一眼扫描。mpstat -P ALL按CPU核心分别统计能看出是单核瓶颈还是整体瓶颈。pidstat按进程或线程输出CPU、内存、IO、上下文切换的详细数据是精确定位的利器。vmstat输出线程数、上下文切换、CPU状态、内存换页、块设备读写等综合指标性价比极高。free查看物理内存和swap使用情况注意区分buff/cache与真实占用。iostat -x输出每个块设备的IOPS、吞吐量、等待时间、队列长度、利用率等详细指标。iotop类似top但按进程显示IO读写速率能揪出“谁在偷偷占用磁盘”。sar系统的历史数据记录器可以回顾过去一段时间CPU、内存、IO的走势。slaptop查看内核slab缓存占用情况定位内存被内核占用的场景。perf内核级别的采样分析器能定位CPU热点函数。dmesg查看内核日志OOM、磁盘IO错误、硬件异常都在这。你不需要把每个工具都背下来但至少要知道粗查用top、vmstat细查用pidstat、iostat -x查历史用sar。后面我会按子系统逐层展开每一条命令该读哪个数字、那个数字该怎么理解、落到实际操作里怎么处置都会讲到。2. CPU瓶颈的定位与分析2.1 用uptime和top把“CPU是否真忙”搞清楚先说uptime这是最容易被误读的命令。它的输出长这样$ uptime 14:32:10 up 120 days, 3:22, 2 users, load average: 5.02, 4.56, 4.01load average后面三个数字分别是过去1分钟、5分钟、15分钟的平均负载。很多人一看load超过CPU核数就认为CPU瓶颈这个判断太粗暴了。load average统计的是处于可运行状态R状态和不可中断睡眠状态D状态的进程数也就是说它不仅包含正在用CPU的进程还包含正在等磁盘IO、等网络IO的不可中断进程。这就是为什么数据库服务器上经常出现load很高但CPU空闲很多的情况——大量的进程在等磁盘读根本没在用CPU但load照样被顶上去。所以我的建议是先看趋势而不是看绝对值。如果15分钟前负载还在3现在突然到5说明有个什么东西在往上冲该查如果负载一直在5上下浮动但业务没感知到明显卡顿可能只是机器上有固定批处理任务不一定需要处理。另外还要看核数四核机器的load5和十六核机器的load5完全是两回事。判断CPU是否真的忙不能只看load要看top里us和id的占比。top命令第二行会输出CPU使用率这行信息量很大%Cpu(s): 12.5 us, 3.2 sy, 0.0 ni, 80.2 id, 3.5 wa, 0.3 hi, 0.3 si, 0.0 stus是用户态sy是内核态wa是等待IOid是空闲hi是硬中断si是软中断st是被虚拟化宿主抢走的时间。判断思路很简单id只有20%以下说明CPU确实在忙如果us占比很高说明用户态程序在大量消耗CPU如果sy占比很高说明内核在忙系统调用、锁竞争或中断处理如果wa很高说明CPU在等磁盘真正的瓶颈在IO而非CPU。2.2 用mpstat和pidstat精确锁定“谁在消耗CPU”top能告诉你整体CPU忙不忙但“整体很忙”和“某几个核心很忙”是两种完全不同的情况。有些应用是单线程密集计算的比如部分老旧的Java应用、Redis的某些版本、或者你写的某个单线程脚本它们只会把一个核跑满其他核都闲着。如果你只看top的总体%CPU可能只有25%四核机器会误判为“CPU没有瓶颈”但实际上你的业务可能已经被这个单核瓶颈卡死了。这时候必须用mpstat把每个核心分开看$ mpstat -P ALL 1 Linux 5.15.0 (hostname) 07/11/2024 _x86_64_ (8 CPU) CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle all 20.13 0.00 3.52 1.90 0.00 0.38 0.00 0.00 0.00 74.07 0 30.15 0.00 5.26 0.65 0.00 0.75 0.00 0.00 0.00 63.19 1 5.62 0.00 1.25 2.31 0.00 0.20 0.00 0.00 0.00 90.62如果某个核心的%usr明显高于其他核心说明确实有单线程瓶颈。接下来用pidstat把这个进程找出来$ pidstat -u 1 5 Linux 5.15.0 (hostname) 07/11/2024 _x86_64_ (8 CPU) Average: UID PID %usr %system %guest %wait %CPU CPU Command Average: 1001 12345 89.66 2.44 0.00 15.40 92.10 2 java这里要重点看最后一行的Command和%CPU。如果某个进程的%CPU接近100且长驻基本就锁定目标了。再往下如果你想看这个进程到底是哪个线程在烧CPU、对应的代码在哪需要先找到线程号。用top -H -p PID看线程或者直接用perf top去采样热点函数$ perf top -p 12345 --stdioperf的输出会直接告诉你调用栈的顶层函数比如lock_wait、sched_yield、某个哈希计算函数对于定位代码级别的热点非常有用。这一条在实际工作中救过我很多次特别是你拿到一个陌生的Java进程时perf能帮你快速确认它到底是真在计算还是卡在某种锁等待上。2.3 上下文切换、中断与st值的隐藏信号有些时候CPU的us和sy看起来都不高id也还剩不少但系统就是“感觉很忙”。这时要去查两个经常被忽略的指标上下文切换和中断。上下文切换用vmstat看cs列或者用pidstat -w按进程来看$ vmstat 1 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 4 0 0 8123456 0 4096 0 0 0 0 1234 5678 15 6 77 2 0cs列就是每秒上下文切换次数。如果这个数值持续上万就要警觉了特别是一些线程数暴涨的Java应用或者大量I/O密集的小线程并发场景。上下文切换本身不是罪但它是系统开销的放大器——每次切换都要保存和恢复寄存器状态切换越多CPU的实际有效工作时间越少。再看中断。vmstat里的in列记录每秒中断次数其中硬中断可以通过/proc/interrupts查看软中断通过/proc/softirqs查看。如果你发现某个网卡的中断数异常高同时sy占比上升那很可能是网络包量过大触发了大量的软中断处理。我在一次排查中遇到过类似问题机器负载不高但sy一直占20%以上用cat /proc/softirqs一看NET_RX这一项突破了千万最后定位是某个服务在疯狂接收小包把CPU的内核态时间给吃掉了。最后说st也就是steal time。很多朋友用虚拟机、云主机时没注意这一项结果CPU高的时候全归咎于业务其实问题出在宿主机上。st占比高意味着宿主机资源超卖你的CPU时间被其他虚拟机抢走了这种情况你无论怎么调优业务都没用只能和云平台沟通迁移或升配。3. 内存瓶颈的定位与分析3.1 free命令背后的机制buff/cache不是“已用”内存问题要比CPU更容易误判因为Linux对内存的管理机制和Windows差别很大。很多人一看到free -h输出的used很高就慌了觉得内存不够了。实际上Linux的内存管理有一个核心原则闲着也是闲着不如拿来缓存。所以那占比很大的buff/cache部分是系统用来加速磁盘读写用的在进程需要时会自动释放。真正能反映“还能再用多少”的指标是available列它在free的基础上加上了可回收的缓存部分。$ free -h total used free shared buff/cache available Mem: 31Gi 18Gi 1.2Gi 256Mi 12Gi 12Gi Swap: 2.0Gi 100Mi 1.9Gi当available低于总内存的10%甚至更低时才需要紧张。真正的内存压力信号不在free里而在于是否开始使用swap。Linux会在内存紧张时把不常用的内存页换到磁盘上的swap分区这一操作极其昂贵因为它会让后续的内存访问变成磁盘IO性能下降是数量级的。很多人会直接把swappiness设成0来禁用swap这个做法我不太推荐。swap在系统设计上是一个安全兜底直接禁掉可能导致OOM杀进程。更好的办法是把swappiness调到一个较小值比如10让系统只在内存极度紧张时才启用swap$ sysctl vm.swappiness10 $ echo vm.swappiness10 /etc/sysctl.conf3.2 swap和内存回收vmstat里的si/so到底说明了什么vmstat的si和so两列是判断内存是否真正吃紧的金标准。si表示每秒从swap换入内存的数据量so表示每秒从内存换出到swap的数据量。只要这两个值持续非零说明内存已经不够用了系统正在不断做内存页的换入换出这会直接把磁盘IO拖垮。$ vmstat 1 10 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 3 1 20480 100000 0 5000 100 250 512 2048 1234 5678 20 15 40 25 0注意看上面这个样本so250意味着每秒都有250KB的内存被换到swap上同时wa25说明CPU已经有四分之一的时间在等IO。这种状态就是典型的“内存不足引发IO雪崩”——程序需要的数据被换到磁盘读的时候要从swap读回来同时又要换走新的内存页磁盘在没完没了的换页中空转系统自然慢得离谱。遇到这种状态优先考虑给业务进程加内存、减少同时运行的进程数量、或者直接调整编码层的内存使用策略。在排查期间可以用一个快速判断看看哪个进程的RSS最大直接用ps aux --sort-%mem找到前几个大内存占用者然后回到业务层面问自己一个问题——“它真的需要占这么多吗”3.3 内存泄漏、OOM和进程级内存定位内存泄漏是Java和C应用最容易出问题的地方也是最难排查的类型之一。判断是否存在泄漏方法其实很简单持续观察某个进程的RSS物理内存占用是否只涨不降。用pidstat -r可以实时看$ pidstat -r 1 10 Average: UID PID minflt/s majflt/s VSZ RSS %MEM Command Average: 1001 12345 123.50 0.00 4200000 3500000 11.3 java如果RSS在数小时内持续攀升而且重启进程后又恢复正常基本可以锁定为内存泄漏。有些同学会问minflt/s和majflt/s是干嘛的minflt是“小缺页”表示进程访问了不在物理内存但在虚拟内存中的页majflt是“大缺页”表示要访问的页不在内存里必须从磁盘读取。majflt/s过高说明进程确实在频繁读磁盘通常和内存不足或文件映射有关。当内存被吃光后Linux的OOM killer会启动根据一套打分机制选一个进程杀掉来释放内存。判断OOM的方法很直接$ dmesg | grep -i oom内核日志会告诉你什么时候触发的OOM、杀掉了哪个进程。但我在实际处分里发现OOM日志晚了——当你在dmesg里看到信息时进程已经被杀了业务已经受损。所以更靠谱的做法是提前监控留意内存available掉到警戒线、或用/proc/meminfo里的MemAvailable来做阈值告警。还有一个常被忽略的点是内核自身的slab内存占用用slabtop看一眼如果某些对象数量异常膨胀往往和内核模块或容器相关。4. 磁盘IO瓶颈的定位与分析4.1 iostat -x磁盘读写的体检报告磁盘IO瓶颈的定位一半的答案都在iostat -x的输出里。这个命令能看到每个块设备最细的读写指标$ iostat -x 1 5 Device r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await %util sda 12.00 45.00 128.00 512.00 22.72 3.45 50.23 15.00 60.50 55.60先看%util它表示设备忙闲程度100%表示在整个采样周期内设备一直都在干活。但我要提醒你一句%util100不一定就是瓶颈。对于底层有RAID阵列或SSD的设备后台做得很多合并和缓存工作%util会高于真实压力。反过来有些单块机械盘%util只有60%但业务已经卡得不行因为机械盘的寻道时间占了大头等盘的时间早就把请求拖跨了。重点需要看的是await也就是平均IO响应时间。机械硬盘在负载适中时await应该在10ms以内SSD应该在1ms量级。如果await到了几十甚至上百ms说明磁盘已经撑不住了。还要把读等待r_await和写等待w_await拆开两种场景的处理方式完全不同。读等待高通常是数据没有命中缓存比如数据库冷启动、频繁的随机查询写等待高往往是日志同步、fsync刷盘过多或者写放大。avgqu-sz是平均排队长度如果持续大于存储设备允许的队列深度机械盘往往在32SSD在几百说明IO请求已经排队拥堵设备处理不过来了。4.2 IOPS、吞吐、延迟和队列四个数字怎么关联排查IO性能问题时很多人只盯着一两个数字看很容易漏掉关键信息。其实IO性能有四个互相关联的核心指标IOPS每秒IO次数、吞吐量每秒传输的字节数、延迟单次IO的耗时、队列长度有多少IO在等待处理。这四个数字必须放在一起看才能还原真实情况。先说IOPS和吞吐量。IOPS对应的是iostat里的r/s和w/s吞吐量对应rkB/s和wkB/s。这里存在一个经典误区如果单次IO的数据量特别大比如数据库做全表扫描每秒IO次数看起来不多但吞吐量已经打满了如果单次IO数据量很小比如随机小数据块读写吞吐量看起来不高但IOPS可能已经顶满。所以你不能只追求IOPS高或者吞吐量高要先搞清楚业务到底是随机小IO还是顺序大IO。延迟是和队列长度连着的。IO请求发到磁盘后等待时间就是await的一部分。队列越长每个请求的等待时间就越长延迟自然一路飙升。判断方式很直观如果avgqu-sz不大但await很高说明磁盘本身出问题了比如坏道、盘片老化如果avgqu-sz和await同时很高那就是磁盘被压爆了需要扩容或优化IO访问模式。还有一个实用技巧用blktrace或perf直接跟踪IO请求在内核中的流向但这属于进阶玩法一般生产环境用iostat加iotop就够定位大部分问题了。4.3 按进程定位IO与常见IO瓶颈场景iostat能告诉你哪块盘慢但它没告诉你哪个进程在制造这些IO。要找出“IO的罪魁祸首”得用iotop$ iotop -oP TID PRIO USER DISK READ DISK WRITE SWAPIN IO COMMAND 1234 be/4 mysql 0.00 B/s 2.50 M/s 0.00 % 55.00% mysqld-o只显示实际有IO的进程-P按进程显示。就从这一行你就能看到mysqld每秒写2.5MB并且它自己占用了55%的时间在等IO完成。这种情况下你完全不用再猜直接冲向数据库层去调慢查询、加缓存、或者调整刷盘策略。常见的IO瓶颈场景有几种我遇到的频率从高到低排一下。第一是数据库类应用的刷盘压力mysql和postgres的binlog、redo log每次事务提交都在fsync磁盘慢的话整体事务能力就被卡死。第二是日志写得多但没合理轮转应用日志从早写到晚同一块盘还被业务数据和备份共用。第三是备份和大数据导出任务凌晨的备份任务往往把整块盘的IO吃光还连累白天高峰期的业务。每个场景的对策不太一样。刷盘压力大的考虑把日志和应用数据分离到不同物理盘、调整fsync策略或使用SSD日志多的用logrotate压缩归档必要时把日志写到独立磁盘备份任务和业务高峰错峰执行这是成本最低最容易见的优化。5. 综合排查与踩坑实录5.1 三个容易误判的经典场景排查工作做久了你会发现大部分时间不是在找瓶颈而是在排除“伪瓶颈”。下面三个场景是我周围同事包括我自己都踩过好几次的典型坑。第一个坑是load高、cpu idle高。你一定见过这种机器top里load average已经到20了但CPU的idle还有70%谁看了都觉得不对劲。原因我刚才说过——load包含D状态不可中断睡眠进程大量D状态进程通常是磁盘IO等待导致的。你可以用ps aux列一下看看STAT列有没有成片的D状态进程如果有方向就指向IO而不是CPU。我当时排查一个数据库集群时就靠这一招定位到是某共享存储的网卡故障引发了IO卡死进程全部堵在D状态。第二个坑是内存显示used高就慌却没意识到可用比fully可用。我记得有个老朋友半夜打电话说服务器内存满了业务要挂了。我看了一眼freebuff/cache占了30多个Gavailable还有20多个G直接告诉他机器稳得很别动。这就是对Linux内存机制不熟的表现我前文也强调过先看available再看swap利用率这两个没异常就别慌。第三个坑是iostat的%util不高但应用卡。这个场景常出现在使用网络存储的环境。本地磁盘的%util不高但是业务确实在等IO——因为慢的是NFS或SAN的远程存储本地iostat根本看不到。排查时需要看nfsiostat、看网络延迟或者直接把业务IO目录挂到本地试一把。这个坑最容易让人怀疑人生明明“磁盘”没问题怎么业务就是卡呢。5.2 快速诊断速查表与“一套组合拳”实际排障时拿到一台机器我不会一条一条命令敲而是按套组合拳来一遍五分钟内就能对系统状态心里有底。这套组合拳是这样的uptime top -bn1 | head -20 vmstat 1 5 free -h iostat -x 1 3 ps aux --sort-%cpu | head -10 ps aux --sort-%mem | head -10第一遍看uptime的负载趋势第二遍看top的CPU各态占比第三遍看vmstat里的r、b、cs、in、si、so、us、wa第四遍看内存available和swap第五遍看磁盘await和%util最后看CPU和内存占用前10的进程。这一套下来95%的问题是哪种类型基本能判断出来。我把判断逻辑整理成一张速查表方便你对照着看观察现象可能原因下一步动作us高id低用户态程序占满CPUpidstat定位进程并用perf查热点sy高id低系统调用或中断过多查/proc/interrupts、软中断、锁竞争wa高磁盘IO跟不上用iostat -x定位盘iotop定位进程st高宿主机资源超卖联系云平台或迁移si/so持续非零内存严重不足swap换页加大内存、降低进程内存占用available低内存紧张用ps aux --sort-%mem找大内存进程await高avgqu-sz高磁盘队列拥堵优化IO模式、换更快的盘await高avgqu-sz低磁盘盘体或链路异常检查磁盘健康状态、RAID状态load高id高大量D状态进程等IOps aux看STAT列定位IO瓶颈这套对照表的本质是“现象到方向的映射”。记住一点不要拿着一个指标死磕要把多个指标串起来看形成一个“现象链”才能尽量避免被单点数据误导。5.3 建立自己的监控基线诊断做完、瓶颈解决之后很多人会直接收工然后下次问题复现时再从头排查一遍。我的建议是每一次排障都应该沉淀成监控基线这是新手到老手最快捷的路径。所谓基线就是这台机器在正常状态下的指标范围。比如一个普通的四核16G的Web服务器正常运行的时候CPU的id通常在80%以上load不超过核数available内存不掉到4G以下iostat的await基本在5ms以内。这些数字不是凭空设定的是你连续观察一周以上总结出来的。当你有了基线再遇到今天这个指标突然飙升的情况对比起来就一目了然。具体做法是把这一次排查中用到的命令和关键输出记录下来标注当时的业务状态和结论整理成一份文档或笔记。以后每次看到类似指标直接翻出来对照处理速度会快很多。我自己就是这样从“遇到问题翻资料”慢慢变成“遇到问题翻自己的笔记”的。另外有条件的话尽早部署监控。我用过Zabbix、PrometheusGrafana也用过纯脚本采集但核心诉求是一致的——把CPU、内存、磁盘IO、网络的历史数据保存下来并设置合理告警阈值。有了历史数据你就不需要“等到问题发生那一刻正好在现场”才能看到那块盘的%util暴涨了因为曲线会替你记录一切。我个人在实际操作中最深的体会是性能排查不是数学题不是哪个指标超标就一定是哪个瓶颈。三大子系统之间会互相“传染”——内存不足引发swap导致磁盘IO飙升磁盘IO飙升导致CPU wa升高wa升高又让应用吞吐量下降最终表现成全面卡顿。只有把CPU、内存、磁盘IO放在一起看沿着“现象链”反向追根才能真正找到问题根源。这套思路我沿用至今也希望你读完这篇文章后下次遇到类似问题能少走几条弯路。
返回列表