服务器卡顿怎么排查

结论:服务器卡顿的排查必须按"先看是哪类资源饱和,再找具体进程"的顺序走,而不是凭感觉重启。标准方法是 USE 方法——依次检查每种资源的使用率(Utilization)、饱和度(Saturation)、错误数(Errors),用 top 看 CPU、vmstat 1 看内存与 swap、iostat -x 1 看磁盘 IO、sar -n DEV 1 看网络,90% 的卡顿能在 5 分钟内定位到具体资源。

服务器卡顿是最让人头疼的故障之一,因为它的表现千奇百怪:SSH 敲命令半天不回显、网页打开要转十几秒、接口偶尔超时又时而正常。很多人的第一反应是"重启一下试试",这在多数时候只是把证据销毁了,问题过两天照旧复现。真正有效的做法是建立一套固定顺序的排查路径,让每一次卡顿都能被归因到具体资源。本文给出这套路径,以及对应的命令与判读标准。

什么是 USE 方法?为什么它能快速定位卡顿?

USE 方法是 Brendan Gregg 提出的一套系统性排查思路:对每一种硬件资源(CPU、内存、磁盘、网络),都检查三个指标。

  • 使用率(Utilization):资源处于繁忙状态的时间百分比。例如 CPU 使用率 85%、磁盘 busy 90%。
  • 饱和度(Saturation):资源过载时积压的排队工作量。例如 CPU 运行队列 r 值、磁盘平均等待队列长度。饱和度比使用率更能说明"到底堵了没有"。
  • 错误数(Errors):硬件或驱动报告的错误。例如网卡丢包、磁盘介质错误。

三者的关系是:使用率高不一定有问题(可能是充分利用),但饱和度持续大于零就一定有排队、一定有延迟;而错误数非 0 通常意味着硬件或配置层面的真实故障。

建立 60 秒快速体检清单,卡顿时按此顺序执行:

bash
# 1. 系统整体负载与最近启动时间
uptime
# 输出里三个 load average 分别是 1/5/15 分钟平均负载

# 2. CPU 全貌:关注 %Cpu(s) 中 us/sy/wa/id 与%Cpu0 的单核热点
top -H -d 1

# 3. 进程、内存换页、IO 块读写、系统中断
vmstat 1 10

# 4. 磁盘扩展指标:重点关注 %util、await、svctm、rkB/s、wkB/s
iostat -x 1 5

# 5. 网络吞吐与丢包
sar -n DEV 1 5

# 6. 每个设备的详细统计(含 IO 合并与队列)
sar -d 1 5

# 7. 磁盘空间是否写满(写满会让业务全面卡死)
df -hT
df -i

怎么用决策树快速归因?

下面这张表把常见现象直接映射到可能的原因与下一步命令,卡顿时逐行对照即可:

现象(关键指标)可能原因下一步命令
load 高 + top 里 %CPU 高的进程明显应用计算密集或死循环top -H 找线程 → jstack / perf top
%sy(内核态 CPU)异常高系统调用过多、上下文切换频繁vmstat 看 cs 值、pidstat -w 1
%wa(IO 等待)高 + %util 接近 100%磁盘 IO 瓶颈iotop -oP、iostat -x 1 定位分区
si/so(swap)持续非 0内存不足开始换页free -h、ps aux --sort=-rss
await 高但 %util 不高随机 IO 过多或 RAID 卡电池故障iotop、检查写缓存策略
r(运行队列)> CPU 核数CPU 饱和,排队请求过多vmstat 1、考虑扩容或优化
网卡 rxkB/s 接近带宽上限带宽跑满iftop、nethogs 看流量来源 IP
sar 显示 retrans 或丢包增长网络质量差或连接跟踪表满ss -s、cat /proc/sys/net/nf_conntrack_count
MySQL 连接数打满、QPS 骤降慢查询或锁等待SHOW PROCESSLIST、mysqldumpslow
Nginx 出现大量 499/502/504后端处理过慢或超时配置过短tail -f error.log、awk 统计状态码

CPU 类卡顿怎么精确定位?

CPU 问题的关键是区分"用户态(us)高"和"内核态(sy)高"。用户态高通常是应用自己的计算(加密、正则回溯、图像处理、序列化);内核态高往往是系统调用过多、频繁读写小文件、上下文切换太猛。

三步定位到具体线程:

