"服务器有点卡"是运维收到的最模糊的一类反馈。它可能是 CPU 打满、可能是磁盘在排队、可能是数据库慢查询、也可能是云厂商的宿主机在抢你的 CPU 时间。不加区分地"加配置"是成本最高、命中率最低的做法。真正有效的是一套有顺序的资源排查法:先确定是哪种资源在饱和,再沿着调用链往下找到具体的进程和代码路径。本文给出可直接照做的方法与判断阈值。
一、先用 60 秒确定"是哪一类资源"
业界通行的做法是 USE 方法(Utilization 使用率、Saturation 饱和度、Errors 错误):对每一种资源,依次看这三个指标。落到 Linux 上,四大类资源的顺序为 CPU、内存、磁盘 I/O、网络。
| 资源 | 使用率指标 | 饱和度指标 | 错误指标 | 常用工具 |
|---|---|---|---|---|
| CPU | top 的 us+sy+st | load average / 运行队列 r | 无 | top、vmstat、pidstat、perf |
| 内存 | 已用内存 / 可用内存 | si/so 换页、PSI memory | OOM kill 计数 | free、vmstat、sar -B、PSI |
| 磁盘 | iostat 的 %util | aqu-sz 队列长度、await | I/O error(dmesg) | iostat、iotop、pidstat -d |
| 网络 | 带宽占用 / 包速率 | 重传率、丢包、队列溢出 | ip -s link 的 errors/drops | sar -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/Waiting | Waiting 很高说明 keepalive 连接占着不放 |
| PHP-FPM | 慢日志与进程池 | slowlog + request_slowlog_timeout=1s;pm.max_children | 出现 server reached pm.max_children 即进程池不足 |
| Java | GC 与线程栈 | 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
七、一套可复制的排查步骤清单
- 确认现象与时间窗:是持续慢还是周期性慢?周期性慢优先想到定时任务、日志切割、备份、GC。
uptime+nproc:算负载与核数的比值,判断是否真的排队。vmstat 1 5:用r、b、si/so、wa、st把问题归类到 CPU / 内存 / I/O / 虚拟化。cat /proc/pressure/*:用 PSI 交叉验证是否真的有任务在等资源。- 按归类深入:CPU →
pidstat -u+perf;内存 →free+pidstat -r;I/O →iostat -x+iotop;网络 →sar -n DEV+nstat -az。 - 定位到进程后,再往下一层:看该进程的日志、慢查询、线程栈、系统调用统计。
- 验证改动效果:任何优化前后都要用同一个指标、同一个采样窗口对比,避免"感觉变快了"。
- 把本次的结论写成一条监控项,保证同类问题下次自动告警。
小结
定位性能瓶颈的要领不是会用多少工具,而是不跳过分类这一步。先用 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,能省掉几天的无效排查。
企业QQ咨询




