服务器突然变慢:一次 Linux 性能排查的复盘

应用服务器忽快忽慢,登上去东敲一个命令西敲一个命令,看了半天理不出头绪。事后痛定思痛,把 Linux 性能排查梳理成有顺序的"四板斧":top 看 load 与 CPU 总览定方向、区分 us/sy/wa 三种忙、free 看 available 别被 cache 骗、iostat 揪磁盘 IO、jstack/strace 钻进程内部、sar 复盘历史。

2024 年我们一台应用服务器出过一次很典型的"变慢"故障。运营反馈说系统卡,接口忽快忽慢,但又没到完全不可用的程度。我登上去想看看怎么回事,却一下子有点发懵——服务器变慢的原因太多了:CPU 可能被烧满,内存可能不够,磁盘 IO 可能堵了,网络可能出问题。到底是哪一个?当时我对着终端,东敲一个命令、西敲一个命令,看了半天也没理出头绪。事后我痛定思痛,把 Linux 服务器性能排查这件事认认真真梳理成了一套有顺序、有章法的方法论。本文复盘这次实战,把这套"四板斧"讲清楚。

问题背景

环境:CentOS 7,Java 应用服务器
事故现象:
- 系统卡顿,接口响应忽快忽慢
- 没有完全宕机,但体验明显劣化
- 持续了一两个小时,时好时坏

现场排查(一开始的混乱):
# 我当时是这样东一榔头西一棒子的:
$ top          # 看了一眼,CPU 好像有点高?
$ free -h      # 内存好像也不太够?
$ ps aux       # 进程一大堆,看不出名堂
# —— 没有顺序,没有判断标准,看了等于没看

复盘后定下的排查顺序:
1. top / uptime  —— 先看 load 和 CPU 总体,定方向
2. CPU 细分      —— us / sy / wa,区分三种"忙"
3. 内存          —— free,但别被 buff/cache 骗了
4. 磁盘 IO       —— iostat,看是不是 IO 在拖
5. 锁定进程后    —— 再往进程内部钻
6. sar           —— 看历史数据,事后也能复盘

根因(后来定位到的):
这次是一个定时任务在跑大批量数据导出,
既吃 CPU 又打满磁盘 IO,和在线请求抢资源。

修复 1:top 先看清——load 和 CPU 总体

# === 排查永远从 top 开始,它是性能的"总览仪表盘" ===
$ top
top - 15:42:01 up 30 days,  load average: 18.7, 16.2, 9.4
Tasks: 210 total,   3 running, 207 sleeping
%Cpu(s): 35.2 us, 12.1 sy,  0.0 ni, 8.3 id, 44.1 wa, ...
MiB Mem : 16000 total, 800 free, 9000 used, 6200 buff/cache

# === 先看 load average:三个数,过去 1/5/15 分钟 ===
# load 大致表示"正在运行 + 正在等待资源的任务数"。
# 怎么判断它高不高?要除以 CPU 核数:
$ nproc
8
# load 18.7 / 8 核 ≈ 2.3 —— 平均每个核背着 2.3 个任务,
# 明显过载了。
# 经验:load / 核数  ≈ 1 算满,持续 > 1 就是过载。

# === 看三个数的趋势 ===
# load average: 18.7, 16.2, 9.4
# 1 分钟(18.7) > 15 分钟(9.4) -> 负载正在【上升】
# 反过来,如果 1 分钟的数最小,说明高峰已经过去。

# === 再看 %Cpu(s) 那一行,它告诉你 CPU 在忙什么 ===
# 这一行是后面所有判断的分水岭,下一节专门讲。

# === top 里几个实用交互键 ===
# P  按 CPU 使用率排序(默认)
# M  按内存使用率排序
# 1  展开显示每个 CPU 核心,看负载是否均衡
# 数字键能看出:是所有核都忙,还是只有一两个核被打满
# (单核打满往往是单线程的程序)

修复 2:CPU 高,要区分三种完全不同的"忙"

