服务器突然卡顿运维排查 SOP
来源:运维派 | 发布日期:2026-07-13 原文:https://mp.weixin.qq.com/s/BskVYSGQ4GpU52njG7ZPvw
本文是 全量排查流程篇,覆盖从卡顿告警到根因确认的完整取证路径。与 server-performance-four-dimensions(五维深度解析)和 cpu-spike-troubleshooting-guide(CPU 专项)互补使用。
一、先明确"卡顿"是什么
收到告警后,先记录完整信息:
- 首次发生时间、持续时长、是否已恢复
- 影响范围:整机、一个服务、一个接口,还是一批客户端
- SSH 是否也慢,还是只有业务请求慢
- 是否最近有发布、扩容、批处理、备份、安全扫描
故障开始时间很关键。只看当前状态可能得出"一切正常"的结论,而
sar、日志和监控可以回看已结束的尖峰。排查时最容易犯的错误,是一登录就重启服务、清缓存或修改内核参数。这样可能暂时恢复业务,却会同时清掉进程现场、短周期指标和部分日志证据。本文命令适用于 RHEL、Rocky Linux、AlmaLinux、Ubuntu、Debian;个别工具需要安装 sysstat、iotop、lsof、ethtool 等软件包。不同发行版、内核和工具版本的字段可能略有差异,以本机帮助信息和实际输出为准。
二、信息收集:先做只读快照
DIAG_DIR="/var/tmp/diag-$(hostname)-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$DIAG_DIR"
uptime | tee "$DIAG_DIR/uptime.txt"
free -h | tee "$DIAG_DIR/free.txt"
vmstat 1 10 | tee "$DIAG_DIR/vmstat.txt"
chmod 700 "$DIAG_DIR"
关键原则: 登录后先执行低风险只读命令,不要把采集文件写到已爆满的分区。系统接近失去响应时,不要同时启动多项高频采集。
三、初步判断:vmstat 区分方向
vmstat 1 10
关键指标解读
| 指标 | 含义 | 异常信号 |
|---|---|---|
r |
等待 CPU 的可运行任务数 | 持续 > CPU 核数 → CPU 排队 |
b |
不可中断睡眠任务数 | I/O 等待,需结合进程状态确认 |
si/so |
每秒换入/换出量 | 持续非零说明活跃交换 |
us/sy |
用户态/内核态 CPU | us 高→应用计算;sy 高→系统调用/内核 |
wa |
CPU 空闲时等待 I/O 比例 | 不是磁盘利用率,wa 低也不能排除设备延迟 |
st |
虚拟机被宿主机拿走的 CPU | 云主机关键,持续升高联系云平台 |
# 确认逻辑 CPU 数
nproc
lscpu
四、CPU 排查
诊断关键
# 查看整体和单核使用率
mpstat -P ALL 1 5
# 示例输出:
# 14:35:01 CPU %usr %nice %sys %iowait %irq %soft %steal %idle
# 14:35:02 all 72.10 0.00 18.30 0.20 0.10 5.20 0.00 4.10
# 14:35:02 0 95.00 0.00 3.00 0.00 0.00 1.00 0.00 1.00
解读: %usr 高通常是应用计算;%sys 高可能与系统调用、锁、网络包或内存管理有关;%soft 高常见于网络软中断;单核满而其他核心空闲可能是单线程热点或中断亲和性问题。
定位进程和线程
# 进程级
pidstat -u 1 10
ps -eo pid,ppid,user,stat,psr,pcpu,pmem,etime,comm,args --sort=-pcpu | head -30
# 示例输出:
# 14:36:09 UID PID %usr %system %guest %wait %CPU CPU Command
# 14:36:09 998 18472 165.0 28.0 0.0 2.0 193.0 6 java
# 多线程进程 %CPU 可超过 100%
# 线程级
top -H -p 18472
pidstat -t -p 18472 1 10
# PID 会复用,执行前通过 ps -p 18472 -o pid,lstart,args 再确认进程身份
系统态与软中断
当 %sys 或 %soft 高时:
sar -w 1 10 # 上下文切换
cat /proc/softirqs # 软中断分布
ethtool -S eth0 # 网卡统计
pidstat -w 1 10 # 自愿/非自愿切换
锁竞争与线程池
进程可能只占少量 CPU,但因锁或线程池耗尽而几乎不工作。表现:请求排队、活动线程固定、吞吐下降,整机仍有大量空闲 CPU。
timeout 10 strace -f -p <PID> -tt -T -c
大量 futex 只说明线程同步活动多,不能证明某把锁是根因。Java 用 jcmd,Go 用 pprof,数据库用自身等待事件。
五、内存排查
free -h
grep -E 'MemAvailable|SwapTotal|SwapFree|Dirty|Slab|SReclaimable' /proc/meminfo
vmstat 1 10
| 检查点 | 说明 |
|---|---|
available |
Linux 用空闲做页缓存,不能只看 free |
si/so 持续 |
只有活跃交换才说明内存压力 |
| OOM 日志 | journalctl -k \| grep -Ei 'out of memory|oom-killer' |
| PSI 压力 | cat /proc/pressure/memory |
| 内存大户 | ps -eo pid,user,rss,vsz,pmem,etime,comm --sort=-rss \| head -30 |
| Slab | 目录项/inode 缓存大可能来自扫描小文件 |
| NUMA | numactl --hardware + numastat 检查节点局部压力 |
容器环境检查 cgroup:
cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.events
区分页缓存、Slab 和匿名内存
| 类型 | 含义 | 异常信号 |
|---|---|---|
AnonPages |
匿名页(进程堆/栈) | 高通常与进程堆、匿名映射有关 |
Slab |
内核对象缓存 | SReclaimable 理论上可回收;SUnreclaim 不可直接回收 |
PageTables |
页表内存 | 大内存映射场景可能显著增长 |
Dirty/Writeback |
脏页 | 持续大量可能说明 IO 堵塞 |
grep -E '^(AnonPages|Mapped|Shmem|Slab|SReclaimable|SUnreclaim|PageTables|Dirty|Writeback):' /proc/meminfo
slabtop -o
在 NUMA 服务器上,系统总内存有余也可能出现节点局部压力:
numactl --hardware
numastat
numastat -p <PID>
不要使用
echo 3 > /proc/sys/vm/drop_caches作为常规修复。它会丢弃可回收缓存并导致后续大量读取,且不能解决泄漏。
六、磁盘与文件系统
df -hT
df -i
iostat -xz 1 10
pidstat -d 1 10
iotop -oPa
findmnt -o TARGET,SOURCE,FSTYPE,OPTIONS
关注 r_await、w_await、队列长度,并与设备类型和历史基线比较。%util 对 NVMe/多队列设备不能简单解释为饱和。
# 检查块设备错误
journalctl -k | grep -Ei 'I/O error|timeout|reset|nvme|ext4|xfs|nfs'
# 已删除但仍占用的文件
lsof +L1
七、网络排查
ip -s link
sar -n DEV 1 10
sar -n TCP,ETCP 1 10
ss -s
ethtool eth0 | grep -E 'Speed|Duplex|Link detected'
nstat -az | grep -E 'TcpRetransSegs|TcpExtListenOverflows|TcpExtListenDrops'
连接状态和热点对端:
ss -tan state established | awk 'NR>1 {print $5}' | sort | uniq -c | sort -nr | head
抓包需授权,限定接口、端口和包长:
timeout 60 tcpdump -i eth0 -nn -s 128 -c 20000 'host 10.x.x.x and port 3306' -w /var/tmp/db-traffic.pcap
chmod 600 /var/tmp/db-traffic.pcap
八、应用与依赖排查
系统没满不代表服务没堵。常见信号:
- 工作线程池 active 达到上限,队列持续增长
- 数据库连接池等待时间和超时上升
- GC 暂停、锁竞争或事件循环阻塞
- 日志同步写、备份、定时任务与故障窗口重合
systemctl status example.service --no-pager
journalctl -u example.service --since "2026-07-13 14:00:00" --until "2026-07-13 15:00:00" --no-pager
关键判断: "并发上限"和"处理能力"要分开。一个连接池只有 20 个连接时,超过 20 个并发请求仍需排队。扩大连接池可能把排队从应用转移到数据库。
K8s 环境:
kubectl -n <namespace> get pod -o wide
kubectl -n <namespace> top pod
kubectl -n <namespace> logs <pod-name> --since=30m --timestamps
不要在排查阶段直接执行删除 Pod、缩扩容或节点驱逐。
九、故障已恢复:用 sar 还原现场
# 查看 sysstat 历史文件
ls -lh /var/log/sa /var/log/sysstat 2>/dev/null
# 回看故障窗口(替换路径和时间)
sar -u -f /var/log/sa/sa13 -s 14:20:00 -e 14:35:00
sar -q -f /var/log/sa/sa13 -s 14:20:00 -e 14:35:00
sar -r -S -f /var/log/sa/sa13 -s 14:20:00 -e 14:35:00
sar -d -p -f /var/log/sa/sa13 -s 14:20:00 -e 14:35:00
sar -n DEV,TCP,ETCP -f /var/log/sa/sa13 -s 14:20:00 -e 14:35:00
检查系统时间是否跳变:
timedatectl status
journalctl -u chronyd -u systemd-timesyncd --since "2026-07-13 14:00:00" --until "2026-07-13 15:00:00" --no-pager
十、资源不高但请求卡住:文件描述符、端口、DNS
文件描述符耗尽
SERVICE_PID=$(systemctl show -p MainPID --value example.service)
cat "/proc/$SERVICE_PID/limits" | grep "open files"
find "/proc/$SERVICE_PID/fd" -maxdepth 1 -type l | wc -l
lsof -nP -p "$SERVICE_PID" | awk '{print $5}' | sort | uniq -c | sort -nr
端口与连接跟踪
sysctl net.ipv4.ip_local_port_range
cat /proc/sys/net/netfilter/nf_conntrack_count 2>/dev/null
cat /proc/sys/net/netfilter/nf_conntrack_max 2>/dev/null
DNS 与 TLS 延迟
/usr/bin/time -f '%e seconds' getent hosts api.example.internal
# TLS 分阶段耗时
curl --output /dev/null --silent --max-time 10 \
--write-out 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} first_byte=%{time_starttransfer} total=%{time_total}\n' \
https://service.example.com/health
十一、虚拟机与容器限额
云主机中 %steal 持续升高说明宿主机争用。容器内 CPU 数与配额可能不一致:
# cgroup v2
cat /sys/fs/cgroup/cpu.max
cat /sys/fs/cgroup/cpu.stat | grep throttled
cat /sys/fs/cgroup/memory.events
# cgroup v1 路径不同,以 mount | grep cgroup 为准
CPU throttling 频繁增长 + 延迟升高 + 节点仍有空闲 → 容器限额可能是瓶颈。
十二、证据链确认根因
根因需满足三个一致:
- 时间一致 — 指标、日志、作业记录的时间窗口重合
- 对象一致 — 同一进程/设备/任务
- 机制一致 — 因果链完整(如:批处理 → 写盘 → w_await 升高 → 接口延迟上升)
示例完整证据链:"接口 P99 在 14:27 升高 → iostat 显示同一窗口 w_await 高于基线 → pidstat 显示批处理进程是主要写入者 → 作业平台确认任务 14:27 启动 → 停止单灰度作业后延迟恢复。"
十三、修复:先止损,再处理根因
低风险止损动作:暂停非核心批任务、摘除异常实例、降低后台并发、关闭刚启用的功能开关。
服务重启按高风险操作处理:
# 验证
systemctl cat example.service
systemd-analyze verify /etc/systemd/system/example.service
# 重启
systemctl restart example.service
systemctl is-active example.service
# 验证后观察至少一个业务周期
修改内核参数同样是高风险操作。不要在故障现场直接
sysctl -w放大队列或关闭交换。
十四、验证四层
修复后至少完成:
| 层 | 检查项 |
|---|---|
| 系统层 | 负载、CPU、内存压力、磁盘延迟、丢包回到基线 |
| 进程层 | 异常线程恢复、队列不再增长 |
| 应用层 | 吞吐恢复、P50/P95/P99 延迟符合目标 |
| 用户层 | 从入口发起受控请求确认关键链路可用 |
十五、可执行排查顺序
☐ 明确故障窗口、影响范围、最近变更
☐ 保存监控,记录 date、uptime、登录
☐ vmstat 判断 CPU/I/O/交换方向
☐ mpstat + pidstat + ps 定位 CPU 进程/线程
☐ free + meminfo + PSI + OOM 日志检查内存
☐ df + iostat + pidstat -d + 内核日志检查磁盘
☐ ip -s + sar + nstat + ss 检查网络
☐ 检查线程池、连接池、GC、日志、下游依赖
☐ 建立时间+对象+机制一致的证据链
☐ 单实例灰度止损,持续验证,满足条件回退
☐ 复盘,补齐监控/限流/容量/变更保护
关联页面
| 页面 | 关联点 |
|---|---|
| server-performance-four-dimensions | CPU/内存/磁盘/网络/文件系统五维深度解析 |
| cpu-spike-troubleshooting-guide | Linux CPU 飙高排查完整方法论 |
| cpu-100-full-chain-diagnosis | CPU 100% 全链路分析与 10 大场景实战 |
| linux-perf-troubleshooting-handbook | Linux 性能排查实战手册(三板斧/阈值速查) |
| fullstack-performance-troubleshooting | Nginx → 应用 → 数据库 → 服务器全栈排障 |
| linux-disk-io-troubleshoot | 磁盘 IO 排查实战 — %util 陷阱与 await 真相。服务器变慢时 CPU/内存正常但磁盘 |
| system-load-high-rsyslogd-case-study | 一个 rsyslogd 被 audit 审计日志刷屏导致的系统负载高案例,涵盖 CPU 拆解、软中断 |
| nginx-log-analysis-troubleshooting-guide | Nginx 日志分析与排障 |