首页 运维干货一台服务器突然卡顿,运维排查步骤是什么?

一台服务器突然卡顿,运维排查步骤是什么?

运维派隶属马哥教育旗下专业运维社区,是国内成立最早的IT运维技术社区,欢迎关注公众号:yunweipai
领取学习更多免费Linux云计算、Python、Docker、K8s教程关注公众号:马哥linux运维

服务器“卡顿”不是一个根因,而是用户对响应时间变长、SSH 操作迟缓、接口超时或任务堆积的统称。CPU 打满会卡,内存回收和交换会卡,磁盘延迟升高会卡,网络丢包会卡,下游依赖变慢也会让本机看起来像卡住。排查时最容易犯的错误,是一登录就重启服务、清缓存或者修改内核参数。这样可能暂时恢复业务,却会同时清掉进程现场、短周期指标和部分日志证据。

本文以常见 Linux 服务器为对象,给出一套可以直接落地的排查顺序。命令适用于主流的 RHEL、Rocky Linux、AlmaLinux、Ubuntu 和 Debian 系统;个别工具需要安装 sysstatiotoplsofethtool 等软件包。不同发行版、内核和工具版本的字段可能略有差异,判断时以本机帮助信息和实际输出为准。

一、先把“卡顿”描述清楚

收到告警后,先记录时间,不要只写“刚才很卡”。至少确认以下信息:

  • 首次发生时间、持续时长、是否已经恢复;
  • 影响的是整台机器、一个服务、一个接口,还是一批客户端;
  • SSH 是否也慢,还是只有业务请求慢;
  • 所有请求都慢,还是某个分位延迟、某类请求或某个可用区异常;
  • 最近是否发布、扩容、执行批处理、备份、日志归档或安全扫描;
  • 告警来自应用、系统、负载均衡器还是用户反馈。

如果有监控,先固定故障窗口,例如 14:20—14:35,再把窗口前后各扩展 15 分钟。至少保留 CPU 使用率、负载、可用内存、交换、磁盘吞吐与延迟、网络吞吐与丢包、连接数、应用吞吐、错误率和延迟分位数。Prometheus 的指标名取决于 node_exporter、应用 exporter 和采集规则,文中提到的指标均以实际 exporter 暴露的指标为准。

故障开始时间很重要。只看当前状态可能得到“现在一切正常”的结论,而 sar、日志和监控可以回看已经结束的尖峰。

二、信息收集:先做只读快照

登录服务器后,先执行一组低风险、只读命令,把输出保存到有足够空间的诊断目录。不要把采集文件写到已经爆满的分区。

   bashdate -Is
hostnamectl
uptime
who -b
last -x | head -n 20

uptime 给出当前时间、运行时长、登录用户数和 1、5、15 分钟平均负载。who -b 和 last -x 用于确认是否发生过重启、关机或运行级别切换。

示例输出:

   text2026-07-13T14:32:18+08:00
 14:32:18 up 87 days,  3:11,  2 users,  load average: 18.42, 15.67, 8.21

这段示例只能说明负载正在上升,不能直接等同于 CPU 打满。Linux 负载包含可运行任务,也包含某些不可中断睡眠任务,磁盘 I/O 卡住同样可能推高负载。

建议建立带时间戳的目录并用 tee 留证:

   bashDIAG_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"

DIAG_DIR 是环境变量,可把 /var/tmp 替换为确认有剩余空间、权限受控且不会影响业务的目录。诊断文件可能包含主机名、进程和网络信息,采集后应限制权限:

   bashchmod 700 "$DIAG_DIR"

如果系统已经接近失去响应,不要同时启动多项高频采集。pidstat 1iostat -xz 1sar -n DEV 1 都会增加少量开销,应该逐项运行并及时停止。

三、初步判断:先分 CPU、内存、I/O 和网络

第一轮判断可以使用 vmstat

   bashvmstat 1 10

示例输出:

   textprocs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
18  0      0 421800 121344 908120    0    0    12   180 9200 17000 88 10  2  0  0
20  1      0 418120 121344 908600    0    0    20   240 9500 18100 90  8  2  0  0

重点字段如下:

  • r:等待 CPU 的可运行任务数。持续明显高于逻辑 CPU 数,说明 CPU 排队;
  • b:不可中断睡眠任务数,常见于 I/O 等待,但需要结合进程状态确认;
  • si/so:每秒换入、换出量。持续非零说明存在活跃交换;
  • us/sy:用户态和内核态 CPU;
  • wa:CPU 空闲时等待 I/O 的比例。它不是磁盘利用率,wa 低也不能排除设备延迟;
  • st:虚拟机被宿主机拿走的 CPU 时间。云主机上持续升高要联系宿主资源争用判断。

逻辑 CPU 数可这样确认:

   bashnproc
lscpu

示例输出:

   text16