=== %Cpu(s) 那一行,每个字段的含义 ===
us (user)    : 用户态 CPU —— 跑你的应用代码、业务逻辑
sy (system)  : 内核态 CPU —— 系统调用、内核在干活
ni (nice)    : 跑被调过优先级的进程
id (idle)    : 空闲
wa (iowait)  : CPU 空着,在【等磁盘/网络 IO】完成
hi/si        : 处理硬中断 / 软中断
st (steal)   : 被同物理机上的其它虚拟机抢走的时间

=== 关键:us 高、sy 高、wa 高,是三个不同的病 ===

【us 高】—— 应用自己在烧 CPU
含义:你的程序在大量做计算。
可能:死循环、复杂运算、频繁 GC、正则回溯、
     序列化/反序列化太重。
方向:往【应用进程内部】查,看是哪个线程在烧。

【sy 高】—— 内核在烧 CPU
含义:系统调用太频繁。
可能:频繁创建销毁进程/线程、海量小 IO 系统调用、
     频繁的上下文切换、网络包处理过多。
方向:看上下文切换(vmstat 的 cs)、系统调用。

【wa 高】—— CPU 在干等 IO
含义:CPU 本身不忙,它在等磁盘/网络把数据给它。
     ★ 我们这次的现场:wa 高达 44%
可能:磁盘 IO 是瓶颈,大量读写把磁盘堵住了。
方向:不要再盯 CPU 了,转去查【磁盘 IO】。

=== 一个最常见的误判 ===
看到 top 里 CPU 占用高,就以为是"CPU 不够、要加核"。
但如果高的是 wa,加 CPU 核心一点用都没有 ——
病根在磁盘,CPU 只是陪着干等。
先分清 us / sy / wa,再决定往哪个方向查,
这是整个性能排查里最重要的一次分流。
# === vmstat:比 top 更适合看 CPU/上下文切换的动态 ===
$ vmstat 2 5      # 每 2 秒采一次,共 5 次
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 5  3      0 820000  10000 6200000   0    0  8200  6400 5200 9800 35 12  9 44  0
# r:正在运行/等 CPU 的进程数。r 持续 > 核数 = CPU 不够
# b:处于不可中断睡眠(通常在等 IO)的进程数。b 高 = IO 堵
# cs:每秒上下文切换次数。cs 异常高 = 线程太多/竞争激烈
# wa:和 top 一致,这里 44 —— IO 等待严重
# si/so:swap 换入换出。非 0 说明内存不够、在用交换分区

修复 3:内存——别被 free 的 buff/cache 骗了

# === 看内存用 free ===
$ free -h
              total        used        free      shared  buff/cache   available
Mem:           15Gi       8.8Gi       0.8Gi       0.2Gi       5.9Gi       5.5Gi
Swap:         2.0Gi       0.3Gi       1.7Gi

# === 新手最大的误区:看到 free 只剩 0.8Gi 就慌 ===
# "内存只剩 800M 了,要爆了!" —— 这是错的。

# === buff/cache 是什么 ===
# Linux 有个理念:空闲的内存就是浪费的内存。
# 所以它会主动拿暂时没用的内存去缓存磁盘文件
# (buff/cache),让下次读文件更快。
# 这部分内存【随时可以还回来】—— 一旦程序要用,
# 系统立刻把 cache 释放掉给程序。

# === 真正该看的是 available,不是 free ===
# available:估算"现在还能给新程序用多少内存",
# 它已经把"可回收的 cache"算进去了。
# 上面 available 是 5.5Gi —— 内存其实还宽裕,
# 别被 free 的 0.8Gi 吓到。
# 口诀:free 看 available,不要看 free 那一列。

# === 什么时候内存才是真的紧张 ===
# 1. available 很低(真的没多少可用了)
# 2. swap 的 used 在持续增长 —— 内存不够,
#    系统被迫把内存数据往磁盘的交换分区挪
# 3. vmstat 里 si/so 持续非 0 —— 正在频繁换页,
#    这个非常伤性能,磁盘速度比内存慢几个数量级

# === 谁吃了内存:按内存排序看进程 ===
$ ps aux --sort=-%mem | head -6
# 或在 top 里按 M 键

