Linux服务器CPU飙高的排查命令实战指南

描述

 

CPU 飙高 99% 时,先不要重启,更不能只凭一眼 top 结束进程。它可能是用户态计算、内核态系统调用、iowait、软中断、虚拟机 steal,或者容器 CPU 配额节流。本文围绕六个主命令——uptimetoppspidstatmpstatsar——建立排障链路;再用线程、cgroup、日志和运行时抓证据。<服务名><命名空间><日志目录>均为占位符。

先判断“99%”的含义

1. uptime:负载必须相对核数看。 load average 是可运行或不可中断任务的平均数,不等于 CPU 百分比。


			   bashuptime nproc getconf _NPROCESSORS_ONLN 

4 核机器短期 load 16 表示队列明显堆积;64 核机器同样的数字未必异常。

2. top:拆开 CPU 构成。


			   bashtop -b -n 1 -w 200 | head -n 40 

us 高偏向计算,sy 高偏向系统调用,wa 高先查 I/O,st 高先查虚拟化争抢。

3. ps:保存按 CPU 排序的进程快照。


			   bashps -eo pid,ppid,user,stat,ni,%cpu,%mem,etime,comm,args --sort=-%cpu | head -n 30 

D 状态是不可中断睡眠;若很多任务是 D,不能把高 load 直接归因为 CPU。

4. pidstat:连续十秒而不是看瞬间。 该命令来自 sysstat,缺失时不要在事故现场随意安装软件包。


			   bashpidstat -u -r -d -h 1 10 

-u 看 CPU,-r 看缺页,-d 看 I/O;持续热点和短时任务会呈现不同曲线。

5. mpstat:发现单核打满。


			   bashmpstat -P ALL 1 5 

少数核心 %soft%irq 高时优先查网络和中断;不要先扩容应用。

6. sar:回看尖峰发生时间。


			   bashsar -u ALL -f /var/log/sa/sa$(date +%d) 

历史文件路径随发行版变化。它回答“何时、持续多久、CPU 构成”,不能直接指出业务代码。

排除 I/O、内存和虚拟化误判

7. 读取原始计数。


			   bashgrep -E '^(cpu|procs_running|procs_blocked)' /proc/stat head -n 12 /proc/stat 

CPU 使用率要比较两次计数差值;单次 jiffies 是自启动累计。

8. 看运行队列和阻塞任务。


			   bashvmstat 1 10 

r 高说明可运行任务排队,b 持续非零说明阻塞。配合 us/sy/wa 才能归因。

9. 排除内存回收和 swap。


			   bashfree -h grep -E 'MemAvailable|SwapTotal|SwapFree|Dirty|Writeback' /proc/meminfo 

重点看 MemAvailable。swap 已用不必然故障,但持续换入换出会推高延迟与系统态开销。

10. 观察上下文切换。


			   bashvmstat -w 1 10 grep -E '^(ctxt|intr)' /proc/stat 

高 cs 常见于线程过多、锁竞争或短连接风暴;它只是线索,需继续定位到进程。

11. 检查软中断与 IRQ。


			   bashcat /proc/softirqs cat /proc/interrupts | head -n 30 

NET_RX 增长只有与单核 %soft、丢包和延迟同时出现时,才支持软中断根因方向。

12. 排查虚拟机 steal。


			   bashmpstat 1 5 | awk '/all/ {print}' systemd-detect-virt || true 

持续 %steal 高说明 vCPU 没获得调度。应保存监控证据交由云或虚拟化平台排查宿主机。

13. 核对块设备延迟。


			   bashiostat -xz 1 5 lsblk -o NAME,TYPE,SIZE,MOUNTPOINTS 

await、队列与 %util 必须结合应用 I/O 模式判断,不能只看一个“100%”。

14. 检查 OOM 与内核异常。


			   bashjournalctl -k --since '30 minutes ago' --no-pager | rg -i 'oom|killed process|error' dmesg -T | tail -n 100 

内核日志是 OOM、驱动错误等根因的重要证据。采集命令行时注意脱敏。

进程、线程与 cgroup

15. 先确认热点 PID 身份。


			   bashPID= ps -p "${PID}" -o pid,ppid,user,stat,ni,psr,%cpu,%mem,etime,cmd readlink -f "/proc/${PID}/exe" 

关键动作前重新确认 PID,因为进程可能退出并被复用。

16. 找到热点线程。


			   bashPID= top -H -b -n 1 -p "${PID}" | head -n 30 ps -L -p "${PID}" -o pid,tid,psr,stat,%cpu,comm --sort=-%cpu | head -n 20 