若 16 核机器的 r 长时间为 30,且 CPU 空闲接近 0,可优先查 CPU。若 b 很高、磁盘延迟高,则优先查 I/O。若 si/so 持续增长并且进程有明显主缺页,则优先查内存。若系统指标平稳但接口慢,应把注意力转到网络、应用线程池、数据库和外部依赖。

四、CPU:区分“谁在用”和“为什么用”

4.1 查看整体和单核使用率

   bashmpstat -P ALL 1 5

示例输出:

   text14: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

整体 CPU 高并不一定是同一种问题。%usr 高通常是应用计算,%sys 高可能与系统调用、锁、网络包或内存管理有关,%soft 高常见于网络软中断,单核满而其他核心空闲则可能是单线程热点或中断亲和性问题。

4.2 定位进程和线程

   bashpidstat -u 1 10
ps -eo pid,ppid,user,stat,psr,pcpu,pmem,etime,comm,args --sort=-pcpu | head -n 30

示例输出:

   text14:36:08 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%。确认进程后再看线程:

   bashtop -H -p 18472
pidstat -t -p 18472 1 10

这里的 18472 必须替换为实际 PID。PID 会复用,执行前应通过 ps -p 18472 -o pid,lstart,args 再确认进程身份。

如果需要调用语言运行时的剖析工具,应优先使用服务自身已有的低开销诊断能力。perf topperf record 等工具需要相应权限,也会产生额外开销;生产环境应限制采样时间并先在单实例灰度:

   bashtimeout 30 perf top -p 18472

该命令只采样 30 秒,不会修改应用,但仍可能带来性能开销。不能仅凭函数名直接认定根因,还要把热点与发布版本、请求量、线程栈和应用指标对应起来。

4.3 识别进程排队和异常状态

   bashps -eo state,pid,ppid,wchan:32,comm,args | awk '$1 ~ /^D/ {print}'

示例输出:

   textD 22104 18472 io_schedule                     worker /srv/app/task

D 表示不可中断睡眠,常见于块设备、网络文件系统或内核路径等待。wchan 只是线索,不是最终根因。需要继续结合 iostat、内核日志、挂载类型和进程调用栈分析。不要对 D 状态进程反复执行 kill -9;信号通常要等内核等待结束后才有机会处理。

4.4 系统态、软中断和上下文切换

当 %sys 或 %soft 明显升高时,需要区分是应用大量系统调用、网络包处理、存储中断,还是内核异常路径。先看系统级趋势:

   bashsar -w 1 10
sar -I SUM 1 10
watch -n 1 'cat /proc/softirqs'

watch 会持续刷新,观察完成后按 Ctrl+C 停止。/proc/softirqs 是从开机累计值,应比较每秒增量和 CPU 分布。不要把某一列累计数大直接当成异常。

示例输出:

   text14:38:01    proc/s   cswch/s
14:38:02      8.00  184220.00

上下文切换高可能来自大量线程、锁竞争、频繁唤醒或正常的高并发网络负载,必须与历史基线和业务吞吐比较。如果请求量不变而 cswch/s 大幅增加、CPU 系统态升高、应用延迟同步恶化,可以继续定位进程的自愿与非自愿切换:

   bashpidstat -w 1 10
pidstat -wt -p 18472 1 10

PID 要替换并核对身份。自愿切换通常发生在线程等待锁、I/O 或条件变量时,非自愿切换通常表示被调度器抢占。它们只是调度现象,仍要结合线程栈、锁指标和运行时剖析确认。

网络软中断异常时,可以检查各 CPU 软中断分布、网卡队列和丢包:

   bashcat /proc/softirqs
ethtool -S eth0
cat /proc/interrupts

eth0 替换为实际接口。ethtool -S 的统计项由驱动定义,字段名不统一;是否为丢包或队列问题要查对应驱动和平台说明。修改 IRQ affinity、RPS、RFS 或网卡队列数量会改变包处理路径,属于内核/网络高风险调优,必须先记录旧值、单机灰度、压测验证并准备恢复。

4.5 锁竞争、线程池和“CPU 没满但处理不动”

进程可能只占少量 CPU,却因为互斥锁、信号量或线程池队列而几乎不工作。表现通常是请求排队、活动线程固定、吞吐下降,而整机仍有大量空闲 CPU。

可先用进程状态和线程栈做只读观察。原生进程可在获得授权后短时间使用:

   bashtimeout 10 strace -f -p 18472 -tt -T -c

strace 附加到进程会带来开销,系统调用频繁的服务影响尤其明显,只能在单实例、有限时间灰度,并在工具退出后确认服务指标。汇总中大量 futex 只说明线程同步活动多,不足以证明某一把锁是根因。

Java 应优先使用与运行时版本匹配的 jcmd 或线程转储能力,Go 应使用事先受控开放的 pprof,数据库则使用自身等待事件和活动会话视图。不要在未知版本进程上随意执行可能触发完整堆转储的命令;堆转储会产生大文件、暂停或磁盘压力。