# === 看某个进程内存的细节 ===
$ cat /proc//status | grep -E 'VmRSS|VmSwap'
# VmRSS:进程实际占用的物理内存(常驻内存)

修复 4:磁盘 IO——iostat 揪出真凶

# === 上一步 wa 高,现在转战磁盘 IO ===
# iostat 来自 sysstat 包(yum install sysstat)
$ iostat -x 2 3      # -x 显示扩展信息,每 2 秒一次
Device  r/s   w/s   rkB/s    wkB/s  await  r_await w_await  %util
vda    120   480   8400    61000   85.3    12.1    103.5    99.2

# === 怎么读这几个关键列 ===
# r/s, w/s    : 每秒读 / 写次数(IOPS)
# rkB/s,wkB/s : 每秒读 / 写的数据量(吞吐)
# await       : 平均每个 IO 请求的耗时(毫秒)——
#               这个数高,说明磁盘响应慢、在排队
# %util       : 磁盘繁忙度。★ 最直观的一个指标
#               接近 100% = 磁盘几乎一刻不停在干活,
#               已经是瓶颈了

# === 我们这次的现场 ===
# %util 99.2% —— 磁盘被打满了
# w/s 480、wkB/s 61MB/s —— 大量写入
# await 85ms —— IO 请求平均要等 85 毫秒,非常慢
# 结论:磁盘 IO 就是这次卡顿的瓶颈。

# === 下一个问题:是哪个进程在疯狂读写磁盘 ===
$ iotop -o
#  -o 只显示真正在产生 IO 的进程
   PID  USER    DISK READ  DISK WRITE  COMMAND
 23891  app        0.00 B/s   58.20 M/s  java -jar export-job.jar
# 真凶现身:一个数据导出任务,每秒往磁盘写 58M

# === 也可以看单个进程累计的 IO ===
$ cat /proc/23891/io
read_bytes:  120000000
write_bytes: 9800000000
# 这个进程已经写了 9.8G

修复 5:锁定进程后,再往进程内部钻

# === 前四步把范围收敛到了"某个进程",现在往里钻 ===
# 我们这次是个 Java 进程,以 Java 为例。

# === 第一步:找出进程内"最烧 CPU 的线程" ===
$ top -Hp 23891       # -H 显示线程,-p 指定进程
# 找到 %CPU 最高的那个线程的 PID,假设是 24015

# === 第二步:把线程 PID 转成 16 进制 ===
$ printf '%x\n' 24015
5dcf

# === 第三步:打印 Java 线程栈,定位到具体代码 ===
$ jstack 23891 | grep -A 30 '0x5dcf'
"export-worker-3" #45 prio=5 ... nid=0x5dcf runnable
   java.lang.Thread.State: RUNNABLE
     at com.app.export.ExportJob.writeRows(ExportJob.java:128)
     ...
# 一路追到 ExportJob.java:128 —— 就是它在猛写

# === 通用手段(不限于 Java)===
# 看进程在做什么系统调用:
$ strace -p 23891 -c -f       # -c 统计,-f 跟子线程
# 它会统计这个进程在调哪些系统调用、各花多少时间。
# 比如发现大量 write 调用,印证它在疯狂写磁盘。

# 看进程打开了哪些文件:
$ ls -l /proc/23891/fd
# 能看到它正在写哪个文件

# === 这次的处理 ===
# 确认是凌晨数据导出任务,被误配成了白天高峰跑。
# 处理:① 立刻把这个任务降优先级,别和在线抢
$ ionice -c 3 -p 23891    # 把它的 IO 优先级设为 idle
$ renice +19 -p 23891     # 把它的 CPU 优先级调到最低
#       ② 改调度,挪到凌晨低峰执行
#       ③ 长远:导出任务和在线业务做资源隔离

修复 6:sar——没在现场,也能复盘历史

# === 一个尴尬:故障往往是"过去时" ===
# 运营说"刚才卡了一下",等你登上去,top 一切正常。
# 现场没了,top/iostat 这些"实时"工具就没用了。
# 这时需要能看【历史】的工具 —— sar。