TID 是后续 JVM 栈和 perf 取证的关键;psr 仅表示最近运行核心。

17. 记录线程级变化。


			   bashPID= pidstat -u -w -t -p "${PID}" 1 15 

高 CPU 但切换少常见于计算或循环;高切换则继续查锁、I/O 与线程模型。

18. 检查进程资源状态。


			   bashPID= grep -E 'State|Threads|VmRSS|voluntary_ctxt_switches|nonvoluntary_ctxt_switches' /proc/${PID}/status ls /proc/${PID}/fd | wc -l 

fd 接近上限可能引发连接失败和重试风暴,应再核对实际限制和泄漏来源。

19. 查看连接类别。


			   bashPID= sudo lsof -nP -p "${PID}" | head -n 100 sudo ss -ntp | rg "pid=${PID}," 

连接清单可能包含内部地址,应限制访问。大量短连接要与入口请求量、连接池配置一起判断。

20. 看进程所在 cgroup。


			   bashPID= cat /proc/${PID}/cgroup cat /sys/fs/cgroup/cpu.max 2>/dev/null || true 

cgroup v1/v2 文件不同,需沿 /proc//cgroup 中的实际路径查限额。

21. 验证配额节流。


			   bashPID= CG=$(awk -F: '$1=="0" {print $3}' /proc/${PID}/cgroup) cat "/sys/fs/cgroup${CG}/cpu.stat" 2>/dev/null || true 

只有 throttled_usec 持续增长才支持节流结论。v1 主机要读取对应控制器路径。

22. 容器环境先关联宿主 PID。


			   bashdocker ps --no-trunc docker stats --no-stream <容器名> docker inspect --format '{{.State.Pid}} {{.HostConfig.NanoCpus}} {{.HostConfig.CpuQuota}}' <容器名> 

容器快照须与宿主 pidstat 对照。运行时命令需要授权,不能把容器 usage 等同于无节流。

23. Kubernetes 必须检查 request 与 limit。 所有 kubectl 命令指定 namespace。


			   bashkubectl -n <命名空间> top pod  --containers kubectl -n <命名空间> describe pod  kubectl -n <命名空间> get pod  -o jsonpath='{.spec.containers[*].resources}' 

kubectl top 依赖 metrics-server;它显示使用量而非 throttling,需结合 cgroup 统计和 Pod 事件。

运行时证据与止血

24. Java:抓线程栈。


			   bashPID= jcmd "${PID}" Thread.print > /var/tmp/jstack-${PID}.txt grep -n 'nid=0x' /var/tmp/jstack-${PID}.txt | head -n 30 

先将热点 TID 转十六进制,再找相同 nidjcmd 可能影响 JVM,应遵循应用运行手册。

25. Go:用受控 pprof 采样。


			   bashcurl -fsS http://127.0.0.1:/debug/pprof/profile?seconds=30 -o /var/tmp/cpu.pprof go tool pprof -top /var/tmp/cpu.pprof 

确认端口为本机受控服务。30 秒采样有开销,生产环境应按容量和安全要求执行。

26. 原生进程:短时 perf top。


			   bashPID= sudo perf top -p "${PID}" 

perf 受内核权限、符号和安全限制影响。不要为了临时诊断降低系统安全设置。

27. perf record 留下可复查报告。


			   bashPID= sudo perf record -F 99 -p "${PID}" -g -- sleep 30 sudo perf report --stdio | head -n 80 

99Hz、30 秒只是保守示例;符号缺失时不能把 unknown 解释为业务函数。

28. strace 仅用于系统调用线索。


			   bashPID= sudo timeout 10 strace -ff -tt -T -p "${PID}" -o /var/tmp/strace-${PID} 

strace 会扰动高频系统调用进程,不能长期挂在高流量服务;它不适合分析纯计算热点。

29. 对齐服务日志与 CPU 时间窗。


			   bashsystemctl status <服务名> --no-pager journalctl -u <服务名> --since '15 minutes ago' --no-pager | tail -n 200 

发布、OOM、连接失败和重试都是候选根因,只有与资源曲线、线程或调用栈吻合,才能形成结论。

30. 检查整点任务与近期发布。


			   bashsystemctl list-timers --all crontab -l 2>/dev/null || true rg -n 'cron|systemd|deploy|release' <日志目录> 2>/dev/null | tail -n 100 

整点相关性不等于因果关系,仍要确认实际 PID、执行命令和资源变化。