bash
# 第一步:看整体,记下 us/sy/wa/id 各自占比
top -d 1

# 第二步:找出 CPU 占用最高的进程 PID,再找它内部最忙的线程
ps aux --sort=-%cpu | head -10

# 第三步:用 -H 显示线程,拿到线程 TID
top -H -p 

# 把 TID 转成 16 进制,用于和 jstack 输出的 nid 对应
printf "%x\n" 

Java 进程进一步抓取线程栈:

bash
# 连续抓 3 次,间隔 5 秒,方便发现 RUNNABLE 的热点线程
for i in 1 2 3; do
  jstack  > /tmp/jstack_$i.txt
  sleep 5
done

# 找出处于 RUNNABLE 状态且重复出现的栈顶方法
grep -A 5 "RUNNABLE" /tmp/jstack_1.txt | head -60

# 系统级热点函数(需要安装 perf)
perf top -p 

常见的 CPU 型卡顿成因:

  • 正则表达式回溯(ReDoS),一条正则把 CPU 跑满。
  • JSON/XML 序列化处理超大报文。
  • 循环内的重复加解密或哈希计算。
  • PHP 未开 OPcache,每次请求重新编译脚本。解决办法是在 php.ini 里启用 opcache.enable=1 并调大 opcache.memory_consumption=256。

磁盘 IO 与内存导致卡顿怎么判断?

磁盘 IO 是最容易被误判的一环,因为 top 里它表现为 %wa 高而 CPU 空闲,很多人会误以为"CPU 很闲说明没问题"。判读 iostat -x 1 的正确姿势:

  • %util 接近 100% → 设备已饱和,IO 请求在排队。
  • await > 10 ms(SSD 应 < 5 ms,NVMe 应 < 1 ms)→ 单次 IO 延迟过高。
  • avgqu-sz 持续 > 1 → 队列里有积压,确认存在排队。
  • r/s + w/s 很高但 rkB/s 很小 → 大量随机小 IO,典型如机械盘上的数据库写入。
bash
# 找出实际负责 IO 的进程(需要 root)
iotop -o -P -d 2

# 查看某进程正在读写的具体文件
ls -l /proc//fd | grep -v socket

# 内存不足时按 RSS 排序找大头
ps aux --sort=-rss | head -10

# 检查是否发生 OOM(内核可能已经杀过进程)
dmesg -T | grep -i "out of memory"
journalctl -k | grep -i oom

另外别遗漏这两个"隐性杀手":

bash
# inode 用尽比磁盘空间用尽更隐蔽,df -h 正常但写不进文件
df -i

# 大量已被删除但仍被进程占用的文件会占空间,需重启对应进程释放
lsof | grep deleted | head -20

MySQL 慢查询与 Nginx 日志怎么配合排查?

如果系统层四个资源都健康,瓶颈大概率在应用层或数据库层。此时最有价值的两个证据源是 MySQL 慢查询日志和 Nginx 访问日志——它们直接告诉你"慢在哪个请求、哪条 SQL"。

统计 Nginx 状态码分布,一眼看出是哪种失败:

bash
# 状态码分布统计(假设日志格式为标准 combined/main)
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head

# 找出最慢的 20 个请求(配合自定义日志格式中的 $request_time)
awk '($NF > 3) {print $7, $NF}' /var/log/nginx/access.log | sort -k2 -rn | head -20

# 实时观察是否有大量 499(客户端主动断开,说明后端响应太慢)
tail -f /var/log/nginx/access.log | awk '{if ($9 == 499 || $9 == 502 || $9 == 504) print $0}'

MySQL 侧同步排查:

sql
-- 查看当前正在执行的会话,重点关注 Time 很大且 State 为 Sending data/Copying to tmp table 的
SHOW FULL PROCESSLIST;

-- 查看锁等待情况
SELECT * FROM performance_schema.data_locks\G

-- InnoDB 引擎状态,可看到事务、锁、缓冲池命中率
SHOW ENGINE INNODB STATUS\G
bash
# 汇总最慢的 SQL 模板,定位 Top N
mysqldumpslow -s at -t 10 /var/log/mysql/slow.log

# 临时开启慢日志(long_query_time 单位为秒)
mysql -uroot -p -e "SET GLOBAL slow_query_log=ON; SET GLOBAL long_query_time=1;"

