
作为天天跟 Linux 服务器打交道的运维我几乎每天都要打开 top 看那么几眼。但说实话用久了你会发现一个很诡异的现象有时候 top 里 CPU 使用率明明不高才 20% 多系统却卡得跟幻灯片一样反过来有时候 CPU 都飙到 90% 了业务却一点毛病没有。如果你也遇到过这种情况那说明你已经被 top 这层皮给骗了。top 只是给你看了一张快照真正决定系统卡不卡的是 CPU 使用率背后那三个纠缠在一起的东西负载load average、内存带宽DDR和 IO。这篇文章我想把我这些年排查性能问题的实战经验掰开揉碎讲清楚——不仅仅是看懂 top 的输出而是搞明白从 CPU 到内存再到磁盘这条链路上瓶颈到底卡在哪个环节。1. 从 top 第一行开始CPU 使用率只是表象1.1 top 里的 CPU 百分比到底是怎么算出来的很多人看 top上来就盯 %Cpu(s) 这一行看到 us 30%、sy 5%就觉得机器挺闲。这个直觉在绝大多数场景下是错的。top 显示的不是CPU 有多忙而是CPU 在过去一个刷新周期里有多少时间花在了非空闲任务上。Linux 内核会为每个 CPU 维护一组时间计数器分别记录userus用户态进程消耗的时间systemsy内核态消耗的时间niceni被调整过优先级进程消耗的时间idleid完全空闲的时间iowaitwa等待 IO 完成的时间irq/softirqhi/si硬中断和软中断消耗的时间stealst在被虚拟化环境中被 hypervisor 抢走的时间top 默认每 3 秒刷新一次它做的事情就是取两个时间点的差值用非空闲时间除以总时间得到一个百分比。也就是说你看到的 30% 是这 3 秒里 CPU 有多少比例的时间非空闲而不是CPU 的平均负载程度。关键问题就在这里只有 us 和 sy 高才代表 CPU 算力真的在被消耗。如果 waiowait占了 30%CPU 实际上是闲着的它在等磁盘、等网络、等内存这种忙是虚假的忙。注意top 里的 wa 列是整个 Linux 性能排查里最容易被误读的指标之一。它高说明 CPU 在空转等待 IO你的系统瓶颈大概率在存储层面而不是计算层面。1.2 瞬时快照 vs 时间均值top 天生带近视眼top 的另一个大坑是它只看瞬间。你看到 CPU 使用率 40%但实际这 3 秒里可能有 2 秒 CPU 打满了另 1 秒在休眠。对于延迟敏感的在线业务这种抖动是致命的但 top 的采样周期根本看不出来。这也是为什么我强烈建议在排查问题时不要单看 top。sar 和 atop 这类工具会做完整的时间序列采集。sar -u 1 10 可以每秒采样一次输出均值atop 则能以 10 秒为单位记录每核每进程的完整历史。生产环境里我通常会在服务器上长期挂着 atopsar出事的时候直接回放当时的 CPU、内存、磁盘、网络全景比对着 top 猜要靠谱一万倍。实操建议top 只适合快速看一眼不适合深入定位问题。一旦 top 显示异常立刻切到 sar/vmstat/pidstat 去拿连续时间维度的数据。1.3 多核场景下top 的百分比是怎么骗人的在 32 核的机器上top 显示的 %Cpu 是所有核的平均值。假设只有 4 个核被打满了另外 28 个核闲着top 显示的是 12.5%看起来毫无压力。但应用如果是单线程或者线程数很少的它就卡在那 4 个核上了。所以看 CPU 使用率我基本不看汇总行而是进 top 后按一下数字键 1把每个核单独列出来看。如果某一两个核持续 100%其他核比较低说明有线程绑定或者热线程集中的问题如果所有核都很均匀地高才说明并行负载起来了。还有一个进阶技巧top -H 可以切换到线程视图找到消耗 CPU 最高的线程 ID然后用 pthread 库或者 Java 的 jstack 去反查代码层是哪个线程在作妖。这是排查 CPU 问题的标准动作。2. load average那个1.5到底是什么意思2.1 别再管 load 叫CPU 使用率了它是任务队列长度top 右上角第一行有个 load average: 1.50, 2.00, 2.50很多人以为这是 CPU 使用率的平均值。大错特错。load 是内核运行队列上的可运行进程数 不可中断进程数的平均值。我来拆一下这两个数可运行进程TASK_RUNNING正在 CPU 上跑的或者在就绪队列里等着 CPU 调度的进程不可中断进程TASK_UNINTERRUPTIBLE正在等 IO 完成的进程通常是在等磁盘读写这种状态在 ps 里显示为 D内核会每 5 秒把这个数值算一次然后分别取 1 分钟、5 分钟、15 分钟的移动平均就是你看到的 load average 的三个值。关键结论来了一个进程只要在等待磁盘 IO它就会一直挂在D 状态持续占用一个 load 名额。cpu 使用率低IO 慢load 照样能冲到 20 以上。2.2 为什么 D 状态进程会让 load 飙到一个离谱的数字我们之前排查过一台数据库服务器现象特别经典CPU 使用率 15%load 却飙到了 35。大家一开始都以为是某些进程死循环了用 top 按 CPU 排序一看排在前面的是几个 PostgreSQL 后台进程CPU 占用才 1%。真正的问题出在哪用 ps -eo pid,state,cmd 一看大量进程的状态是 D。它们全在等一块慢到家的机械硬盘做数据落盘。这些 D 状态进程既不能被 kill也不会被调度器放假就硬生生地占着 load 名额。内核为什么这么设计因为不可中断状态是为了保证进程在等待 IO 完成时不会丢失数据如果强行中断IO 回来的数据就没人接收了。这也是为什么 D 状态进程连 kill -9 都杀不掉——内核根本不响应这个信号只能等 IO 超时这是保护机制但也是 load 虚高的根源。2.3 判断 load 是否正常的经验法则很多文章说 load 的及格线是 CPU 核数四核机器 load 超过 4 就要报警。这个说法其实太粗糙了。我的经验是分场景看纯计算型任务load 超过核数*0.8 就该警惕了说明 CPU 快饱和了IO 密集型任务load 可以超过核数很多因为大部分时间 CPU 在空闲等待 IO此时要结合 iowait 和磁盘 util 判断混合型业务互联网后端的常态load 和核数的比例在 1 到 1.5 之间问题不大超过 2 就要看是 CPU 问题还是 IO 问题的分叉了另外 load 的三个值一定要结合起来看。1 分钟值 15 分钟值说明系统负载在上升是个异常信号反过来说明负载在下降可能刚经历了一次抖动正在恢复。注意只看 load 绝对值没意义要看趋势。连续几个时间点 load 都在攀升才是需要干预的时候。2.4 用 vmstat 验证 load 的构成别只盯着 top要搞清楚 load 到底是 CPU 撑起来的还是 IO 撑起来的我通常直接上 vmstat。重点关注两列rrunning和 bblocked。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 1 2 0 408960 12340 4098600 0 0 12 64 0 0 5 2 78 15 0 3 4 0 406920 12340 4098800 0 0 256 1024 1200 8000 10 5 55 30 0r 列是正在运行和等待 CPU 的进程数。这个值长期大于核数说明 CPU 确实是瓶颈b 列是处于 D 状态阻塞在 IO的进程数。b 列显著大于 0尤其大于 r 列时说明 load 主要是 IO 撑起来的那一行我一直记着r 是 CPU 排队的人数b 是 IO 排队的人数。load 高的时候先分清楚是哪个队伍排长队了再动手去解决方向才不会跑偏。3. DDR 内存带宽CPU 算不过来还是数据喂不进来3.1 CPU 和内存之间那条水管才是真瓶颈很多人有个误解CPU 使用率高 瓶颈在 CPU。其实不对。CPU 使用率高的另一种可能是CPU 一直在等内存数据。这里要理清一个概念CPU 内部有缓存L1/L2/L3缓存没命中才去访问内存DDR。访问 L1 缓存大约 1ns访问 L3 大约 10ns访问内存则要 100ns 量级。一旦算法或数据布局导致缓存命中率差CPU 就大量地陷入等内存的状态。CPU 厂商为了解决这个问题搞了乱序执行、超标量流水线、预取器。看起来 CPU 在满负荷运转实际上指令队列里有大量空泡——都在等内存的数据回来。此时 top 的 us 可能还是 80% 多但真正的算力产出很低为什么因为 CPU 的执行单元大部分时间是闲置的只是流水线前端一直在忙碌地发射指令。3.2 内存带宽怎么算又该怎么侧DDR 内存有标称带宽计算方式很简单DDR4-2666 的理论带宽 2666 MHz × 8 字节 × 2双倍速率≈ 42.6 GB/s。但这是理论峰值实际能用到的通常只有理论值的 60%-80%原因包括内存控制器开销、地址映射开销、ECC 校验等。判断应用是否在吃内存带宽简单粗暴的方法是看 CPU 的访存指令比例。用 perf stat 可以看真实数据perf stat -e cycles,instructions,cache-references,cache-misses -p PID重点关注 cache-misses 的占比。如果 miss 率超过 10%说明内存访问已经成为瓶颈。另一个更直接的指标叫 CPIcycles per instruction即每条指令平均消耗多少 CPU 周期。CPI 越高说明 CPU 等数据的时间越长。正常计算密集型程序 CPI 在 0.5 到 1 之间如果超过 2基本可以断定在等内存。3.3 NUMA 架构下的内存访问差异现在服务器几乎都是多路 NUMA 架构。CPU 访问本地内存和远端内存的延迟差别很大。比如 2 路服务器CPU0 访问自己插槽上的内存条延迟 70ns访问 CPU1 插槽上的内存则要 130ns 左右吞吐量差距更明显。这带来一个很现实的问题如果你的进程被调度到了 CPU0但它主要访问的数据在插槽 1 的内存里性能直接打七折。这个现象在 top 里是完全看不出来的——CPU 利用率照样高但吞吐量就是上不去。排查办法用 numastat -p PID 看进程的内存分配情况。如果 local_node 的命中率很低说明跨 NUMA 访问严重可以考虑用 numactl --cpunodebind0 --membind0 绑定进程或者用 numa_alloc_onnode 之类的 API 来优化内存分配策略。3.4 用压力测试验证内存带宽的上限如果你怀疑是内存带宽不够别猜直接压。stream 是个简单粗暴的内存带宽测试工具编译完直接跑能看到 Copy、Scale、Add、Triad 四类操作的实际带宽。我一般会在新服务器上线前跑一遍 stream把结果记到资产台账里。这样后面如果觉得CPU 应该很快但实际很慢就先跑一遍 stream看结果是否和初始记录一致。如果带宽明显下降可能是内存条坏了、降频了或者 CPU 的温度墙触发导致内存控制器降速了。在京东上的内存条好几百一根但内存问题导致的生产故障损失是以小时以几十万计的这个账一定要算清楚。4. IO 到底怎么侵占 CPU 和 load 的4.1 从一次磁盘读看整个阻塞链一条 read() 系统调用从发出到完成全程是这样的应用进程发起 read进入内核态内核把请求交给文件系统和块设备层请求进入磁盘的等待队列进程被标记为 D 状态TASK_UNINTERRUPTIBLECPU 此时可以切换到其他进程但如果所有进程都在等 IOCPU 就进入了 idle 状态这个 idle 时长会被记录为 iowaitwa磁盘数据到了触发中断进程回到可运行状态重新排队等 CPU看到问题了吗在这个流程中拓扑上 CPU 是闲的但 load 里挂着那个 D 状态进程load 是高的。所以就会形成标题里说的经典幻觉CPU 使用率低load 却高得吓人。如果业务对读写时延敏感这个链路里每一个队列都是延迟放大器。从应用层看只是发起了一个 IO等待了 100ms但在系统内部这 100ms 可能经历了 5 个队列每个队列都堆积了几十个任务。4.2 iowait 不等于磁盘慢了也可能是 CPU 被中断打爆了iowait 高第一反应都是磁盘慢。但有一种特殊情况磁盘硬件很快但 CPU 被海量 IO 中断打满了。尤其是 NVMe SSD 时代单盘能跑到每秒百万级 IOPS如果中断处理不当CPU 会花大量时间处理中断。怎么区分这两种情况看 iostat -x 1 输出的 %util 列这是磁盘忙的比例。如果 %util 很高接近 100%但 svctm 很低、await 也不高说明磁盘本身处理能力没问题是 CPU 处理中断的能力到顶了。这时候的解决方案是调大中断合并coalescing或者用多队列 RPSReceive Packet Steering把中断分散到多个核。另一个极端的坑某些云厂商虚拟化环境里iowait 很高但用 iostat 看磁盘延迟完全正常。这大概率是宿主机上的邻居在抢 IO 资源你看到的 iowait 是被偷走的等待时间跟你自己的磁盘没半毛钱关系。注意判断 IO 问题务必同时看 CPU 的 wa、磁盘的 %util、await、以及 iostat 里的 aqu-sz平均队列长度。只看其中任何一个都可能得出完全错误的结论。4.3 典型 IO 瓶颈场景的现场还原场景一日志写入拖垮一切。Java 应用使用 log4j2 的异步日志但底层磁盘是整个系统里最慢的机械盘。高峰时每秒产生 20MB 日志磁盘顺序写能力只有 80MB/s。这台机器的现象就是CPU 使用率 5%load 飘到 10 以上应用接口超时率飙升。解决方案很简单换 SSD或者把日志目录挂到 tmpfs 里问题直接消失。场景二数据库刷脏页。MySQL 的 InnoDB 在内存里改了数据页后台线程负责把它们刷到磁盘。如果磁盘写入能力不够脏页比例会持续上升。当比例达到 75%用户查询也会被迫参与到刷盘里延迟从 1ms 飙到 500ms。top 看起来什么样CPU 不高但进程的 D 状态很明显load 一天比一天高。场景三容器存储的叠加层。容器里的写操作如果落在 overlayfs 的 upperdir所有读写都要经过宿主机上的存储驱动。常见的做法是把数据目录挂成 volume绕开容器层确实是踩过坑之后得出的教训。4.4 快速定位 IO 瓶颈的命令组合单靠 top 完全讲不清 IO 问题我的标准动作是四连招top 先看全局确认是否有异常vmstat 1 5 看 wa 和 b 列iostat -x 1 看具体磁盘的 %util、await、w_awaitpidstat -d 1 找到具体是哪个进程在产生 IOpidstat -d 输出示例Linux 4.18.0-305.el8.x86_64 10/24/2024 _x86_64_ (32 CPU) 02:43:12 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command 02:43:13 PM 1001 12345 0.00 20480.00 0.00 12 mysqld如果看到某个进程的 kB_wr/s 特别高iotop 再确认一下是不是它拖累了整个系统就知道该治谁了。5. 实战案例从top 看起来没事到真正定位瓶颈5.1 一次典型的 CPU 低、load 高排查实录有一回我们一个金融客户的生产环境报障交易接口响应时间从 50ms 涨到 3s。我登录上去第一件事就是 toptop - 14:22:01 up 120 days, 2:14, 3 users, load average: 22.50, 21.30, 19.87 Tasks: 320 total, 1 running, 318 sleeping, 1 stopped, 0 zombie %Cpu(s): 8.2 us, 3.1 sy, 0.0 ni, 75.4 id, 12.5 wa, 0.3 hi, 0.5 si, 0.0 stload 高到 22CPU 使用率却只有 11%而且 wa 达到 12.5%。看到这个组合基本可以断定不是 CPU 算力的问题。继续用 vmstat 1 3procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 2 18 0 589372 102400 8049800 0 0 1024 819200 0 0 8 3 68 21 0注意 b 列 18r 列只有 2。压垮 load 的完全是被 IO 阻塞住的 18 个进程。iostat -x 1 看了下数据盘%util 直接到了 99%await 从 5ms 涨到 980msaqu-sz 高达 40 多。磁盘请求队列已经堵成一锅粥。定位到具体进程用 pidstat -d发现是 MySQL 的 redo log 刷盘和 binlog 同步在争抢磁盘而磁盘的 IOPS 能力已经到了物理极限。最终方案是把 redo log 和 binlog 分开到两个不同的物理 SSD再把 innodb_io_capacity 参数从 200 调到跟硬件匹配的 2000。处理完再看load 降到 3 左右接口延迟恢复到 40ms。5.2 反向案例CPU 使用率 90%但业务延迟依然稳定另外一个案例是反向的。一台 64 核的推荐算法服务器top 显示 CPU 使用率长期在 90% 以上。按照一般的思路CPU 都快被打满了应该加机器了吧我看了下 qps 曲线和延迟曲线发现虽然 CPU 高但 p99 延迟非常稳定说明这台机器实际上还在能力范围内。进一步用 perf top 看了下 CPU 热点发现耗时大部分集中在矩阵乘法库上都是高效的 SIMD 指令没有浪费。这时候 CPU 使用率高恰恰说明资源被用在了刀刃上。加不加机器判断依据不应该是 CPU 百分比而应该是业务的性能指标是否满足 SLO。CPU 高不是问题CPU 高但延迟抖动、排队堆积才是问题。5.3 一套适合自己的日常巡检命令组合上面是线上出问题的排查思路但要避免事故发生日常巡检更重要。我自己惯用的是一套定时任务组合每 1 分钟采集一次 sar -u、sar -r、sar -d保留 30 天每 10 秒用 atop 记录一次全量资源快照保留 7 天每周跑一遍 stream 内存带宽测试对比基线每月用 pidstat 拉一次 TOP10 进程的历史系统资源消耗观察业务变化趋势这套东西你可以在每台机器上配置也可以接入 Prometheus 那套体系。关键是坚持记录因为性能问题大多是慢慢恶化的没有基线数据等到量变引发质变的时候你都说不清是哪天开始变慢的。5.4 常见误导场景速查表现象容易得出的错误结论实际可能原因正确排查命令CPU 0%load 20机器没事D 状态进程在等 IOvmstat 的 b 列、ps 看 D 状态CPU 90%应用慢CPU 不够用内存带宽打满/缓存 miss 率太高perf stat 的 cache-misses、streamCPU 30%wa 30%磁盘慢磁盘不慢CPU 中断处理不过来iostat 的 %util 和 svctm 对比单核 100%整体 20%整体没有瓶颈单线程热点top 按 1 键看每核pidstat -t 查线程load 15核数 32完全没压力队列分配不均部分核过于繁忙mpstat -P ALL 1 看单核分布6. 工具链全景从 top 出发构建自己的排查体系6.1 各层级工具的定位和选择性能排查工具大体分三层全局视角层top、vmstat、sar、atop。看的是系统整体资源状态进程视角层pidstat、htop、iotop。定位到具体进程内核/指令视角层perf、bpftrace。深到 CPU 指令级、内核函数级的分析我见过不少人排查性能问题只会用 top碰到怪问题就发愁。我的建议是top 之下的每一个命令都值得花一个下午去深入练习比如 vmstat 的每一列iostat 的每一列都是内核某个子系统的统计输出弄懂它们背后的原理比记住命令本身重要得多。6.2 动态追踪工具给传统排查带来极大便利看 perf 或者 bpftrace 的输出有时候会劝退新手但这类工具才是性能排查的大杀器。举个例子前几年我们用 bpf 的 offcputime 工具统计进程在等待 IO 时究竟卡在哪一棵内核调用树上一眼就看到 NFS 客户端的 rpc_wait 占了 60% 的等待时间。如果是用传统工具一层层查可能要好几天。不过动态追踪工具是高手向的玩法新手建议先把 vmstat、iostat、pidstat、sar 这几个基本功练扎实形成从全局到局部的排查思维再上手 bpf 工具效率会高很多。6.3 建立自己的性能基线库最后想重点聊聊基线这件事。很多人排查问题的时候最大的困难不是找不到工具而是不知道正常值应该是多少。16 核的机器 load 到 8 正常不正常你的数据库磁盘 iops 消耗到 5000 是不是上限这些问题如果心里没有谱排查起来就是大海捞针。我的做法是每台服务器上线后在业务低峰期用 sysbench、fio、stream 分别跑一轮 CPU、磁盘、内存的基准测试记录到台账里之后只需对比和基线的偏离程度性能恶化的苗头就能第一时间发现。这套方法你任何时候任何环境都可以用它才是真正能让你不再被 top 骗的根本手段。踩过的坑多了之后我最大的感触是top 只是个入口它负责把你引到问题现场至于真凶是谁还是要靠对整个系统运行机制的透彻理解以及一套顺手的工具组合去深挖。希望这篇分享能给你提供一条清晰的排查路径别再让 top 上的数字带偏方向。