线程池问题的完整证据通常包括:队列长度持续增长、活动线程达到上限、任务等待时间升高、线程栈集中等待同一资源,以及解除该资源瓶颈后吞吐恢复。只看到“线程数很多”不能证明线程池耗尽。

五、内存:不要把 free 小当成内存不足

5.1 看 available、交换和回收

   bashfree -h
grep -E 'MemAvailable|SwapTotal|SwapFree|Dirty|Writeback|Slab|SReclaimable' /proc/meminfo
vmstat 1 10

示例输出:

   text               total        used        free      shared  buff/cache   available
Mem:            31Gi        27Gi       420Mi       380Mi       3.6Gi       2.1Gi
Swap:          4.0Gi       3.7Gi       300Mi

Linux 会用空闲内存做页缓存,因此不能只看 free。更有意义的是 available、活跃换入换出、主缺页、内存回收和 OOM 记录。Swap 已使用也不等于当前正在抖动;只有 vmstat 的 si/so 持续出现、延迟同步升高时,才说明交换可能正在影响业务。

5.2 找出内存大户和增长进程

   bashps -eo pid,user,rss,vsz,pmem,etime,comm,args --sort=-rss | head -n 30
pidstat -r 1 10

示例输出:

   text  PID USER       RSS      VSZ %MEM     ELAPSED COMMAND COMMAND
18472 app    12584200 18840000 38.4  2-03:18:41 java /srv/app/app.jar

RSS 是驻留物理内存,VSZ 是虚拟地址空间,不能把很大的 VSZ 直接当成泄漏。判断泄漏需要观察同一进程 RSS、堆、堆外内存或对应 cgroup 指标是否随时间单调增长,并排除请求量、缓存预热和定时任务影响。

容器环境还要看 cgroup 限额。cgroup v2 可检查:

   bashcat /proc/18472/cgroup
cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.events

路径需依据 /proc/<PID>/cgroup 的实际映射确定,不能假定进程一定处于根 cgroup。cgroup v1 的文件名和层级不同,应以挂载信息 mount | grep cgroup 为准。

5.3 确认 OOM 和内存压力

   bashjournalctl -k --since "2026-07-13 14:00:00" --until "2026-07-13 15:00:00" | grep -Ei 'out of memory|oom-killer|killed process'

时间范围要替换为实际故障窗口。如果系统没有持久化 journal,可检查发行版对应的内核日志,例如 /var/log/kern.log 或 /var/log/messages

示例输出:

   textkernel: Out of memory: Killed process 18472 (java) total-vm:18840000kB, anon-rss:12584200kB

只有看到 OOM 日志、cgroup oom_kill 计数增长或明确的进程退出证据,才能判断进程因 OOM 被杀。不能仅凭“服务重启过”推断 OOM。

支持 PSI 的内核还可以查看:

   bashcat /proc/pressure/memory

示例输出:

   textsome avg10=24.31 avg60=18.02 avg300=5.47 total=921734201
full avg10=8.14 avg60=5.90 avg300=1.62 total=214004120

some 表示至少有任务因内存压力停顿,full 表示所有非空闲任务同时停顿。total 是自开机以来累计微秒数,不能把累计值直接当成当前压力。

5.4 区分页缓存、Slab 和匿名内存

free 只能给出总览。需要进一步判断内存去了哪里时,可以查看:

   bashgrep -E '^(AnonPages|Mapped|Shmem|Slab|SReclaimable|SUnreclaim|PageTables|KernelStack|Dirty|Writeback):' /proc/meminfo
slabtop -o

示例输出:

   textAnonPages:      18420320 kB
Slab:            2184200 kB
SReclaimable:    1742100 kB
SUnreclaim:       442100 kB

匿名页高通常与进程堆、匿名映射有关;Slab 是内核对象缓存,其中 SReclaimable 理论上可在压力下回收,SUnreclaim 不可直接回收。目录项和 inode 缓存大可能来自扫描大量小文件,但仍需观察趋势和业务行为。

不要使用 echo 3 > /proc/sys/vm/drop_caches 作为常规修复。它会丢弃可回收缓存并导致后续大量真实磁盘读取,可能让业务更慢,而且不能解决泄漏。若怀疑页缓存挤压匿名内存,应结合工作集、主缺页、I/O 和 PSI 判断,再从应用访问模式、缓存策略或容量入手。

在 NUMA 服务器上,系统总内存有余也可能出现节点局部压力。可只读检查:

   bashnumactl --hardware
numastat
numastat -p 18472

PID 需要替换。跨 NUMA 节点访问、进程绑核和内存策略需要硬件拓扑与应用特性共同分析。直接调整 NUMA policy、CPU 亲和性或启动参数会改变性能和可用性,应先在同规格环境验证,再单实例灰度。

六、磁盘与文件系统:吞吐不高也可能很慢

