服务器运行缓慢如何定位瓶颈?系统资源与应用性能分析方法

"服务器有点卡"是运维收到的最模糊的一类反馈。它可能是 CPU 打满、可能是磁盘在排队、可能是数据库慢查询、也可能是云厂商的宿主机在抢你的 CPU 时间。不加区分地"加配置"是成本最高、命中率最低的做法。真正有效的是一套有顺序的资源排查法:先确定是哪种资源在饱和,再沿着调用链往下找到具体的进程和代码路径。本文给出可直接照做的方法与判断阈值。

一、先用 60 秒确定"是哪一类资源"

业界通行的做法是 USE 方法(Utilization 使用率、Saturation 饱和度、Errors 错误):对每一种资源,依次看这三个指标。落到 Linux 上,四大类资源的顺序为 CPU、内存、磁盘 I/O、网络。

资源使用率指标饱和度指标错误指标常用工具
CPUtop 的 us+sy+stload average / 运行队列 r无top、vmstat、pidstat、perf
内存已用内存 / 可用内存si/so 换页、PSI memoryOOM kill 计数free、vmstat、sar -B、PSI
磁盘iostat 的 %utilaqu-sz 队列长度、awaitI/O error(dmesg)iostat、iotop、pidstat -d
网络带宽占用 / 包速率重传率、丢包、队列溢出ip -s link 的 errors/dropssar -n DEV、nstat、ss、ethtool

1.1 第一条命令:uptime + vmstat

# 看负载与在线时长
uptime
cat /proc/loadavg
nproc                       # CPU 逻辑核数,是判断负载是否超标的基准

# 每 1 秒采样一次,共 5 次(第一行为开机以来的平均值,忽略)
vmstat 1 5

# 带时间戳和磁盘统计的版本
vmstat -t -w 1 5

vmstat 输出各列怎么读:

  • r:运行队列长度(等待 CPU 的进程数)。持续大于 CPU 逻辑核数时说明 CPU 已经饱和,这是判断 CPU 瓶颈最硬的指标,比看百分比更可靠。
  • b:处于不可中断睡眠(D 状态)的进程数。大于 0 且持续存在,多半是磁盘或网络存储卡住。
  • si / so:每秒换入/换出内存。只要持续非零,就说明已经在用磁盘当内存,性能必然断崖式下降。
  • us / sy / id / wa / st:用户态、内核态、空闲、等 I/O、被虚拟化层偷走的时间占比。
  • cs / in:每秒上下文切换与中断数。若 cs 高而 us 低,可能是线程过多或锁竞争。

负载(load average)的判断口径:把三个数值分别除以 CPU 逻辑核数。1 分钟值持续超过核数的 0.7 倍说明比较忙,超过 1 倍说明有排队。注意只看 1 分钟值容易被瞬时峰值误导,要看它是否持续。

1.2 关键分诊规则

现象组合结论后续动作
负载高 + us 高(>70%)CPU 计算密集,业务本身在消耗用 perf/pidstat -u 找热点进程与函数
负载高 + wa 高(>20%)磁盘 I/O 瓶颈转 iostat -x、iotop
负载高 + sy 高(>30%)+ cs 极高系统调用或上下文切换过多检查线程数、strace -c 统计系统调用
负载高 + id 也高存在 D 状态进程,常为网络存储/NFS/云盘异常ps -eo stat,pid,comm \| grep '^D'
负载不高 + st 持续 >5%云主机被宿主机抢占 CPU考虑换实例规格或迁移宿主机
负载正常但业务慢瓶颈不在本机资源查依赖的数据库、缓存、外部 API、DNS

st(steal time,被偷走的时间)是云环境下最容易被忽略的一项。它在物理机上恒为 0;在 VPS 或云主机上持续高于 5%~10%,说明宿主机上的其他租户在抢占你的 CPU 配额,此时无论你怎么优化应用都没用,只能换宿主机或升规格。

二、CPU:找到具体是哪个进程、哪段代码

# 按进程看 CPU 占用,1 秒刷新
top
# 或更直观的非交互式快照
ps -eo pcpu,pid,user,comm,args --sort=-pcpu | head -15

# 按进程/线程持续采样(-u CPU,-r 内存,-d 磁盘,-t 显示线程)
pidstat -u 1 5
pidstat -ut 1 3            # -t 展开线程,Java/Go 程序必看

# 找出线程数最多的进程
ps -eo nlwp,pid,comm --sort=-nlwp | head -10

# 系统调用频次统计(抓 10 秒后 Ctrl+C 看汇总)
sudo strace -c -p 12345

要到函数级精度,用 perf(需安装 linux-tools-$(uname -r) 或对应发行版的 perf 包):

sudo apt install -y linux-tools-common linux-tools-$(uname -r)   # Debian/Ubuntu
# RHEL 系:sudo dnf install perf