还有一个常被忽略的方向——网络出口。用 iftop 看实时流量,如果某 IP 占满了带宽,那服务端再优化也救不了用户体验:

bash
iftop -i eth0 -n
nethogs eth0
ss -antp | awk '{print $1}' | sort | uniq -c | sort -rn

常见误区 / 排错提示

  • 误区一:load 高一律当成 CPU 高。 load average 统计的是"可运行态 + 不可中断睡眠态(D 状态)"的进程数,IO 等待也会让 load 飙升。看到 load 高但 top 里 CPU 很闲,八成是磁盘或网络文件系统卡住了(NFS 挂起最典型)。
  • 误区二:卡顿就先重启。 重启会销毁现场证据,问题必然复现。正确做法是先保留 top -b -n 1 > /tmp/top.txt、vmstat 1 10、iostat -x 1 10 的输出快照,必要时重启,但事后必须复盘。
  • 误区三:只看平均值不看峰值。 间歇性抖动在 5 分钟平均负载上完全看不出来。必须部署秒级监控(如 Prometheus node_exporter + Grafana),保留至少 30 天历史数据用于回溯。
  • 误区四:忽略 D 状态进程。 top 里 State 为 D(Uninterruptible Sleep)的进程无法被 kill,它们在等 IO。堆积说明存储层已经出问题了。
  • 排错:SSH 登录本身就很卡。 若延迟来自 DNS 反查,在 /etc/ssh/sshd_config 里设 UseDNS no;若来自 PAM 或 sudo 卡顿,检查 /etc/hosts 是否缺少本机 hostname 解析。
  • 排错:iostat 命令不存在。 需要安装 sysstat 包:apt install sysstat 或 yum install sysstat,并确认 systemctl enable --now sysstat 已开启历史采集。

常见问题(FAQ)

怎么看 load average 才算高?

结论:load 超过 CPU 逻辑核数就说明有排队,超过核数的 2 倍就是明显过载。一台 4 核机器 load 达到 8 意味着平均有 4 个进程在抢不到 CPU 而排队。但要注意,IO 等待同样计入 load,因此 iostat 里 %util 接近 100% 时 load 高不代表 CPU 不够用,此时加 CPU 完全无效。

卡顿的时候要不要马上重启?

结论:生产环境优先保正服务,但重启前一定要先"留证"。顺序是:先采集 10 秒的 top -b -n 1、vmstat 1 10、iostat -x 1 10、ss -antp > /tmp/net.txt 输出并保存到 /tmp,若有条件再抓 Java thread dump 或 perf record。确认证据留存后再执行重启,事后用这些快照复盘根因。

为什么 CPU 和内存都很闲,网站还是很慢?

结论:八成是 IO 或外部依赖阻塞。检查顺序:iostat -x 1 看 %util 和 await;查 MySQL 是否有锁等待(SHOW PROCESSLIST 里 State 卡在 Waiting for lock);查应用是否在同步调用外部 HTTP 接口或 DNS 解析超时;最后确认是否是 DNS、NTP、NFS 等外部服务抖动导致的连锁反应。

MySQL 慢查询阈值设多少合适?

结论:交互式业务建议 0.5~1 秒,批量任务可放宽到 2~5 秒。设置过大会漏掉那些单条不慢、但被循环调用几千次的 SQL;设得过小则慢日志本身会吃掉大量磁盘 IO,形成负反馈。折中做法是设 1 秒 + 开启 log_queries_not_using_indexes,定期用 mysqldumpslow 汇总分析。

怎么用一条命令判断是哪种资源瓶颈?

结论:用 vmstat 1 基本能一次看清四类资源。输出里:r 列持续大于 CPU 核数说明 CPU 排队;b 列非 0 说明有进程因 IO 阻塞;si/so 非 0 说明内存不足在换页;cs 每秒上下文切换超过 10 万次说明调度开销过大。配合 iostat -x 1 看 %util,基本就能锁定是哪一类资源在拖后腿。

间歇性卡顿(时快时慢)怎么抓?

结论:靠常驻的秒级监控而不是临时登机器。推荐部署 Prometheus + node_exporter(采集间隔 15 秒以内)+ Grafana,配合 MySQL exporter 与 Nginx 的 status 模块。同时建议开启 sar 的历史采集(sysstat 包),它每 10 分钟记录一次,故障后可以用 sar -r -f /var/log/sa/saXX 回看当时的数据,成本极低而价值很高。