6.1 检查空间和 inode

   bashdf -hT
df -ih
findmnt -o TARGET,SOURCE,FSTYPE,OPTIONS

示例输出:

   textFilesystem     Type  Size Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4  200G 190G  1.8G  99% /

文件系统接近满容量可能导致写入失败、数据库检查点变慢或日志无法落盘。inode 用尽时,即使还有字节空间也不能创建新文件。此时不要直接执行大范围 rm 或 find ... -delete,先定位目录、确认文件归属和保留策略。

只读定位可以使用:

   bashdu -xhd1 / 2>/dev/null | sort -h
lsof +L1

du 会遍历目录,在大文件系统上可能产生明显 I/O;应避开高峰,逐层缩小范围。lsof +L1 用于找出已删除但仍被进程占用的文件,这类空间在进程关闭文件描述符前不会归还。

6.2 查看设备延迟和队列

   bashiostat -xz 1 10

示例输出:

   textDevice          r/s    w/s  r_await  w_await aqu-sz  %util
nvme0n1       120.0  980.0     1.20    42.80  18.40  99.20

关注 r_awaitw_await、队列长度和吞吐,并与设备类型、历史基线及云盘性能规格比较。%util 在传统单队列设备上较直观,但对 NVMe、多队列设备、RAID 和虚拟块设备不能简单解释为“达到 100% 就一定饱和”。单次尖峰也不足以判定根因,关键是它是否与业务延迟窗口一致。

定位进程 I/O:

   bashpidstat -d 1 10
iotop -oPa

iotop 通常需要 root 或相应 capability,并可能依赖内核任务延迟统计。生产环境运行前先确认工具开销和权限策略。

6.3 检查块设备、文件系统和远程挂载错误

   bashjournalctl -k --since "2026-07-13 14:00:00" --until "2026-07-13 15:00:00" | grep -Ei 'I/O error|timeout|reset|nvme|blk_update|ext4|xfs|nfs'
findmnt -t nfs,nfs4,cifs

示例输出:

   textkernel: nvme nvme0: I/O 817 QID 4 timeout, aborting

设备超时、控制器 reset、文件系统错误是强证据。若进程 D 状态、iostat 延迟和内核错误发生在同一时间,才形成较完整的 I/O 根因链。网络文件系统卡顿还需检查 NFS 服务端、网络链路和挂载参数。

七、网络:区分带宽、丢包、重传和连接队列

7.1 看接口与 TCP 统计

   baship -s link
sar -n DEV 1 10
sar -n TCP,ETCP 1 10
ss -s

示例输出:

   textIFACE   rxpck/s  txpck/s  rxkB/s  txkB/s  %ifutil
eth0    82400.0  79100.0  98420.0 87310.0    82.14

接口使用率要结合网卡速率确认:

   bashethtool eth0 | grep -E 'Speed|Duplex|Link detected'

示例输出:

   textSpeed: 10000Mb/s
Duplex: Full
Link detected: yes

eth0 要替换为 ip link 显示的实际接口。虚拟机、bond、bridge 和容器 veth 的观察点不同,不能只看任意一个虚拟接口。

接口 droppederrors 是否异常,应计算故障窗口增量,而不是只看开机以来累计数。TCP 重传可通过 sar -n ETCPnstat 或监控趋势观察:

   bashnstat -az | grep -E 'TcpRetransSegs|TcpExtTCPSynRetrans|TcpExtListenOverflows|TcpExtListenDrops'

示例输出:

   textTcpRetransSegs                 18342              0.0
TcpExtListenOverflows         215                0.0

同样需要做时间差值。ListenOverflows 增长说明监听队列曾溢出,但还要检查应用 accept 能力、监听 backlog、请求突发和内核上限,不能直接通过放大参数掩盖应用处理不足。

7.2 查看连接状态和热点对端

   bashss -tanp
ss -lntp
ss -tan state established | awk 'NR>1 {print $5}' | sort | uniq -c | sort -nr | head

示例输出:

   text  428 10.20.8.15:3306
  196 10.20.9.21:443

这只能说明当前连接数量,不代表这些对端一定异常。需要结合连接上限、请求量、响应时间、连接池等待和应用日志判断。对于短连接业务,瞬时 ss 快照可能漏掉问题,应依靠持续监控或限定时间的抓包。

抓包属于敏感操作,数据中可能包含业务载荷、Cookie、Token 和个人信息。应获得授权、限定接口、主机、端口、包长、数量和文件权限:

   bashtimeout 60 tcpdump -i eth0 -nn -s 128 -c 20000 'host 10.20.8.15 and port 3306' -w /var/tmp/db-traffic.pcap
chmod 600 /var/tmp/db-traffic.pcap

占位地址 10.20.8.15、端口 3306、接口 eth0 都必须替换为实际目标。执行前确认 /var/tmp 空间,采集后按安全规范转移并删除。tcpdump 只提供网络证据,不能替代数据库慢查询、服务端延迟等应用层证据。