# === sar 会自动、定期采集系统指标并存档 ===
# 它同样来自 sysstat 包,装好后默认每 10 分钟
# 采一次,数据存在 /var/log/sa/ 下。

# === 看某天的历史 CPU 使用情况 ===
$ sar -u -f /var/log/sa/sa15      # sa15 = 当月 15 号
12:00:01  CPU  %user  %system  %iowait  %idle
14:30:01  all  35.10    12.04    44.21    8.65
14:40:01  all  38.22    11.80    45.10    4.88
# 一眼看到 14:30 那会儿 iowait 飙到了 44% ——
# 哪怕事故已经过去,也能精确还原当时的状况

# === sar 能看的历史指标很全 ===
$ sar -u        # CPU(今天的)
$ sar -r        # 内存
$ sar -b        # 磁盘 IO 总览
$ sar -d        # 每块磁盘的 IO
$ sar -n DEV    # 网络流量
$ sar -q        # load average 和运行队列
$ sar -W        # swap 换页
# 加 -f /var/log/sa/saXX 就是看历史某天

# === 经验 ===
# 实时排查靠 top/vmstat/iostat,
# 事后复盘靠 sar。
# 一台生产服务器,sysstat(提供 sar)应该是标配,
# 装好、确认它的定时采集在正常跑。
# 否则故障一过,你就永远失去了现场。

命令速查

排查阶段          命令                  看什么
=============================================================
总览              top / uptime          load average、%Cpu 那一行
CPU 动态          vmstat 2              r、b、cs、us/sy/wa
区分忙的类型      top 的 %Cpu(s)        us 高/sy 高/wa 高,三个方向
内存              free -h               看 available,不是 free
内存换页          vmstat 的 si/so       非 0 = 内存不够在用 swap
磁盘 IO           iostat -x 2           %util、await
哪个进程在 IO     iotop -o              DISK READ/WRITE
进程内热点线程    top -Hp          找最烧 CPU 的线程
Java 线程栈       jstack           定位到代码行
系统调用          strace -p  -c    进程在调什么系统调用
历史复盘          sar -u/-r/-d -f saXX  事后还原当时状况

口诀:top 定方向 -> us/sy/wa 分流 -> 锁进程 -> 钻内部 -> sar 复盘

避坑清单

  1. 性能排查要有固定顺序:top 定方向,再按 CPU/内存/IO 分头深入,别东一榔头西一棒子
  2. load average 要除以 CPU 核数来判断,看三个数的趋势能知道负载在升还是在降
  3. CPU 高必须先分清 us/sy/wa,这三个是完全不同的病,决定了往哪个方向查
  4. wa 高是 CPU 在等 IO,此时加 CPU 核心毫无用处,病根在磁盘,要转查 IO
  5. free 看内存别看 free 那一列,buff/cache 可随时回收,真正该看的是 available
  6. swap 的 used 持续增长、vmstat 的 si/so 非 0,才是内存真的紧张的信号
  7. iostat 的 %util 接近 100% 说明磁盘被打满,await 高说明 IO 请求在排队
  8. 用 iotop -o 找出真正在产生磁盘 IO 的进程,别只凭猜测
  9. 锁定进程后用 top -Hp 找热点线程,Java 配合 jstack,通用场景用 strace
  10. 生产服务器必装 sysstat,sar 能看历史数据,故障过去了也能复盘当时的现场

总结

