
接到告警说服务器负载高登录上去先跑哪个命令很多人的第一反应是top看一眼 us 高不高、wa 高不高然后……就没有然后了。真正的问题是如果 us 高下一步该看什么如果 wa 高你怎么知道是哪个进程在折磨磁盘如果内存明明还有几个 G 的空闲业务却像被掏空了一样卡顿这又是怎么回事作为一个常年跟 Linux 服务器打交道的运维我太清楚这种“命令背了不少、一上机器就抓瞎”的状态了。原因很简单排查性能问题缺的不是某个神奇命令而是一条完整的分析链路。CPU、内存、带宽这些指标不是孤立数据它们是同一台机器上的共享资源任何一个出现瓶颈都会让业务表现出现类似的“卡、慢、超时”但方向选错了后面全白做。这篇文章不打算写成命令手册的搬运工我按实际排查的顺序把 CPU、内存、带宽这几个维度的核心命令、每个命令输出里最值得盯的指标、以及命令背后的判断逻辑拆开讲清楚。内容会更适合那些已经能熟练操作 Linux、但遇到线上故障时总感觉缺少一套完整思路的人。1. 排查前的第一步先分清方向而不是先跑命令很多新手甚至不少老手犯的第一个错误是拿到服务器就迫不及待地敲命令。这就像病人还没挂号你就开始给他开药。真正高效的排查第一步不是跑命令而是先做信息收集这个告警是怎么来的是 CPU 使用率超过 90% 的主动告警还是业务方反馈接口超时的被动告警是同机房所有机器一起报警还是只有这一台这两类来源决定了完全不同的排查起点。如果是监控系统主动告警那么问题大概率就出在告警指标本身顺着指标往下查就行。但如果是业务方反馈慢那就要警惕了用户感觉到的慢不一定是资源不足还可能是应用代码里有慢查询、有锁竞争、有垃圾回收停顿甚至可能是某个上游接口把调用线程池占满了。1.1 先看 Load Average但这玩意容易骗人uptime命令给出的 load average是我建议的默认起点。它包含三个数字分别是过去 1 分钟、5 分钟、15 分钟的平均负载。很多文章告诉你“负载不能超过 CPU 核数”这话对了一半因为它没有区分负载的构成。负载的本质是“处于可运行状态和不可中断睡眠状态的进程数”的统计平均值。可运行状态是指正在用 CPU 或排队等 CPU 的进程不可中断睡眠状态通常是在等磁盘 I/O 的进程。也就是说如果你看到负载很高不能直接断定是 CPU 不够也可能是磁盘 I/O 阻塞了太多进程。有一个比较实用的判断方法把 load average 和 CPU 核数对比同时叠加观察趋势。如果 1 分钟负载远高于 15 分钟负载说明是近期突然飙高大概率是业务流量突增或某种异常任务。如果三个数字都很高且一直居高不下那就是持续性的资源瓶颈。但这只是方向具体是 CPU 还是 I/O还需要下一步拆解。1.2 三层过滤从系统全局到进程再到线程我自己的排查习惯是遵循一个“系统级 → 进程级 → 线程级”的三层过滤思路。系统级看整体资源态势进程级锁定“谁在制造问题”线程级才能定位到具体的代码执行点。比如 CPU 高系统级用mpstat看是不是某颗 CPU 被打满进程级用top排序找出那个进程线程级用top -H找到具体线程号再结合 Java 的jstack或 C/C 的调试器去看代码栈。这套思路同样适用于内存和带宽排查。内存系统级用free看剩余量和 cache进程级用ps按内存排序再深入就要看进程内部的内存映射或者堆内存使用率。带宽也是系统级用iftop或nload看总流量进程级用nethogs如果没有装上可以结合ss看连接和进程要再往深走就是抓包看具体连接的流量特征。这个三层过滤的意义在于它让你每一步行动都有依据而不是被一堆命令的输出带着跑。2. CPU 排查top 只是入口别在入口处迷路top是 CPU 排查的经典入口但它的输出信息密度太高新手很容易被各种行和列带偏。我建议先只盯前几行里的关键指标确认方向之后再往下挖。2.1 top 第一行和第二行的正确读法先说第一行 load average前面已经聊过了它告诉你的是整体负载趋势而不是当前 CPU 用量。第二行是进程和 CPU 状态统计真正有价值的是这行里的%Cpu(s)前面的状态拆解行比如 us、sy、ni、id、wa、hi、si、st。这里重点看四个值us用户态 CPU 占比。如果 us 持续超过 80%说明是应用本身在大量消耗 CPU这时要优先怀疑代码逻辑、死循环、复杂计算或者 GC 频繁。sy内核态 CPU 占比。sy 超过 20% 就需要警惕常见原因是系统调用过多、上下文切换频繁比如高并发的短连接请求会大量触发系统调用和软中断。wa等待 I/O 的 CPU 占比。如果 wa 高但 us 不高说明 CPU 大部分时间在等磁盘问题方向应该在存储而不是计算。st被虚拟化环境偷走的 CPU 占比云服务器上要额外注意如果 st 长期偏高那大概率是宿主机上的邻居在争抢资源这种时候单纯优化应用已经没什么用了。2.2 从进程定位到线程换一种看待 top 的方式找到吃 CPU 的进程后下一步是确定是哪个线程在作怪。这里用的还是top但要加上-H参数并且把显示方式切到按 CPU 排序。在交互界面里按P键或者直接执行top -Hp 进程PID就能看到该进程下所有线程的 CPU 占用情况。拿到占用最高的线程 ID 后这个数字是十进制的而在 Java 线程转储文件jstack 输出中线程号通常是十六进制。这就要求你做一个进制转换printf %x\n 线程ID。转换出来的十六进制值去 jstack 输出里搜就能精准定位到出问题的代码栈。这个方法同样适用于 Native 应用。对于 C/C 程序拿到高 CPU 线程号后可以附加 gdb 或者使用perf top -p 进程PID -t 线程ID来看热点。很多人会卡在“知道进程高但不知道哪行代码高”这一步其实只是缺了进制转换这个细节。2.3 别忽略短时间内尖刺vmstat 配合使用top看的是瞬时状态如果问题不是持续性的而是每隔几十秒才出现一次你在top里可能根本看不到什么。这时要引入vmstat它能在后台定期采样记录系统状态的连续变化。vmstat 1 10的意思是一秒采样一次一共采样 10 次。重点看第三行的 r运行队列和第四行的 us、sy、wa 列。采样多次后如果发现 r 值经常超过 CPU 核数那说明 CPU 资源已经处于饱和排队状态。如果 wa 列时不时冲到很高说明 I/O 尖刺是间歇性的这种场景下top的瞬时快照当然捕捉不到。我之前遇到过一台机器业务方反馈每隔 5 分钟卡一下每次卡 30 秒左右。直接top看 CPU 一切正常后来用vmstat 1连续记录发现 wa 列周期性飙到 90% 以上顺着往下查才知道是 cron 定时任务在每 5 分钟触发一次全量日志压缩把磁盘 I/O 打满了。2.4 核心场景拆解us 高、sy 高、wa 高分别怎么查CPU 排查容易卡住很多时候是不清楚不同状态高代表什么方向。这里整理一个对照表是我自己排查时习惯用来快速对方向的。现象初步判断下一步操作us 高用户态 CPU 持续饱和应用代码消耗大定位线程栈检查是否有死循环、大计算、GC 或锁竞争sy 高内核态 CPU 使用偏高系统调用频繁、上下文切换多用vmstat看 cs上下文切换列必要时用perf分析内核热点wa 高CPU 大量等待 I/O磁盘读写是瓶颈iostat -x 1看 %util 和 await定位热盘和热点分区st 高虚拟化 CPU 被抢占宿主机资源争抢换云厂商物理机或调整实例规格这属于资源层问题hi/si 高硬/软中断频繁网络包或磁盘中断过密结合cat /proc/interrupts看中断分布可能网卡队列不均这张表的价值在于帮你快速缩小范围但别把它当绝对标准。比如 sy 高有时也伴随 us 高这时优先看线程栈因为大多数系统调用都是由用户态代码直接或间接触发的。3. 内存排查free 只是开胃菜重点在于理解内存到底去哪了内存问题比 CPU 问题更隐蔽因为 Linux 的内存管理机制里有个容易让人误解的输出明明程序很吃内存但free一看还剩好几个 G这就让人松懈了。实际上 Linux 把空闲内存拿去做 cache 是正常现象关键要看的是“可回收”和“不可回收”的构成以及具体进程的占用趋势。3.1 读懂 free -h 的三行输出free -h的输出分三行可以用一两个词概括物理内存总量、已用、空闲、shared、buff/cache、available。这里最容易混淆的是 used 这一列它没有包括 buff/cache而 buff/cache 里的绝大部分是可以随时释放的。真正需要关心的是 available它才是估算“还能分配多少内存给新程序”的可靠指标。如果你看到 available 很低而 buff/cache 很高先不要急着怀疑缓存进程。这时候可以查看哪些进程占用内存最多ps aux --sort-rss | head -n 20。按 RSS常驻内存排序能快速列出物理内存消耗最大的进程。但这里有个细节很多人会踩坑RSS 会把多个进程共享的共享库计算多次导致个别进程的 RSS 看似异常高。要更精确地评估单进程内存建议去看/proc/PID/smaps或者用smem工具它会在 RSS 基础上扣除共享库的重复计算给出更接近实际的 PSS 值。3.2 别小看了 Page Cache 的延迟问题很多线上故障的根子出在 Page Cache 上。Linux 为了提高文件读写性能会把读写过的磁盘页缓存在内存里。正常情况下当内存压力出现时内核会通过后台回收线程释放一部分缓存这个过程叫 kswapd 或直接回收。但在某些极端场景下比如一次性大面积写入大文件、或者某个程序突然读取了大量数据cache 会急速膨胀短时间内把可用内存压没。表现是什么业务进程启动不了提示 Out of Memory 错误但是free一看 buff/cache 巨高。很多人这时候就慌了想把 cache 手工清掉执行echo 3 /proc/sys/vm/drop_caches然后就发现业务照样起不来——因为 cache 虽然释放了但内存分配还要时间而且有些页是脏页必须先写回磁盘才能释放这个动作会导致磁盘 I/O 突发反而让系统更卡。正确的处理方式一是确认进程确实是因为内存不够起不来二是调整 vm 层参数比如vm.zone_reclaim_mode、vm.min_free_kbytes等或者通过 cgroup 限制其他进程的内存占用。手工 drop_caches 这个动作我建议只在低峰期应急使用不要当作常规操作。3.3 内存泄漏定位的一种实用套路内存泄漏的表现是随着时间推移进程的 RSS 或堆内存持续增长但重启后恢复。定位内存泄漏最直接的方式是监控进程的内存趋势而不是等到 OOM 杀进程之后才找根因。可以用/usr/bin/time -v 命令来查看进程峰值的 max resident 值但这只适用于短生命周期任务。对于长驻进程配合top -p PID定期记录 RSS或者借助监控系统画内存趋势图观察是线性上升还是阶梯式上升。如果是 Java 或 Go 服务还可以通过暴露的内存指标接口比如 Prometheus 的 jvm_memory_used_bytes来细分堆内、堆外、Metaspace 等区域。定位到具体泄漏点时Java 服务可以连续抓两份jmap -histo:live输出对比看哪些类实力数量在暴涨Native 进程可以用valgrind或 AddressSanitizer 重新编译运行。不过这些工具在线上环境往往不好直接启用更常见的做法是先用pmap -x 进程PID看虚拟内存段分布再结合代码审查定位。3.4 堆外内存与堆内内存Java 场景的一个特殊关注点热词里出现了“jvm内存模型”和“堆外内存”说明不少人在 Java 应用的内存排查上踩过坑。Java 进程的内存不只是-Xmx指定的堆内存还包括线程栈、Metaspace、DirectBuffer堆外内存、JIT 编译产物等。经常出现的一种诡异现象是堆内存使用率正常但整个进程的 RSS 很高甚至被内核 OOM。这种场景下要检查两个方向。一是 DirectBuffer它通过ByteBuffer.allocateDirect分配不受堆大小限制却算在进程 RSS 里。二是 JNI 调用的 Native 库比如常见的解压缩库、OpenSSL 等可能在 C 层分配了大量内存。排查手段上可以通过-XX:MaxDirectMemorySize限制 DirectBuffer 大小用jcmd 进程PID GC.heap_info看 JVM 的角度统计再配合pidstat -r或者查看/proc/PID/status里的VmRSS对比 JVM 报告的内容和操作系统看到的内存差额很可能就是堆外部分或 Native 内存。排查思路可以按下面这个流程走用ps aux或top确认进程 RSS 是不是真的异常。用jcmd或jstat确认堆内内存区域是否正常。如果堆内正常看 DirectBuffer 统计jcmd 进程PID VM.native_memory需要 JVM 启动参数开启 NMT。对比/proc/PID/status里的 VmRSS 和 JVM 统计差额大的话考虑 Native 库或线程栈消耗。4. 带宽排查不只盯着流量大小更要看连接质量说到带宽很多人第一反应是看“流量满没满”。但实际上网络性能问题大致可以分两种一种确实是流量达到物理带宽上限另一种是网络质量变差导致的重传、丢包、延迟增高。这两种问题的处理方法完全不同而排查入口也不太一样。4.1 先量化当前机器流量到底有多大最直观的工具是iftop它会像top一样实时刷新网络连接流量按流量大小排序。如果一时没有安装用nload也可以它能画两个大字符图形分别展示进方向和出方向的速率。不过我想特别提醒一点如果是云服务器iftop看到的是实例内部的虚拟网卡流量不一定等于云厂商计费的“带宽”。比如公网流量会不会经过 NAT 网络、内网流量是否被限速这些在虚拟化环境下视图可能有差异。真有疑问时建议先到云控制台看监控曲线那个数据往往更接近实际计费出口。单纯看流量大小是不够的还要对数量级有概念。假设一台 2 核 4G 的机器公网带宽规格是 5Mbps那它的上限就是每秒 640KB 左右。如果iftop显示瞬间流量到了 3Mbps它就明显是热点问题。但要是机器规格是 100Mbps你却只看到 10Mbps 流量在跑这时出现卡顿优先去查延迟和丢包而不是流量。4.2 延迟与丢包用 ping 粗略看用 MTR 精细看排查网络质量最粗糙的办法是ping -c 100 目标IP看丢包率。如果丢包率持续超过 1%那传输层就很容易出现重传TCP 吞吐量会断崖式下滑。但ping只是能测通它直接测的是 ICMP 协议实际应用走的是 TCP 或 UDP表现并不完全一致。更实用的工具是mtr它像是traceroute和ping的结合体能展示从本机到目标地址每一跳的丢包和延迟情况。mtr -r -c 100 目标IP会给出每一跳的统计。这里有个观察技巧如果丢包只出现在最后一跳目标服务器那一跳而中间路由器都正常那问题大概率在目标服务器的自身处理能力比如防火墙规则导致的丢包、目标服务器的 TCP 接收队列满等。如果丢包出现在中间某两个节点之间那就要考虑网络链路本身的问题。4.3 TCP 重传检查一个被很多人忽略的重要指标TCP 重传是网络质量问题的直接反映但在多数操作系统的默认监控里却容易被忽略。快速检查方式是ss -s它会输出 TCP 连接的统计信息里面包含当前的重传率估算。但更准确的是netstat -s它统计了从启动到当前时刻的累计重传次数。如果累计重传次数很高最好是先确认是不是某个具体的连接在频繁重传而其他连接都正常。ss -i可以查看每个 socket 的详细信息包含发送队列、接收队列、拥塞窗口等。如果要实时看重传事件的现场可以用tcpdump抓包抓取特定端口的流量检查是否有大量重复的 ACK 或超时重传。重传的根因通常分两种一种是网络链路丢包比如运营商线路不稳定、防火墙策略丢包另一种是缓冲区不足导致的丢包比如接收端应用程序处理不过来socket 接收缓冲区被填满内核只能丢包。这两种情况的处理方向南辕北辙前者需要找网络服务商或者优化路由后者需要优化应用处理性能甚至调整sysctl参数如net.core.rmem_max、net.ipv4.tcp_rmem。4.4 连接数与连接状态带宽限制之外的另一种“带宽”在带宽排查的链路中连接数这个维度经常被忽视。一台机器即使总带宽没有打满但 TIME_WAIT 或 SYN 队列堆积同样会让新连接进不来、业务仿佛被网络卡死了。ss -s能快速看到不同状态连接的数量ss -ant | awk {print $1} | sort | uniq -c这种命令则能按 TCP 状态做个快速分组。如果 TIME_WAIT 数量庞大通常说明大量短连接没有被有效复用后续可以考虑开启tcp_tw_reuse或者优化应用的长连接策略。如果 SYN_RECV 状态的连接一直很多说明半连接队列满了或 handshake 没完成配合查看/proc/net/netstat里的ListenOverflows基本就能判断是不是 SYN 泛洪被排队到了。我自己排查过一个典型的连接数问题一台机器的带宽并没有跑满但接口频繁出现超时。用ss -s一看TIME_WAIT 连接数超过两万。进一步抓包确认是业务请求里频繁创建了新的 TCP 短连接给 Redis而 Redis 本身也有连接数限制。最终方案改成了连接池复用问题立刻缓解。网络问题有时并不在链路上而在连接资源的管理上。5. 综合实战案例从 top 到定位根因的完整排查链路前面把 CPU、内存、带宽分别拆开了讲但在真实场景里它们往往是搅在一起的。我来分享一个比较有代表性的案例把前面说的思路串成一条完整链路。5.1 现象业务接口平均耗时从 50ms 涨到 800ms某天上午一个 Web 服务出现严重的响应变慢。我先是从监控上看到机器节点的 CPU 使用率没有明显飙升内存也足够但接口耗时呈线性爬升趋势。直觉告诉我问题可能不在计算资源上而是网络或锁之类的点需要登录服务器进一步确认。第一步登录后先跑uptime看负载负载 4.2但机器是 8 核的这个数值并不算高。接着跑mpstat -P ALL 1看单核分布发现各核心使用率很均匀都不超过 40%。这基本排除了 CPU 瓶颈。第二步跑free -h内存 available 还有 6G 多buff/cache 也不算反常内存方向暂时排除。第三步顺理成章怀疑网络层。使用ss -ant看到大量连接处于 ESTABLISHED 状态且重传率有点高。进一步iftop看流量发现出方向流量只有 2Mbps 左右远没有达到带宽上限。这就矛盾了——流量不大重传却不低。5.2 深入从重传找到真正的黑洞既然有重传我决定用tcpdump抓包定位是哪些连接在重传。tcpdump -i eth0 tcp port 8080 -w /tmp/tcp.cap抓了大约 30 秒的包然后用 Wireshark 打开通过统计菜单里的 TCP 分析看到大量重复 ACK 都指向同一个目标段 IP。顺着排查这个 IP 段的通信质量发现是跨机房的专线出现了间歇性丢包丢包率 3% 到 5%。这里有一个关键点TCP 丢包 3% 对用户感知而言可能已经非常明显了。因为它会导致拥塞窗口收缩、触发快速重传或超时重传应用层接口耗时就可能十倍甚至百倍增长。而流量本身没有打满是因为拥塞控制机制在丢包时主动降低了发送速度这是一个自我保护的假象。最终处理是和网络团队确认专线在那一时间段进行了路由割接割接过程中出现了不稳定。等网络操作完成、路由收敛稳定后重传率自然降下来业务耗时也恢复了。5.3 复盘为什么 CPU 和内存都没问题却如此卡顿复盘这个案例我最想强调的其实是排查顺序和思路的价值。如果当时我看到 CPU 不高、内存够用就得出“服务器没问题”的结论这个方向就完全错了。资源充裕与业务稳定之间不是简单等式。网络链路上的问题同样可以表现为外层业务的异常。另一个收获是重传率是一个比流量大小更有意义的信号。流量不高不代表网络就健康它可能只是演员在拥塞控制的操控下放慢了脚步。遇到类似“接口变慢但资源不紧张”的故障建议优先检查 TCP 重传和延迟抖动。做一次完整排查的动线如下面对异常先跑uptime和vmstat 1 5快速确认系统整体是否健康。再看 CPU 单核分布和内存可用量排除计算与存储的显著饱和。仍无头绪时趁早进入网络层先ss -s看连接统计再用iftop看实时流量最后用tcpdump抓包确认是否为链路问题。6. 长期监控与常用速查把事后排查变成事前感知上面讲的都是“已经出问题你得赶紧查”的应急场景。但更成熟的做法是让这些问题在还没有影响业务之前就被发现。这也是为什么我建议你在机器上提前布好监控同时建立几个固定脚本让你在需要时能一键收集关键指标。6.1 轻量监控方案不用重工具也能覆盖核心指标并不是所有团队都有条件上完整的 Prometheus Grafana很多小团队可能只有一台测试机和一台生产机。这种情况下我推荐用三个轻量手段搭起一个“极简监控网”。第一用crontab定期执行vmstat、free、ss -s的记录脚本把输出追加到/var/log/perf/目录下的文件里保留 30 天。一旦出问题翻历史文件就能看到时间点的资源快照。脚本很简单核心是加上date时间戳。第二用sar采集历史数据。安装sysstat后sar -u可以看历史 CPU、sar -r看历史内存、sar -n DEV看历史网络流量。前提是sysstat的后台采集服务已经启动。这套工具的优点是采样频率比手动命令更密集能让你判断问题是从什么时候开始的。第三业务访问日志里的耗时曲线往往是最早的告警信号。如果你的网关或应用框架里有请求耗时日志定期扫描 p95、p99 耗时一旦这两个值超过设定阈值就值得深入排查了。很多时候服务器资源还完全正常时业务耗时已经先一步开始涨。6.2 一页纸速查覆盖 CPU、内存、带宽的核心命令最后我把高频用到的命令整理成一个速查表方便你贴在笔记里或者在排查时直接翻开对照。场景命令重点关注总览负载uptimeload average 的 1/5/15 分钟值及趋势CPU 状态mpstat -P ALL 1us/sy/wa/st尤其单核是否有热点上下文切换vmstat 1r 和 cs 列cs 过高查锁与短线程定位线程top -Hp PID最高 CPU 的线程号十进制Java 线程转储jstack PID匹配线程号的十六进制值内存概览free -havailable 列而不是 used 列进程内存排序ps aux --sort-rssRSS 最高的进程精确进程内存smem -k -s rssPSS 值扣共享内存后更合理流量实时iftop/nload进/出方向速率哪个连接占流量高连接状态ss -antTIME_WAIT、SYN_RECV 数量TCP 统计netstat -s重传、乱序、丢包累计值全路径网络质量mtr -r -c 100 IP丢包率出现在哪一跳抓包分析tcpdump -i eth0 port 8080 -w /tmp/t.cap重复 ACK、重传包特征7. 最后分享几个调试小技巧排查做到最后我发现很多经验不是从书里学来的而是被线上事故教育出来的。说几个我自己的习惯可能对你也有用。第一线上操作前先留证据。哪怕你只是跑一下top最好也把输出存到文件里。很多问题带有瞬时性等你分析完再回看现场可能已经变了。存下来的快照能帮你回溯和复盘也能在汇报故障时给团队提供依据。第二尽量别在业务高峰期执行会引发系统级阻塞的调试操作。比如strace -p PID这种动态追踪工具它会让目标进程暂停在系统调用边界甚至把整个进程卡死。如果非要做先确认维护窗口或者改用perf这类对被观测程序影响更小的工具并且限制采样时间。第三定期给自己的服务器做“性能基线”记录。同一个接口在业务低峰期响应 30ms高峰期响应 300ms这些数字本身没有绝对意义但如果你有历史基线就能很快判断当前的 800ms 是不是异常偏离。很多问题都是相对变化而不是绝对数值。Linux 排查这条路没有终点因为生产环境永远在变旧的坑填完新的坑又会出现。但只要你掌握了“先定方向、再逐层拆解、最后精准定位”的思考方式即便遇到没见过的诡异现象也不会慌乱。希望这篇文章里那些从实战里摸出来的细节能让你在下次面对报警时少走几步弯路。