八、应用和依赖:系统没满不代表服务没堵

如果 CPU、内存、磁盘和网络都没有与故障时间对应的异常,应继续检查应用内部排队。常见信号包括:

  • 工作线程池 active 达到上限,队列持续增长;
  • 数据库连接池等待时间和超时数上升;
  • 下游 HTTP、RPC、DNS、缓存或数据库延迟上升;
  • GC 暂停、锁竞争或事件循环阻塞;
  • 请求量没有明显增加,但某类接口的错误率和 P99 延迟升高;
  • 日志同步写、审计、备份或定时任务与故障窗口重合。

先确认服务状态和近期日志:

   bashsystemctl 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

example.service 和时间必须替换为实际值。日志量很大时,先缩小时间,避免 journalctl 本身造成额外磁盘读取。

示例输出:

   textJul 13 14:27:18 host app[18472]: upstream request timeout after 3000 ms, target=10.20.9.21:443

这条日志说明应用等待某个上游超时,是方向性证据。下一步应核对同一时间的上游服务指标、链路追踪、DNS 和网络重传。若只重启本机应用,可能暂时清空队列,却不会修复真正的上游故障。

应用排查还要把“并发上限”和“处理能力”分开。线程池、协程池、数据库连接池、HTTP 客户端连接池、消息消费并发都可能形成最窄的队列。一个连接池只有 20 个连接时,即使服务器有 64 核,超过 20 个同时访问数据库的请求仍会排队。应检查池的当前使用数、等待数、等待时长、超时数和创建失败数,并和请求量、数据库活动连接对应。

不能看到池满就直接放大。扩大数据库连接池可能把排队从应用转移到数据库,让数据库 CPU、锁和内存更差;扩大工作线程也可能增加上下文切换。合理做法是先确认单请求耗时为何变长,再计算服务目标下需要的并发量,单实例灰度调整,并同时观察本服务与下游。

队列类系统还要检查“进入速率”和“处理速率”。积压增长可能是消费端变慢,也可能是生产突发。只看当前积压量无法区分;需要对比同一窗口的生产速率、消费速率、最老消息年龄、失败重试和死信数量。盲目增加消费者会放大对数据库或第三方接口的压力。

定时任务、备份和发布也应纳入时间线。systemd timer、cron、CI/CD、配置下发、漏洞扫描、日志压缩、数据库备份都可能在固定时刻启动。检查记录时,应确认任务真正开始和结束的时间、运行主机、并发、读写目录与资源限制。不能因为任务名称里有“backup”就认定它是根因,仍要有 pidstat、I/O、CPU 或应用延迟的对应证据。

如果应用自身暴露健康检查,要确认它检查的是存活、就绪还是依赖完整性。一个只返回固定字符串的 /health 在连接池耗尽时仍可能是 200。负载均衡摘流应使用能反映接单能力的 readiness 信号,但健康检查也不能执行昂贵查询,否则探测本身会在故障时加压。

若业务运行在 Kubernetes 中,所有命令必须明确 namespace:

   bashkubectl -n <namespace> get pod -o wide
kubectl -n <namespace> top pod
kubectl -n <namespace> logs <pod-name> --since=30m --timestamps
kubectl -n <namespace> describe pod <pod-name>

<namespace> 和 <pod-name> 是占位符,分别替换为业务命名空间和 Pod 名称。kubectl top 依赖 Metrics Server,显示的是资源使用快照;容器是否 OOMKilled、是否被驱逐、探针是否失败,应结合 describe 事件、容器上次退出原因和监控判断。不要在排查阶段直接执行删除 Pod、缩扩容或节点驱逐。

九、故障已经恢复:用历史数据还原现场

很多卡顿只持续几分钟,工程师登录时 CPU 和磁盘已经恢复。此时不能因为 top 正常就关闭告警,应使用历史监控、sar 数据和日志还原故障窗口。

如果 sysstat 服务已经在事故前启用,可以查看当天的历史文件。不同发行版的目录可能是 /var/log/sa/ 或 /var/log/sysstat/,文件名也可能不同,先查看实际目录:

   bashls -lh /var/log/sa /var/log/sysstat 2>/dev/null

假设确认历史文件为 /var/log/sa/sa13,可按时间读取:

   bashsar -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

文件路径和时间范围必须替换为实际值。sar 只能回看采集器已经记录的字段,事故后才安装 sysstat 无法补出过去数据。采样间隔如果是 10 分钟,也可能错过 30 秒尖峰;分析时要写明分辨率,不能把缺少样本解释成没有异常。

示例输出:

   text14:25:01        runq-sz  plist-sz   ldavg-1   ldavg-5  blocked
14:25:01           18       612      16.42      8.31        7