这次服务器变慢的排查,真正给我上了一课的,不是某一个具体的命令,而是"排查要有章法"这件事本身。我清楚地记得故障刚发生时我的样子:登上服务器,先敲个 top 瞄一眼,觉得 CPU 好像有点高;又敲个 free,觉得内存好像也有点紧;再敲个 ps,面对一屏幕的进程不知道该看哪个。我看了一大堆命令的输出,却始终没能形成任何一个判断,因为我心里根本没有一套顺序,也没有一套标准——我只是在漫无目的地"看",而不是在"排查"。事后我把这件事彻底想清楚了:服务器性能排查,本质上是一个不断收敛范围的过程,你必须先有一个总览,从总览里分流出方向,顺着方向锁定到具体的进程,最后再钻进进程内部找到那一行代码。这个过程必须是有顺序的,跳过任何一步,或者顺序乱了,你都会迷失在海量的指标里。这套方法论的第一步,永远是 top,它是整台服务器的总览仪表盘。在 top 里,我最先要看的是 load average 那三个数字,但看它有个前提——必须拿它去除以 CPU 的核数,一个 load 18 在八核机器上是严重过载,在三十二核机器上却很轻松;同时这三个数字分别代表过去一分钟、五分钟、十五分钟,把它们放在一起看趋势,就能知道负载到底是正在往上冲,还是高峰已经过去。但 top 里真正决定整个排查走向的,是 %Cpu 那一行,它告诉你 CPU 究竟在忙些什么——而这正是我过去最大的盲区。我以前看到 CPU 占用高,脑子里唯一的反应就是"CPU 不够用了,得加核",可这次我才真正理解,CPU 的"忙",其实是三种完全不同的病:us 高,是你自己的应用代码在疯狂做计算;sy 高,是内核在忙,通常意味着系统调用太频繁;而 wa 高,意思是 CPU 其实一点都不忙,它只是空空地坐在那里,等着磁盘或网络把数据喂给它。这三种病的查法南辕北辙,而我们这次的现场,wa 高达百分之四十四——如果我当时顺着"CPU 高就加核"的直觉去扩容,加再多的 CPU 核心也是徒劳,因为那些核心只会跟着一起干等,病根压根不在 CPU,而在磁盘。所以 us、sy、wa 的这一次分流,是整个排查里最关键、也最省功夫的一个动作:它一步就把你从"CPU 方向"切换到了正确的"磁盘 IO 方向"。顺着 wa 高这条线索转去查磁盘,iostat 里有一个指标特别直观,就是 %util,它代表磁盘的繁忙度,当它贴近百分之百,就说明这块磁盘已经一刻不停在连轴转了,它就是瓶颈;再用 iotop 一看,真凶立刻现形——是一个数据导出任务,每秒往磁盘灌进去几十兆。范围收敛到这个进程之后,再往里钻就水到渠成了,对 Java 进程可以用 top -Hp 找出最烧的线程、再用 jstack 把它精确定位到某一行代码,对任何进程都可以用 strace 去看它到底在反复调用哪些系统调用。最后我还想特别说一下 sar,因为它解决的是性能排查里一个特别让人无奈的尴尬:故障常常是"过去时"。运营跑来说"刚才系统卡了一下",可等你手忙脚乱登上服务器,top 显示一切风平浪静,现场已经没了——top、iostat、vmstat 这些工具全都只能看"此刻",现场一旦消失,它们就全部失效。而 sar 是那个能让你回到过去的工具,只要服务器装了 sysstat,它就会默默地、每隔几分钟采集一次系统的各项指标并存档,哪怕事故已经过去几个小时,你依然能把当时那一刻的 CPU、内存、IO 状况精确地调出来还原。所以我现在把 sysstat 当成每一台生产服务器的标配,装好它、并且确认它的定时采集确实在跑,因为它保存的,是你未来某次深夜排障时唯一能抓住的那个"现场"。这次排查从最初的手忙脚乱,到最后整理出 top 定方向、us/sy/wa 分流、iostat 锁瓶颈、jstack 钻代码、sar 留复盘这样一条清晰的链路,我最大的收获就是明白了:面对一台变慢的服务器,真正能救你的,从来不是你记住了多少个命令,而是你心里有没有那张"先看什么、再看什么、看到这个结果该往哪走"的地图。命令谁都能查到,而那张地图,得靠一次次这样的复盘,一笔一笔自己画出来。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
Linux教程

服务器磁盘突然爆满:一次 Linux 磁盘空间排查的复盘

2026-5-20 17:12:36

Linux教程

接口偶发超时,竟是 TIME_WAIT 堆积:一次 Linux 网络排查的复盘

2026-5-20 17:20:35

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索