31. 有摘流和副本时才做优雅重载。


			   bashset -euo pipefail sudo systemctl reload <服务名> systemctl is-active --quiet <服务名> curl -fsS http://127.0.0.1:<端口>/<健康路径> >/dev/null 

reload 是否支持由服务决定,不是 restart 的替代品。健康检查失败时按发布回滚流程恢复或摘除实例。

32. 结束进程是最后手段。


			   bashPID= ps -p "${PID}" -o pid,ppid,lstart,cmd kill -TERM "${PID}" sleep 10 ps -p "${PID}" -o pid,stat,cmd || true 

SIGTERM 允许清理资源。SIGKILL 仅在进程无法退出、数据风险已评估且获得批准时使用;操作前摘流,操作后验证错误率、队列、CPU 与数据一致性。

最后在重启、发布或止血前保存证据包。它能让“CPU 降了”变成可复现的根因与修复结论。


			   bash#!/usr/bin/env bash set -euo pipefail OUT="/var/tmp/cpu-incident-$(date +%F-%H%M%S)" mkdir -p "${OUT}" uptime > "${OUT}/uptime.txt" mpstat -P ALL 1 3 > "${OUT}/mpstat.txt" ps -eo pid,ppid,user,stat,%cpu,%mem,etime,args --sort=-%cpu > "${OUT}/ps.txt" vmstat 1 3 > "${OUT}/vmstat.txt" tar -C "$(dirname "${OUT}")" -czf "${OUT}.tgz" "$(basename "${OUT}")" 

采集包可能含内部地址和命令行参数,应限制访问。完成闭环时要同时验证业务健康、错误率和资源曲线,并把触发条件、回滚方式和监控缺口写入复盘。

用证据区分四类高 CPU 场景

同样的 99% 可能是完全不同的问题。正确做法不是按经验套结论,而是把“症状—命令输出—后续验证”连成证据链。下面的场景用于选择下一步,不可替代现场数据。

用户态计算热点

若 top 和 mpstat 显示 us 持续偏高,pidstat -t 指向同一线程,且 JVM 栈或 perf 多次采样落在相同函数,才可将方向收敛到算法、序列化、压缩、加密、正则回溯或批处理。只看到一个进程高不够:多 worker 服务可能正常地将计算分摊到多个子进程。


			   bashPID= for i in 1 2 3 4 5; do   date -Is   ps -p "${PID}" -o pid,stat,%cpu,%mem,etime,cmd   sleep 5 done 

这是连续采样脚本;正式放入巡检库时应补上日志轮转。五次都高才支持持续热点,短暂上升可能只是 JIT、GC 或正常批次任务。


			   bashPID= taskset -cp "${PID}" grep -E 'Cpus_allowed_list|Mems_allowed_list' /proc/${PID}/status 

若进程被限制在少数 CPU,整体利用率不高也会排队。修改 CPU 亲和性会影响 NUMA、缓存和实时任务,排障中只读取证据,不要直接解除绑定。

系统态与网络软中断热点

sy 高常见于频繁系统调用、网络包、日志写入、锁竞争或内核工作。结论需要把进程、连接、软中断和网络错误关联起来。单独看到 sy 高,不能说明“网络太多”。


			   bashss -tan | awk 'NR>1 {state[$1]++} END {for (s in state) print s, state[s]}' | sort ss -s 

SYN-RECV、TIME-WAIT 或 ESTAB 的增量要与请求 QPS、fd 使用量和负载均衡日志对齐。连接数多本身不是事故。


			   baship -s link show cat /proc/net/dev 

接口错误、丢包和丢弃计数必须做两次采样看增量;自启动以来的累计值不能证明本次尖峰的原因。


			   bashnstat -a | rg 'Tcp(ActiveOpens|PassiveOpens|RetransSegs|InSegs|OutSegs)' sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog 

使用 nstat -a 是只读查询。不要使用带 -z 的清零选项,也不要看到重传就先扩大 backlog;先查应用 accept 能力、下游超时与入口流量。

iowait 与不可中断任务

iowait 高时,机器可能并没有被计算打满,但大量任务在等待磁盘、网络文件系统或块存储。此时直接增加 vCPU 常常无效,甚至会增加排队。应先确认 D 状态任务和挂载点。


			   bashps -eo pid,ppid,stat,wchan:32,comm,args | awk '$3 ~ /^D/ {print}' | head -n 50 

