「慢」是一个结果,不是原因。排查的顺序应该是:先确认是哪类资源不够,再看是谁在占用,最后才改配置。顺序反了,容易一边加内存一边还在被磁盘拖住。
下面所有命令都以 Ubuntu 为例,只读命令居多,可以在线执行。
第 0 步:先问三个问题
- 是一直慢,还是某个时段慢?(备份、定时任务、流量高峰都可能导致)
- 是整站慢,还是某些页面/接口慢?
- 最近改过什么?(更新、装插件、改 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 持续高 |
换套餐或迁移机房 |
| 应用 | 资源正常、接口慢 | 慢查询、外部依赖、进程数配置 |
四个常见误判
free低就加内存:先看available,缓存不算浪费。- 负载高就升 CPU:先看
wa和b,可能是磁盘。 %util100% 就是磁盘到顶:SSD/RAID 上这个值不绝对;看await与业务延迟。- swap 用了一点就坏事:少量换页正常;持续
si/so才是问题。
长期做法
- 留一份正常时段的基线(负载、内存、IO、带宽各是多少),没有基线就判断不了「异常」;
- 给关键指标设告警:
available、await、磁盘使用率、5xx 数量; - 变更前后各跑一次同样的命令,确认改动有没有效果。
一句话总结
uptime 看负载 → vmstat 分 CPU/IO → free 看 available → iostat 看 await → ss 看网络 → 日志看应用。 一次只改一个变量,留好改前改后的对比数据。