CPU 飙高 99% 时,先不要重启,更不能只凭一眼 top 结束进程。它可能是用户态计算、内核态系统调用、iowait、软中断、虚拟机 steal,或者容器 CPU 配额节流。本文围绕六个主命令——uptime、top、ps、pidstat、mpstat、sar——建立排障链路;再用线程、cgroup、日志和运行时抓证据。、<服务名>、<命名空间>、<日志目录>均为占位符。
1. uptime:负载必须相对核数看。 load average 是可运行或不可中断任务的平均数,不等于 CPU 百分比。
bash
uptime nproc getconf _NPROCESSORS_ONLN 4 核机器短期 load 16 表示队列明显堆积;64 核机器同样的数字未必异常。
2. top:拆开 CPU 构成。
bash
top -b -n 1 -w 200 | head -n 40
us 高偏向计算,sy 高偏向系统调用,wa 高先查 I/O,st 高先查虚拟化争抢。
3. ps:保存按 CPU 排序的进程快照。
bash
ps -eo pid,ppid,user,stat,ni,%cpu,%mem,etime,comm,args --sort=-%cpu | head -n 30
D 状态是不可中断睡眠;若很多任务是 D,不能把高 load 直接归因为 CPU。
4. pidstat:连续十秒而不是看瞬间。 该命令来自 sysstat,缺失时不要在事故现场随意安装软件包。
bash
pidstat -u -r -d -h 1 10
-u 看 CPU,-r 看缺页,-d 看 I/O;持续热点和短时任务会呈现不同曲线。
5. mpstat:发现单核打满。
bash
mpstat -P ALL 1 5
少数核心 %soft、%irq 高时优先查网络和中断;不要先扩容应用。
6. sar:回看尖峰发生时间。
bash
sar -u ALL -f /var/log/sa/sa$(date +%d) 历史文件路径随发行版变化。它回答“何时、持续多久、CPU 构成”,不能直接指出业务代码。
7. 读取原始计数。
bash
grep -E '^(cpu|procs_running|procs_blocked)' /proc/stat head -n 12 /proc/stat CPU 使用率要比较两次计数差值;单次 jiffies 是自启动累计。
8. 看运行队列和阻塞任务。
bash
vmstat 1 10
r 高说明可运行任务排队,b 持续非零说明阻塞。配合 us/sy/wa 才能归因。
9. 排除内存回收和 swap。
bash
free -h grep -E 'MemAvailable|SwapTotal|SwapFree|Dirty|Writeback' /proc/meminfo
重点看 MemAvailable。swap 已用不必然故障,但持续换入换出会推高延迟与系统态开销。
10. 观察上下文切换。
bash
vmstat -w 1 10 grep -E '^(ctxt|intr)' /proc/stat
高 cs 常见于线程过多、锁竞争或短连接风暴;它只是线索,需继续定位到进程。
11. 检查软中断与 IRQ。
bash
cat /proc/softirqs cat /proc/interrupts | head -n 30
NET_RX 增长只有与单核 %soft、丢包和延迟同时出现时,才支持软中断根因方向。
12. 排查虚拟机 steal。
bash
mpstat 1 5 | awk '/all/ {print}' systemd-detect-virt || true
持续 %steal 高说明 vCPU 没获得调度。应保存监控证据交由云或虚拟化平台排查宿主机。
13. 核对块设备延迟。
bash
iostat -xz 1 5 lsblk -o NAME,TYPE,SIZE,MOUNTPOINTS
await、队列与 %util 必须结合应用 I/O 模式判断,不能只看一个“100%”。
14. 检查 OOM 与内核异常。
bash
journalctl -k --since '30 minutes ago' --no-pager | rg -i 'oom|killed process|error' dmesg -T | tail -n 100 内核日志是 OOM、驱动错误等根因的重要证据。采集命令行时注意脱敏。
15. 先确认热点 PID 身份。
bash
PID= ps -p "${PID}" -o pid,ppid,user,stat,ni,psr,%cpu,%mem,etime,cmd readlink -f "/proc/${PID}/exe" 关键动作前重新确认 PID,因为进程可能退出并被复用。
16. 找到热点线程。
bash
PID= 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. 记录线程级变化。
bash
PID= pidstat -u -w -t -p "${PID}" 1 15 高 CPU 但切换少常见于计算或循环;高切换则继续查锁、I/O 与线程模型。
18. 检查进程资源状态。
bash
PID= grep -E 'State|Threads|VmRSS|voluntary_ctxt_switches|nonvoluntary_ctxt_switches' /proc/${PID}/status ls /proc/${PID}/fd | wc -l fd 接近上限可能引发连接失败和重试风暴,应再核对实际限制和泄漏来源。
19. 查看连接类别。
bash
PID= sudo lsof -nP -p "${PID}" | head -n 100 sudo ss -ntp | rg "pid=${PID}," 连接清单可能包含内部地址,应限制访问。大量短连接要与入口请求量、连接池配置一起判断。
20. 看进程所在 cgroup。
bash
PID= cat /proc/${PID}/cgroup cat /sys/fs/cgroup/cpu.max 2>/dev/null || true
cgroup v1/v2 文件不同,需沿 /proc/ 中的实际路径查限额。
21. 验证配额节流。
bash
PID= 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。
bash
docker ps --no-trunc docker stats --no-stream <容器名> docker inspect --format '{{.State.Pid}} {{.HostConfig.NanoCpus}} {{.HostConfig.CpuQuota}}' <容器名> 容器快照须与宿主 pidstat 对照。运行时命令需要授权,不能把容器 usage 等同于无节流。
23. Kubernetes 必须检查 request 与 limit。 所有 kubectl 命令指定 namespace。
bash
kubectl -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:抓线程栈。
bash
PID= jcmd "${PID}" Thread.print > /var/tmp/jstack-${PID}.txt grep -n 'nid=0x' /var/tmp/jstack-${PID}.txt | head -n 30
先将热点 TID 转十六进制,再找相同 nid。jcmd 可能影响 JVM,应遵循应用运行手册。
25. Go:用受控 pprof 采样。
bash
curl -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。
bash
PID= sudo perf top -p "${PID}" perf 受内核权限、符号和安全限制影响。不要为了临时诊断降低系统安全设置。
27. perf record 留下可复查报告。
bash
PID= sudo perf record -F 99 -p "${PID}" -g -- sleep 30 sudo perf report --stdio | head -n 80 99Hz、30 秒只是保守示例;符号缺失时不能把 unknown 解释为业务函数。
28. strace 仅用于系统调用线索。
bash
PID= sudo timeout 10 strace -ff -tt -T -p "${PID}" -o /var/tmp/strace-${PID} strace 会扰动高频系统调用进程,不能长期挂在高流量服务;它不适合分析纯计算热点。
29. 对齐服务日志与 CPU 时间窗。
bash
systemctl status <服务名> --no-pager journalctl -u <服务名> --since '15 minutes ago' --no-pager | tail -n 200 发布、OOM、连接失败和重试都是候选根因,只有与资源曲线、线程或调用栈吻合,才能形成结论。
30. 检查整点任务与近期发布。
bash
systemctl list-timers --all crontab -l 2>/dev/null || true rg -n 'cron|systemd|deploy|release' <日志目录> 2>/dev/null | tail -n 100 整点相关性不等于因果关系,仍要确认实际 PID、执行命令和资源变化。
31. 有摘流和副本时才做优雅重载。
bash
set -euo pipefail sudo systemctl reload <服务名> systemctl is-active --quiet <服务名> curl -fsS http://127.0.0.1:<端口>/<健康路径> >/dev/null
reload 是否支持由服务决定,不是 restart 的替代品。健康检查失败时按发布回滚流程恢复或摘除实例。
32. 结束进程是最后手段。
bash
PID= 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}")" 采集包可能含内部地址和命令行参数,应限制访问。完成闭环时要同时验证业务健康、错误率和资源曲线,并把触发条件、回滚方式和监控缺口写入复盘。
同样的 99% 可能是完全不同的问题。正确做法不是按经验套结论,而是把“症状—命令输出—后续验证”连成证据链。下面的场景用于选择下一步,不可替代现场数据。
若 top 和 mpstat 显示 us 持续偏高,pidstat -t 指向同一线程,且 JVM 栈或 perf 多次采样落在相同函数,才可将方向收敛到算法、序列化、压缩、加密、正则回溯或批处理。只看到一个进程高不够:多 worker 服务可能正常地将计算分摊到多个子进程。
bash
PID= 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 或正常批次任务。
bash
PID= taskset -cp "${PID}" grep -E 'Cpus_allowed_list|Mems_allowed_list' /proc/${PID}/status 若进程被限制在少数 CPU,整体利用率不高也会排队。修改 CPU 亲和性会影响 NUMA、缓存和实时任务,排障中只读取证据,不要直接解除绑定。
sy 高常见于频繁系统调用、网络包、日志写入、锁竞争或内核工作。结论需要把进程、连接、软中断和网络错误关联起来。单独看到 sy 高,不能说明“网络太多”。
bash
ss -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 使用量和负载均衡日志对齐。连接数多本身不是事故。
bash
ip -s link show cat /proc/net/dev 接口错误、丢包和丢弃计数必须做两次采样看增量;自启动以来的累计值不能证明本次尖峰的原因。
bash
nstat -a | rg 'Tcp(ActiveOpens|PassiveOpens|RetransSegs|InSegs|OutSegs)' sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog
使用 nstat -a 是只读查询。不要使用带 -z 的清零选项,也不要看到重传就先扩大 backlog;先查应用 accept 能力、下游超时与入口流量。
iowait 高时,机器可能并没有被计算打满,但大量任务在等待磁盘、网络文件系统或块存储。此时直接增加 vCPU 常常无效,甚至会增加排队。应先确认 D 状态任务和挂载点。
bash
ps -eo pid,ppid,stat,wchan:32,comm,args | awk '$3 ~ /^D/ {print}' | head -n 50
wchan 是内核等待位置线索,不是最终根因。对 NFS、云盘、数据库数据目录还要检查服务端和存储平台指标。
bash
findmnt -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 证明。
bash
kubectl -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。
bash
PID= 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 暴露为准,以下查询只在拥有对应指标时使用。
promql
100 * (1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))) 该查询反映总体忙碌度,不能区分 user、system 与 iowait。
promql
100 * avg by (instance) (rate(node_cpu_seconds_total{mode="iowait"}[5m])) 若 iowait 升高,应触发存储与下游依赖排查,而不是自动重启应用。
promql
sum 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 权限、明确的采集目录和自动清理机制。这样真正发生尖峰时,工程师不必临时提升权限或复制未经验证的命令。所有会改变服务状态的动作,例如扩容、迁移流量、修改配额、重载服务和结束进程,都应在对应步骤中写清影响范围、前置检查、灰度方式、成功标准与回滚路径;普通的只读检查则保持轻量,避免把排障流程变成额外风险源。
最终目标不是记住更多命令,而是在压力下仍能按证据做出可回退的判断。
全部0条评论
快来发表一下你的评论吧 !