runq-sz、负载和 blocked 同时升高只是历史线索,还需与设备延迟、进程日志和作业记录对应。传统 sar 通常不保存“具体哪个进程”这一层信息,因此事故前配置进程级监控、应用指标和集中日志很重要。

系统日志要围绕故障窗口查,而不是对整个大文件做无差别搜索:

   bashjournalctl --since "2026-07-13 14:20:00" --until "2026-07-13 14:35:00" --priority=warning..alert --no-pager
journalctl -k --since "2026-07-13 14:20:00" --until "2026-07-13 14:35:00" --no-pager

时间必须替换。还要确认系统时间是否发生跳变:

   bashtimedatectl status
journalctl -u chronyd -u systemd-timesyncd --since "2026-07-13 14:00:00" --until "2026-07-13 15:00:00" --no-pager

NTP 校时、时区错误或虚拟机时钟漂移会让不同系统的日志无法对齐,甚至导致证书、Token 和定时任务异常。发现时间偏差时不要直接在生产机执行大幅手工改时;时间回拨会影响数据库、分布式租约、日志排序和监控。应按时间同步方案评估影响、灰度调整并验证。

十、资源不高但请求卡住:检查限制、DNS 和文件描述符

10.1 文件描述符和进程限制

服务可能因为文件描述符耗尽而无法接受新连接或打开日志,此时整机 CPU、内存都不一定高。先定位主进程 PID,再检查:

   bashSERVICE_PID=$(systemctl show -p MainPID --value example.service)
printf 'service_pid=%s\n' "$SERVICE_PID"
cat "/proc/$SERVICE_PID/limits"
find "/proc/$SERVICE_PID/fd" -maxdepth 1 -type l | wc -l

example.service 必须替换为实际 systemd 单元。若 MainPID 为 0,说明单元可能未运行或类型特殊,应停止使用该 PID。不要把 PID 0 拼入 /proc 后继续分析。

示例输出:

   textservice_pid=18472
Max open files            65535                65535                files
18420

18420 个描述符是否异常取决于服务基线、连接数和上限。继续分类描述符目标:

   bashlsof -nP -p "$SERVICE_PID" | awk '{print $5}' | sort | uniq -c | sort -nr
ss -lntp

同时检查应用日志中是否有 Too many open files。不能只把 LimitNOFILE 调大;如果描述符持续增长,可能存在连接或文件泄漏。修改 systemd 的 LimitNOFILE 需要 daemon-reload 和服务重启,应按配置变更流程备份、单实例灰度、验证并准备回滚。

10.2 端口、连接跟踪和临时端口

大量出站短连接可能耗尽临时端口,防火墙或 NAT 节点的连接跟踪表也可能满。只读检查:

   bashsysctl net.ipv4.ip_local_port_range
ss -tan state time-wait | wc -l
ss -tan state syn-sent | head -n 20
cat /proc/sys/net/netfilter/nf_conntrack_count 2>/dev/null
cat /proc/sys/net/netfilter/nf_conntrack_max 2>/dev/null

示例输出:

   textnet.ipv4.ip_local_port_range = 32768 60999

TIME_WAIT 多不自动等于故障,它是 TCP 正常状态。要形成证据,需要看到连接创建失败、端口分配错误、conntrack 接近上限或丢弃计数增长,并与请求超时一致。不要盲目开启过时的 TCP 参数或缩短协议计时器;不同内核版本的参数可能不存在或语义不同。

10.3 DNS 和 TLS 延迟

服务调用域名时,DNS 故障可能表现为线程阻塞和接口超时。先确认系统解析路径:

   bashgetent hosts api.example.internal
resolvectl status 2>/dev/null
cat /etc/resolv.conf

api.example.internal 是占位域名,替换为实际依赖。getent 遵循系统 Name Service Switch 配置,比只运行 dig 更接近多数应用的系统解析行为。但 Java、Go 或带自定义 DNS 客户端的应用可能有自己的缓存和解析逻辑,还需查看运行时配置。

可在获得授权后做有限次计时:

   bashfor i in 1 2 3; do /usr/bin/time -f '%e seconds' getent hosts api.example.internal >/dev/null; done

示例输出:

   text0.01 seconds
2.00 seconds
2.00 seconds

示例只说明后两次解析耗时较长,不能凭三次样本确认 DNS 根因。应继续查看解析器日志、超时与重试配置、DNS 服务端指标和网络路径。

HTTPS 或 TLS 调用变慢时,可用 curl 输出分阶段耗时:

   bashcurl --output /dev/null --silent --show-error --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

地址要替换为已授权的实际端点。结果分别反映 DNS、TCP 连接、TLS、首字节和总时间,但单次客户端测量会受缓存和网络位置影响,必须多点、有限次验证,并和服务端数据结合。

十一、虚拟机与容器:看到的是宿主还是限额