# 实时看热点函数
sudo perf top -p 12345

# 采样 30 秒并生成报告(99Hz 避免与定时器频率重合)
sudo perf record -F 99 -g -p 12345 -- sleep 30
sudo perf report --no-children | head -60

# 需要火焰图时,导出折叠栈再交给 FlameGraph 脚本
sudo perf script > /tmp/perf.out

线程数这个指标值得单独关注。一个进程的线程数(NLWP)超过几百,而 CPU 占用并不高、cs 却很高,往往是线程池配置过大或锁竞争激烈,此时减少线程数反而能提升吞吐。

三、内存:别被 free 的输出骗了

free -mh
cat /proc/meminfo | grep -E 'MemTotal|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Slab|SReclaimable'
vmstat 1 5                 # 看 si/so 与 sr
sar -B 1 5                 # 看 pgscank/s、pgsteal/s 与 %steal(需 sysstat)

# 按进程看物理内存占用(RSS 列)
ps -eo rss,pid,user,comm --sort=-rss | head -15
pidstat -r 1 5

判断标准:

  • 看available 而不是 free。Linux 会尽量把空闲内存用作 page cache,所以 free 很小是正常的;available 低于总内存的 10% 时才算真的紧张。
  • si/so 持续大于 0 就是告警信号,说明在换页。哪怕 swap 只用了几百 MB,只要有持续的换入换出,延迟就会剧烈抖动。
  • vm.swappiness(/proc/sys/vm/swappiness)默认值为 60,数据库类服务器建议调到 1~10,让内核尽量不把热数据换出。
  • Slab 中 SReclaimable 很大通常是 dentry/inode 缓存,属于正常可回收部分,不必恐慌。

3.1 PSI:更直接的"卡不卡"指标

内核 4.20 起提供 Pressure Stall Information(PSI,压力失速信息),它直接回答"有多少比例的任务因为资源不足而被迫等待",比百分比更贴近体感。

cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/io

# cgroup v2 下针对单个服务查看
cat /sys/fs/cgroup/system.slice/app.service/memory.pressure
cat /sys/fs/cgroup/system.slice/app.service/cpu.pressure

输出形如 some avg10=12.34 avg60=5.67 avg300=1.23 total=1234567。avg10 持续超过 10% 就说明已经有明显比例的进程在排队等待;full 这一行(全部任务都被阻塞)一旦有数值,基本意味着服务已经对外不可用了。cpu.pressure 与 memory.full 是两类最强信号。

四、磁盘 I/O:最容易背锅的一环

# 关键:-x 展开扩展统计,1 秒刷新
iostat -x 1 5
iostat -x -p ALL 1 3        # 展开所有分区

# 按进程看 I/O
sudo iotop -oPa              # -a 累计量,-o 只显示有 I/O 的
pidstat -d 1 5

# 磁盘本身健康与错误
dmesg -T | grep -iE 'error|i/o|reset|blk_update|nvme'
sudo smartctl -a /dev/sda    # 需 smartmontools

# 空间与 inode
df -hT
df -i
du -xh --max-depth=2 /var 2>/dev/null | sort -hr | head -20

iostat -x 的读法与阈值:

字段含义判断阈值
%util设备处理 I/O 的时间占比持续接近 100% 即设备饱和(注意 SSD/RAID 下该值不完全等价"带宽打满")
await平均每次 I/O 的等待+处理时间(毫秒)NVMe/SSD 持续 >10ms、机械盘 >50ms 需关注
r_await / w_await读/写分别的延迟二者差异大时,说明读写负载不均衡或缓存策略有问题
aqu-sz / avgqu-sz平均请求队列长度持续 >2 说明请求在排队
rkB/s、wkB/s读写吞吐与设备标称吞吐对比,判断是否是带宽瓶颈
rrqm/s、wrqm/s每秒合并的请求数数值高说明访问模式较随机

两个常见误区:其一,%util 接近 100% 但 await 很低,对 NVMe 这类多队列设备来说是正常的高利用率,不代表有问题;其二,新版 sysstat(12.x 起)已经移除了 svctm 字段,依赖它的旧脚本需要改掉。

五、网络:延迟与重传比带宽更常见

# 接口层吞吐与错误
sar -n DEV 1 5
ip -s link show dev eth0        # 看 RX/TX 的 errors、dropped、overruns

# TCP 层重传统计(比 netstat -s 更清晰)
nstat -az | grep -iE 'retrans|listen|drops|overflow|abort'
nstat -az | grep -iE 'TcpActiveOpens|TcpPassiveOpens|TcpRetransSegs|TcpExtListenOverflows|TcpExtListenDrops'

# 连接状态总览
ss -s
ss -ant | awk 'NR>1{print $1}' | sort | uniq -c | sort -rn

# 重传率粗算:重传段 / 发送段
nstat -az | grep -E 'TcpRetransSegs|OutSegs'