wchan 是内核等待位置线索,不是最终根因。对 NFS、云盘、数据库数据目录还要检查服务端和存储平台指标。


			   bashfindmnt -T <挂载路径> df -hT <挂载路径> journalctl -k --since '30 minutes ago' --no-pager | rg -i 'ext4|xfs|nfs|blk|i/o error' | tail -n 100 

<挂载路径>替换为热点服务的数据目录。满盘、只读 remount 和 NFS stale handle 都可能让应用表现为超时和高 load。

容器配额节流

容器中“100% CPU”经常表示它用尽了 1 个 vCPU 的 limit,宿主机总体可能仍有余量。增加 CPU limit、扩 Pod 副本、优化代码是三种不同的风险:提高 limit 会抢节点资源,扩副本依赖会话与队列可水平扩展,优化代码需要 profile 证明。


			   bashkubectl -n <命名空间> get pod  -o wide kubectl -n <命名空间> get pod  -o jsonpath='{range .spec.containers[*]}{.name}{" "}{.resources.requests.cpu}{" "}{.resources.limits.cpu}{" "}{end}' 

该命令只读资源配置。若要改 limit,先检查 namespace 配额、节点余量、HPA 行为和灰度发布方案,不能直接填节点总 CPU。


			   bashPID= CG=$(awk -F: '$1=="0" {print $3}' /proc/${PID}/cgroup) cat "/sys/fs/cgroup${CG}/cpu.stat" sleep 30 cat "/sys/fs/cgroup${CG}/cpu.stat" 

两个样本之间 throttling 相关字段持续增加才支持限额节流结论。记录采样间隔和对应业务流量,以免把低流量时的历史计数误判为实时问题。

让下一次排障更快

主机监控应同时保留 CPU 构成、load、上下文切换、每核软中断、进程/容器 CPU、cgroup throttling、请求延迟和错误率。Prometheus 指标名称以实际 exporter 暴露为准,以下查询只在拥有对应指标时使用。


			   promql100 * (1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))) 

该查询反映总体忙碌度,不能区分 user、system 与 iowait。


			   promql100 * avg by (instance) (rate(node_cpu_seconds_total{mode="iowait"}[5m])) 

若 iowait 升高,应触发存储与下游依赖排查,而不是自动重启应用。


			   promqlsum by (namespace, pod, container) (rate(container_cpu_cfs_throttled_periods_total[5m])) / clamp_min(sum by (namespace, pod, container) (rate(container_cpu_cfs_periods_total[5m])), 1) 

这类 cAdvisor 指标的标签可能不同。节流比例必须与 CPU limit、延迟和实际负载一起分析。

排障结论至少应说明影响时间、CPU 构成、热点线程或 cgroup、相关发布/依赖事件以及验证动作。证据不足时将它记录为“待验证假设”,而不是把一次临时止血写成根因修复。

实践中最容易遗漏的是“恢复后的验证”。如果通过扩容、迁移流量、提高 CPU limit 或重启恢复服务,必须继续观察至少一个正常业务周期:请求延迟是否回落、错误率是否稳定、队列是否清空、重传和 I/O 等伴随指标是否改善。若 CPU 只是从一台实例转移到另一台,或者错误率下降但延迟显著变长,说明问题没有真正解决。将排障命令、采样时间、版本变更、容量策略和最终修复提交关联到同一复盘记录,下一次才能快速区分已知模式与新问题。

对于周期性尖峰,还应把采样窗口覆盖到尖峰前、尖峰中和尖峰后。只抓到恢复阶段,往往会得到“没有异常”的错误结论。对无法复现的偶发问题,可以保留低开销的主机指标和按阈值触发的进程快照,但必须限制采集频率、保存周期和敏感字段,避免诊断系统本身耗尽 CPU、磁盘或隐私预算。若错误率和延迟已明显影响用户,优先使用既有的摘流、扩容或降级开关;诊断动作应在业务恢复和证据完整之间取得可审计的平衡。

在日常演练中,可以为关键服务预先准备只读诊断账号、受控的 perf/jcmd 权限、明确的采集目录和自动清理机制。这样真正发生尖峰时,工程师不必临时提升权限或复制未经验证的命令。所有会改变服务状态的动作,例如扩容、迁移流量、修改配额、重载服务和结束进程,都应在对应步骤中写清影响范围、前置检查、灰度方式、成功标准与回滚路径;普通的只读检查则保持轻量,避免把排障流程变成额外风险源。

最终目标不是记住更多命令,而是在压力下仍能按证据做出可回退的判断。

 


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分