在虚拟机中,vmstat 或 mpstat 的 steal 时间持续升高,可能说明宿主机 CPU 超分或调度争用。云主机的磁盘和网络还受实例规格、突发积分、IOPS、吞吐上限约束。操作系统只看到虚拟设备,需要同时检查云平台指标和配额事件。

容器内看到的 CPU 数可能与配额不一致。例如进程能看到宿主机 64 个逻辑 CPU,但 cgroup 只允许 2 核配额。cgroup v2 可检查:

   bashcat /sys/fs/cgroup/cpu.max
cat /sys/fs/cgroup/cpu.stat
cat /sys/fs/cgroup/memory.events
cat /sys/fs/cgroup/io.stat

示例输出:

   text200000 100000
usage_usec 821734201
nr_throttled 18421
throttled_usec 92400120

cpu.max 的 200000 100000 表示每 100000 微秒周期最多使用 200000 微秒 CPU 时间,即约 2 个 CPU 的配额。nr_throttled 和 throttled_usec 是累计值,要比较故障窗口增量。cgroup v1 使用 cpu.cfs_quota_uscpu.cfs_period_us 等不同文件,路径也取决于容器层级。

CPU throttling 频繁增长、应用延迟同时升高且节点仍有空闲时,说明容器限额可能是瓶颈。但修复不能直接把所有 limit 删除:需要评估节点容量、QoS、邻居影响和成本,先对单个实例调整,观察业务和节点指标,再决定扩容或优化应用。

十二、用证据链确认根因

根因不是“看到一个高值”,而是至少满足时间一致、对象一致和机制一致。

例如要判断“批处理写盘导致接口卡顿”,证据链可以是:

  1. 接口 P99 在 14:27 开始升高,14:34 恢复;
  2. 同一窗口块设备 w_await 和队列长度明显高于历史基线;
  3. pidstat -d 显示批处理进程是主要写入者;
  4. 作业平台记录表明该批处理在 14:27 启动、14:34 结束;
  5. 应用线程在该窗口等待文件或数据库 I/O,而不是 CPU 计算;
  6. 停止后续单个灰度作业时,磁盘延迟和接口延迟同步恢复。

下面几种结论都不够严谨:

  • “load 20,所以 CPU 有问题”:负载可能来自 D 状态任务;
  • “内存用了 95%,所以内存泄漏”:页缓存也计入已用内存;
  • “磁盘 util 100%,所以磁盘坏了”:要结合设备模型、延迟、队列和错误;
  • “重启后好了,所以是服务问题”:重启同时清空了连接、缓存和队列;
  • “日志出现 timeout,所以网络有问题”:也可能是服务端处理慢或连接池耗尽。

十三、修复:先止损,再处理根因

修复动作要与证据对应。常见低风险止损包括暂停可确认的非核心批任务、在流量入口摘除单个异常实例、降低后台并发、关闭刚启用的功能开关。任何动作都要先确认控制面正常,避免同时操作所有实例。

如果必须重启服务,应按高风险操作处理:

13.1 影响范围

确认该实例承载的请求、长连接、未完成任务、本地状态和主从角色。单实例服务重启可能直接中断全部业务;有状态服务还可能引发选主、恢复或数据一致性风险。

13.2 执行前检查和备份

  • 保存故障窗口的监控、日志、线程栈、进程和网络快照;
  • 确认负载均衡器中还有健康冗余实例;
  • 检查服务启动命令、配置、依赖、磁盘空间和最近一次成功启动时间;
  • 有本地状态时,按应用官方方式完成备份或一致性快照;
  • 明确操作人、观察人、回滚条件和最大等待时间。

13.3 灰度或 dry-run

先从入口摘除一个实例,等待连接排空,再仅重启该实例。systemctl restart 没有通用 dry-run;可以先用以下命令验证单元和配置,但它们不能保证应用一定启动成功:

   bashsystemctl cat example.service
systemd-analyze verify /etc/systemd/system/example.service

如果应用提供 nginx -thaproxy -c -f 等专用配置检查,应优先执行对应检查。不要把不同软件的检查参数混用。

13.4 执行和验证

   bashsystemctl restart example.service
systemctl is-active example.service
systemctl status example.service --no-pager

example.service 替换为实际服务。重启后验证进程、监听端口、健康检查、真实请求、错误率、延迟和资源指标,观察至少覆盖一个典型业务周期。仅看到 active (running) 不等于业务可用。

13.5 回滚

若新进程无法启动或指标恶化,回滚到已验证的程序包和配置版本;恢复流量前再次做健康检查。若重启只是临时止损,应创建后续问题单继续分析根因,不能把“重启恢复”写成根因结论。

修改内核参数同样是高风险操作。不要在故障现场直接执行 sysctl -w 放大队列、关闭交换或清理缓存。参数变更必须说明适用内核版本,先备份 /etc/sysctl.conf 及 /etc/sysctl.d/ 中相关文件,在单机灰度,记录旧值,并用业务指标验证。回滚时恢复旧值并通过同样的生效方式加载。