# 网卡层丢包与驱动统计
ethtool -S eth0 | grep -iE 'drop|err|miss|over'
ethtool eth0            # 确认速率与双工是否协商正确

判断阈值:重传率(TcpRetransSegs / OutSegs)持续超过 1%~2% 即应处理;TcpExtListenOverflows 或 TcpExtListenDrops 计数持续增长,说明 listen 队列溢出,需要调大 somaxconn 与应用自身的 backlog。ip -s link 里的 dropped/overruns 增长通常指向网卡 Ring Buffer 不足或 CPU 处理不过来。

六、应用层:把"慢"落到具体请求与语句

资源层面都正常、但业务依然慢,问题通常在应用内部或依赖服务上。分组件来看:

组件关键手段开启/查看方式关注阈值
MySQL / MariaDB慢查询日志slow_query_log=ON、long_query_time=1(默认 10 秒,生产建议 1 秒)扫描行数远大于返回行数的语句
MySQL 运行时当前会话与锁SHOW FULL PROCESSLIST;、SHOW ENGINE INNODB STATUS\G出现大量 Sending data、Locked、等待超过 1s 的会话
Nginx访问日志耗时字段log_format 中加入 $request_time 与 $upstream_response_time二者差值大 = 应用慢;二者都大 = 网络或客户端慢
Nginx 内部连接与排队stub_status 模块:Active connections、Reading/Writing/WaitingWaiting 很高说明 keepalive 连接占着不放
PHP-FPM慢日志与进程池slowlog + request_slowlog_timeout=1s;pm.max_children出现 server reached pm.max_children 即进程池不足
JavaGC 与线程栈jstat -gcutil PID 1s、jstack PID、开启 GC 日志FGC 频繁或单次 GC 停顿 >1s
任意进程I/O 与句柄cat /proc/PID/io、ls /proc/PID/fd \| wc -l句柄数接近 LimitNOFILE 的 80%

Nginx 的耗时字段是性价比最高的一步改造,直接在 log_format 里加上 $request_time、$upstream_response_time、$upstream_addr,就能把"哪台后端慢、慢在转发还是处理"一眼分开:

log_format timing '$remote_addr $request $status '
                  'rt=$request_time urt=$upstream_response_time '
                  'up=$upstream_addr req_len=$request_length';
access_log /var/log/nginx/timing.log timing;

# 实时看最慢的 20 个请求
awk '{print $0}' /var/log/nginx/timing.log | sort -t= -k3 -rn | head -20

七、一套可复制的排查步骤清单

  1. 确认现象与时间窗:是持续慢还是周期性慢?周期性慢优先想到定时任务、日志切割、备份、GC。
  2. uptime + nproc:算负载与核数的比值,判断是否真的排队。
  3. vmstat 1 5:用 r、b、si/so、wa、st 把问题归类到 CPU / 内存 / I/O / 虚拟化。
  4. cat /proc/pressure/*:用 PSI 交叉验证是否真的有任务在等资源。
  5. 按归类深入:CPU → pidstat -u + perf;内存 → free + pidstat -r;I/O → iostat -x + iotop;网络 → sar -n DEV + nstat -az。
  6. 定位到进程后,再往下一层:看该进程的日志、慢查询、线程栈、系统调用统计。
  7. 验证改动效果:任何优化前后都要用同一个指标、同一个采样窗口对比,避免"感觉变快了"。
  8. 把本次的结论写成一条监控项,保证同类问题下次自动告警。

小结

定位性能瓶颈的要领不是会用多少工具,而是不跳过分类这一步。先用 vmstat 的 r/b/si-so/wa/st 五组数字把问题归到某一类资源,再进到那一类的专用工具里深挖,能避开九成的无用功。

避坑建议:

  • 别只看 free 的空闲值,Linux 会用空闲内存做缓存;要看 available 和 si/so。
  • 云主机上务必看 st(steal time),持续超过 5% 时,本机优化基本无效,问题在宿主机。
  • 负载高但 CPU 空闲,一定要查 D 状态进程,这类问题往往出在 NFS、云盘、异常的存储后端上。
  • iostat 的 %util 对 NVMe 不等于"打满",要结合 await 和 aqu-sz 一起看;新版 sysstat 已移除 svctm。
  • 优先用 ss、ip、nstat 而不是 netstat、ifconfig,后者属于 net-tools,长期无维护且部分发行版默认不再安装。
  • 不要在没有基线数据的情况下做优化。先把 sar(sysstat)采集打开,让它跑满一个业务周期,你才知道"正常"长什么样。
  • 扩容是手段不是诊断。先把"到底是哪一类资源在饱和"说清楚,否则加了配置也只是在推迟问题。

性能分析里最贵的一句话是"应该是数据库的问题吧"。多花三分钟跑一遍 vmstat 和 iostat,能省掉几天的无效排查。