「慢」是一个结果,不是原因。排查的顺序应该是:先确认是哪类资源不够,再看是谁在占用,最后才改配置。顺序反了,容易一边加内存一边还在被磁盘拖住。

下面所有命令都以 Ubuntu 为例,只读命令居多,可以在线执行。

第 0 步:先问三个问题

  1. 是一直慢,还是某个时段慢?(备份、定时任务、流量高峰都可能导致)
  2. 是整站慢,还是某些页面/接口慢?
  3. 最近改过什么?(更新、装插件、改 Nginx、加容器)

这三点能排除掉一半的误判。

第 1 步:看负载

uptime

输出末尾三个数字是 1、5、15 分钟的平均负载。手册里的定义是:

load average figures giving the number of jobs in the run queue (state R) or waiting for disk I/O (state D) averaged over 1, 5, and 15 minutes.

关键在 state D:等磁盘 I/O 的进程也算进负载。所以「负载高」有两种完全不同的情况:

  • CPU 真的忙:负载高 + CPU 使用率高;
  • 磁盘卡住:负载高 + CPU 大量空闲 + wa 高。

判断阈值别死记数字,先看趋势:负载持续超过 vCPU 数量,且业务确实变慢,才值得处理。

第 2 步:分清 CPU 忙还是 IO 等

vmstat 1 5

重点看四列(定义来自 vmstat 手册):

列 含义 怎么读
r 可运行进程数(运行中或等 CPU) 持续大于 vCPU 数 → CPU 排队
b 阻塞等待 I/O 的进程数 持续大于 0 → 有进程卡在 IO
si / so 每秒换入/换出内存 持续非 0 → 内存在换页
wa CPU 等待 IO 的时间百分比 高 → 瓶颈大概率在磁盘

再看谁在吃 CPU:

top -o %CPU

顺手看 st(steal)这一列。VPS 上 st 持续偏高,说明宿主机在争抢物理 CPU,这不是你能优化的,考虑换套餐或换机房。

第 3 步:看内存

free -h

别只看 free 列。手册对 available 的定义是:

Estimation of how much memory is available for starting new applications, without swapping.

也就是说,available 才是「还能用多少」,它把可回收的缓存算进去了;free 只是完全没被使用的部分,低是正常的。

再看有没有被系统杀掉进程:

dmesg -T | grep -i -E "out of memory|killed process"
journalctl -k | grep -i "out of memory"

如果 si/so 持续非 0、available 长期很低、日志里有 OOM,才需要加内存或减少常驻服务。

第 4 步:看磁盘 IO

iostat -xz 1 5

两个关键指标:

  • await:I/O 请求从发出到完成的平均毫秒数,含排队时间;
  • %util:设备有 I/O 请求的时间占比。

官方对 %util 有一句很重要的限定:

Device saturation occurs when this value is close to 100% for devices serving requests serially. But for devices serving requests in parallel, such as RAID arrays and modern SSDs, this number does not reflect their performance limits.

所以 SSD 上 %util 100% 不等于「一定到顶」,但 await 明显升高、业务延迟同步上升,就说明磁盘在拖后腿。

再看是谁在读写:

iotop -o

第 5 步:看网络

ss -s
ss -tun state established | wc -l
ip -s link

重点:

  • 带宽是否被打满(下载、备份、被刷流量都会);
  • 连接数是否异常增长(爬虫、攻击、应用连接池泄漏);
  • 是否有大量重传(ss -ti 里的 retrans 计数)。

第 6 步:看应用层

资源都正常但就是慢,问题多半在应用:

# Nginx
tail -f /var/log/nginx/error.log
tail -f /var/log/nginx/access.log

# PHP-FPM 慢日志(需在 pool 配置里开启 request_slowlog_timeout)
tail -f /var/log/php*-fpm-slow.log

# 数据库慢查询
mariadb -e "SHOW VARIABLES LIKE 'slow_query_log';"

# 容器
docker stats --no-stream

常见原因:数据库缺索引、外部 API 超时、PHP-FPM 进程数不够、容器内存被限制后频繁回收。

按瓶颈选动作

瓶颈 证据 处理顺序
CPU r 持续大于 vCPU、CPU 使用率高 先限流/缓存,再考虑升配
IO b 高、wa 高、await 高 查大查询与日志写入 → 升 NVMe → 加内存减少换页
内存 available 低、si/so 持续、OOM 查泄漏 → 减常驻服务 → 加内存/加 swap 兜底
网络 带宽满、连接数异常 上 CDN/限流/排查攻击,再考虑加带宽
平台 st 持续高 换套餐或迁移机房
应用 资源正常、接口慢 慢查询、外部依赖、进程数配置

四个常见误判

  1. free 低就加内存:先看 available,缓存不算浪费。
  2. 负载高就升 CPU:先看 wa 和 b,可能是磁盘。
  3. %util 100% 就是磁盘到顶:SSD/RAID 上这个值不绝对;看 await 与业务延迟。
  4. swap 用了一点就坏事:少量换页正常;持续 si/so 才是问题。

长期做法

  • 留一份正常时段的基线(负载、内存、IO、带宽各是多少),没有基线就判断不了「异常」;
  • 给关键指标设告警:available、await、磁盘使用率、5xx 数量;
  • 变更前后各跑一次同样的命令,确认改动有没有效果。

一句话总结

uptime 看负载 → vmstat 分 CPU/IO → free 看 available → iostat 看 await → ss 看网络 → 日志看应用。 一次只改一个变量,留好改前改后的对比数据。