十四、验证:证明问题真的解决了

修复后至少完成四层验证:

  1. 系统层:负载、CPU、内存压力、交换、磁盘延迟、丢包和重传回到基线;
  2. 进程层:异常进程或线程恢复,队列不再增长,文件描述符和连接数稳定;
  3. 应用层:吞吐恢复,错误率下降,P50、P95、P99 延迟均符合目标;
  4. 用户层:从真实入口发起受控请求,确认关键链路可用。

验证命令应使用只读方式。例如:

   bashcurl --fail --silent --show-error --max-time 5 https://service.example.com/health

https://service.example.com/health 必须替换为已授权的实际健康检查地址。健康接口可能只验证进程存活,因此还要补充真实业务探测;对会写数据的接口,应使用专用测试账号和可清理的测试数据。

不要只对比修复前后的一个瞬时值。应选择同一流量级别、相近业务周期和相同指标口径。如果修复后请求量也下降了,延迟下降不能单独证明优化有效。

十五、回滚与二次故障防护

每个修复动作都要预先定义回滚触发条件,例如:

  • 灰度实例 5 分钟错误率高于变更前;
  • P99 超过服务目标并持续两个采集周期;
  • 出现新的 OOM、磁盘错误或进程崩溃;
  • 下游连接数或重试量持续放大。

回滚不是“再重启一次”。配置变更要恢复原文件和原参数,版本变更要恢复原构建产物,流量变更要恢复原权重,任务限流要恢复原并发。回滚后仍需重复系统层、应用层和用户层验证。

若服务器已出现块设备硬件错误、文件系统只读、持续 I/O timeout 或内核异常,不要继续在原机上反复尝试。优先隔离流量、保护数据副本并按硬件或云平台流程迁移。涉及数据库主从切换、数据迁移、集群扩缩容时,需要单独的变更方案、备份、演练、验证和回退路径,不能把它们当成普通排查命令。

十六、复盘:把一次卡顿变成可观测能力

复盘应记录事实时间线,而不是只写“服务器资源不足”。建议至少包含:

  • 发现方式:谁在什么时间通过哪个告警发现;
  • 影响范围:服务、实例、接口、用户和持续时间;
  • 证据链:监控截图、命令输出、日志、链路和变更记录;
  • 直接原因与促成因素:例如磁盘排队是直接原因,批任务无限并发和缺少延迟告警是促成因素;
  • 止损、修复及每一步验证结果;
  • 为什么现有监控、容量规划或发布流程没有提前发现;
  • 明确负责人、截止时间和可验收的改进项。

值得补齐的系统监控通常包括 CPU 饱和度、运行队列、内存 available、PSI、活跃交换、磁盘延迟、文件系统空间和 inode、网络丢包与 TCP 重传。应用侧还要有请求量、错误率、延迟分位数、线程池、连接池、消息积压和下游依赖指标。Prometheus 指标名称和标签以实际 exporter 暴露的指标为准,告警阈值应从历史基线和服务目标推导,不应照抄其他系统。

最后固化一份现场采集脚本时,应默认只读、有限时长、限制输出大小,避免收集密钥和完整业务载荷,并在测试环境验证兼容性。自动采集可以缩短定位时间,但不能替代工程师对时间线、指标口径和因果关系的判断。

十七、一份可执行的排查顺序

实际值班时可以按下面的顺序推进:

  1. 明确故障窗口、影响范围和最近变更;
  2. 保存监控,记录 dateuptime、重启和登录信息;
  3. 用 vmstat 判断 CPU 排队、I/O 等待、交换和虚拟化争用方向;
  4. 用 mpstatpidstatps 定位 CPU 进程和线程;
  5. 用 free/proc/meminfo、PSI、OOM 日志和 cgroup 指标检查内存;
  6. 用 dfiostatpidstat -d 和内核日志检查空间、inode、延迟和设备错误;
  7. 用 ip -s linksarnstatss 检查丢包、重传、队列和连接;
  8. 检查应用线程池、连接池、GC、日志、下游依赖和近期任务;
  9. 建立时间、对象和机制一致的证据链,再确定根因;
  10. 单实例灰度止损或修复,持续验证,满足回滚条件立即回退;
  11. 完成复盘,把缺失的监控、限流、容量和变更保护补上。

这套顺序的核心不是把所有命令执行一遍,而是每一步都回答一个问题:卡在哪里、谁在等待、为什么等待、证据是否与故障窗口一致。只有这样,处理结果才不会停留在“重启以后暂时好了”。

一台服务器突然卡顿,运维排查步骤是什么?插图

本文链接:https://www.yunweipai.com/archives/49368

网友评论comments

发表回复

您的电子邮箱地址不会被公开。

暂无评论

Copyright © 2012-2022 YUNWEIPAI.COM - 运维派 京ICP备16064699号-6
扫二维码
扫二维码
返回顶部