线上 CPU 飙高最怕两件事:一是盯着 top 看了半小时,最后还是不知道是谁打满了核;二是误把负载高当成 CPU 高,处理动作做反了,越处理越抖。生产环境里,CPU 问题通常不是单一指标异常,而是一条完整链路:业务请求放大、线程模型失控、内核软中断堆积、磁盘或网络抖动把 CPU 拖进系统态,最后体现在告警平台上只剩一行“CPU usage > 90%”。
我自己排这类故障时,不会一上来就改参数,也不会先重启服务。第一步永远是把现场固定住,先判断是整机 CPU 打满、单进程热点、线程级死循环、内核态消耗,还是虚拟化层 steal time。判断对了,后面的命令基本就是顺着证据走。
一、概述
1.1 背景介绍
Linux 服务器 CPU 飙高,常见表现有三类:
监控中 CPU usage 连续 5 分钟超过 85%,业务 RT 明显抬升
load average 飙升,但应用日志没有大量报错,看起来像“无声故障”
单台机器异常,重启后短时间恢复,过一阵又复发
这类问题本质上是在回答四个问题:
CPU 时间消耗在用户态还是内核态
是哪个进程、哪个线程、哪段调用栈在吃 CPU
是业务代码导致,还是网络、中断、磁盘、容器配额把 CPU 顶上去
现场止血之后,怎么避免再次发生
1.2 技术特点
分层排查:先看整机,再看进程,再看线程,再看调用栈,避免在错误层级浪费时间
证据驱动:每一步都保留命令输出、时间点和 PID,复盘时能回放故障过程
兼容生产环境:优先使用低侵入命令,只有在证据不足时才上 perf、strace 这类更重的工具
1.3 适用场景
电商、支付、营销活动高峰期间,某台业务机 CPU 持续 90% 以上
Java、Go、Python 服务 RT 抬升,容器副本正常但单 Pod 热点明显
Nginx、网关、日志采集节点出现高系统态 CPU,怀疑是软中断或连接风暴
1.4 环境要求
| 组件 | 版本要求 | 说明 |
|---|---|---|
| 操作系统 | CentOS 7/8、Rocky Linux 8/9、Ubuntu 20.04+ | 文中命令以主流生产发行版为准 |
| 诊断工具 | procps-ng 、sysstat、perf、strace、lsof | sysstat 提供 sar/mpstat/pidstat,perf 用于热点采样 |
| 硬件配置 | 4 vCPU / 8 GB 内存起 | 低于该配置时 CPU 抖动更明显,结论要结合负载模型看 |
| 监控体系 | Prometheus + Node Exporter 或等价方案 | 用于回放故障窗口和设置阈值 |
二、详细步骤
2.1 准备工作
2.1.1 系统检查
先确认这台机器是不是真的在烧 CPU,而不是负载高、IO 卡顿或虚拟机被宿主机抢占。第一轮命令我通常固定成下面这一组:
date hostname -f uptime w top -b -n 1 | head -20 mpstat -P ALL 1 3 vmstat 1 5 sar -u 1 5 sar -q 1 5 free -h df -h dmesg -T | tail -50
这组命令重点看下面几个指标:
%usr:用户态 CPU,高了通常先看应用进程或线程热点
%sys:系统态 CPU,高了先看网络、磁盘、中断、内核路径
%iowait:高了不一定是 CPU 问题,很多人会误判
%steal:云主机上很关键,高了说明宿主机在抢 CPU
run queue:vmstat 中的 r 持续大于 CPU 核数,说明调度压力明显
load average:只说明等待队列变长,不等于 CPU 一定打满
如果现场还没装诊断工具,先补齐:
# Debian / Ubuntu sudo apt update sudo apt install -y sysstat linux-tools-common linux-tools-generic strace lsof iotop dstat tcpdump linux-cpupower # RHEL / CentOS / Rocky / AlmaLinux sudo yum install -y sysstat perf strace lsof iotop dstat tcpdump kernel-tools
生产环境里建议把 sysstat 常驻打开,不要等出事了才发现机器上没有 sar 历史数据。
2.1.2 固定现场
CPU 问题最容易丢现场,尤其是应用被自动拉起、容器自动重建或者值班同学顺手重启服务。先把关键现场落盘:
sudo mkdir -p /var/log/cpu-hotspot/$(date +%F_%H%M%S)
SNAPSHOT_DIR=$(ls -dt /var/log/cpu-hotspot/* | head -1)
top -b -n 1 > "${SNAPSHOT_DIR}/top.txt"
ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%cpu | head -50 > "${SNAPSHOT_DIR}/ps_top_cpu.txt"
mpstat -P ALL 1 5 > "${SNAPSHOT_DIR}/mpstat.txt"
pidstat -u -r -d -h 1 5 > "${SNAPSHOT_DIR}/pidstat.txt"
vmstat 1 5 > "${SNAPSHOT_DIR}/vmstat.txt"
sar -u 1 5 > "${SNAPSHOT_DIR}/sar_u.txt"
sar -n DEV 1 5 > "${SNAPSHOT_DIR}/sar_net.txt"
sar -d 1 5 > "${SNAPSHOT_DIR}/sar_disk.txt"
dmesg -T | tail -200 > "${SNAPSHOT_DIR}/dmesg_tail.txt"
如果是容器环境,再补一组 cgroup 和 kubelet 侧信息:
kubectl top pod -A --containers | sort -k3 -hr | head -20 kubectl describe pod-n crictl stats cat /sys/fs/cgroup/cpu.max 2>/dev/null || cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us cat /sys/fs/cgroup/cpu.stat 2>/dev/null || cat /sys/fs/cgroup/cpu/cpu.stat
这里最容易踩坑的是:容器里看到 CPU 100%,但那是**单核 100%**,不是整机 100%。如果 Pod 只给了 500m,应用被节流后一样会表现成 RT 飙升。
2.2 核心排查步骤
2.2.1 先判断是整机问题还是单进程问题
先看全局,再看个体:
mpstat -P ALL 1 3 ps -eo pid,user,ni,pri,psr,stat,%cpu,%mem,etime,cmd --sort=-%cpu | head -30 pidstat -u -p ALL 1 5
判断原则我一般这么定:
所有核心都高,且多个业务进程都在跑:优先看流量、批处理、宿主机争抢、全局中断
只有 1-2 个核心高:大概率是单线程热点、锁竞争、自旋或绑核不均
整机 CPU 不高,单进程 CPU 很高:直接进线程级定位
整机 %sys 高、进程视角又不明显:转到中断、网络栈、磁盘 IO 路径
业务止血时,不要急着 kill -9。先确认是不是可以摘流:
# 例如在负载均衡层先摘掉异常节点 curl -s http://127.0.0.1:9090/healthz sudo ipvsadm -Ln sudo ss -s
如果节点还能响应,优先在网关或注册中心把实例摘掉,再做深度取证。
2.2.2 区分用户态 CPU 和系统态 CPU
这一刀必须切清楚,不然会把应用问题排到内核层,或者把中断风暴排到业务线程。
top -b -n 1 | head -10 mpstat -P ALL 1 5 sar -u ALL 1 5
经验上可以这样理解:
%usr + %nice 高:代码热点、死循环、频繁 JSON 编解码、正则、GC、压缩解压
%sys 高:系统调用密集、网络包处理、软中断、磁盘路径、连接风暴
%soft 高:网络包小包过多、连接突发、iptables/conntrack 压力
%irq 高:硬件中断、网卡队列、某些存储控制器异常
%steal 高:不是你机器的问题,先找云平台或宿主机资源争抢
如果 %sys 明显高,继续执行:
cat /proc/softirqs cat /proc/interrupts sar -n DEV,EDEV 1 5 ethtool -S eth0 | egrep 'drop|miss|error|timeout'
如果 %usr 高,直接进入进程和线程热点定位。
2.2.3 进程级定位:谁在吃 CPU
先把 top 进程抓出来,再看生命周期和启动参数:
ps -eo pid,ppid,lstart,etime,pcpu,pmem,cmd --sort=-pcpu | head -20 pidstat -u -t -p1 5 cat /proc/ /status cat /proc/ /limits lsof -p | head -50
几个实战判断点:
新拉起的进程 CPU 高:通常和新版本发布、配置变更、缓存未热有关
运行几天后的进程 CPU 高:更多见于线程泄漏、连接泄漏、任务堆积、GC 退化
只有某个工作线程高:优先抓线程栈,不要先抓完整堆 dump,太重
Java 进程排查可以直接把高 CPU 线程映射到十六进制线程号:
top -H -pprintf '0x%x ' jstack | less
Go 进程建议先看 pprof:
curl -s http://127.0.0.1:6060/debug/pprof/profile?seconds=30 -o cpu.pprof go tool pprof -top cpu.pprof
Python 进程如果 CPU 很高,先看是不是纯 Python 死循环、JSON 序列化过重,或者多进程 worker 数配大了:
top -H -pstrace -p -tt -T -f -o /tmp/python.strace -c
2.2.4 线程级定位:哪个线程、哪段栈在热
线程级定位是 CPU 问题里最有价值的一步,很多线上问题在这里就能定性。
top -H -pps -Lp -o pid,tid,psr,pcpu,stat,comm --sort=-pcpu | head -20 pidstat -t -p 1 5
常见现象和结论:
某个线程固定占满 100% 单核:典型死循环、自旋锁、空轮询
多个线程同时高:并发打满、线程池放大、热点 key 或批任务冲击
线程 CPU 高且上下文切换也高:可能是锁竞争、频繁唤醒、线程数过多
继续往下抓热点时,优先用 perf,比 strace 更适合 CPU 分析:
sudo perf top -p-g sudo perf record -F 99 -p -g -- sleep 30 sudo perf report --stdio | head -80
perf 的使用原则:
采样 15-30 秒通常够了,时间太长会把短期热点冲淡
线上优先 -F 99 或 -F 49,不要把采样频率拉太高
perf top 适合现场判断,perf record/report 适合留证据
如果内核限制了 perf_event_paranoid,临时调整前先评估权限:
sysctl kernel.perf_event_paranoid sudo sysctl -w kernel.perf_event_paranoid=1
改完记得回收,别把调试口子常驻留在生产机上。
2.2.5 系统态 CPU 高:重点盯中断、网络和磁盘
系统态高最容易出现“应用没问题,但机器已经快扛不住”的情况。排查顺序建议固定:
看网卡收发和丢包
看软中断分布
看连接状态和 backlog
看磁盘队列和 IO 等待
看内核日志有没有网卡、驱动、文件系统异常
sar -n DEV,EDEV,TCP,ETCP 1 5 ss -s ss -ant state syn-recv netstat -s | egrep -i 'listen|overflow|drop|retrans' cat /proc/softirqs cat /proc/net/softnet_stat iostat -xz 1 5 pidstat -d 1 5
几种典型模式:
NET_RX 飙高:小包洪峰、负载均衡不均、网卡队列/中断绑核不合理
ksoftirqd 高:软中断堆积,用户态进程看起来并不高,但 CPU 已经被内核吃掉
wa 高、avgqu-sz 高:不是 CPU 问题本身,根因在 IO
SYN-RECV 很多:上游连接风暴、半连接队列不足、攻击流量或健康检查异常
2.2.6 云主机、虚拟化和容器环境的特殊检查
很多“CPU 高”其实是资源被限制或者宿主机抢占。
mpstat -P ALL 1 5 sar -u ALL 1 5 grep . /sys/fs/cgroup/cpu.max 2>/dev/null grep . /sys/fs/cgroup/cpu.stat 2>/dev/null cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us 2>/dev/null cat /sys/fs/cgroup/cpu/cpu.cfs_period_us 2>/dev/null
重点关注:
%steal 持续超过 5%:宿主机资源争抢,业务再怎么调也只是缓解
nr_throttled 持续增长:容器被 CPU quota 节流
usage_usec 增长平缓但 RT 很高:可能是线程拿不到调度片
Kubernetes 场景里,再补两步:
kubectl top node kubectl top pod -A --containers | head -30 kubectl describe node| egrep -A3 'Allocated resources|Non-terminated Pods'
如果节点超卖严重,优先做资源回收、亲和性重排、Pod 驱逐,而不是只盯某个业务进程。
2.3 验证排查结论
2.3.1 先验证临时止血动作是否生效
常见止血动作有三种:摘流、限流、降并发。动作做完后至少看 10 分钟,不要看 30 秒就下结论。
watch -n 2 "uptime; echo '---'; mpstat -P ALL 1 1; echo '---'; ss -s"
止血动作有效时,通常会看到:
热点核心利用率下降到 60%-75%
业务 RT 在 3-5 分钟内回落
连接重传率和超时率同步下降
2.3.2 再验证根因是否闭环
根因验证至少要满足三件事:
# 1. 热点线程已消失或热点函数占比明显下降
sudo perf report --stdio | head -40
# 2. 关键业务接口恢复
curl -s -o /dev/null -w "%{http_code} %{time_total}
" http://127.0.0.1:8080/health
# 3. 监控指标回归
sar -u 1 3
闭环标准建议写得硬一点:
CPU 峰值恢复到过去 7 天同时间窗 P95 附近
错误率回到告警阈值以下
相同流量下未再次出现同类热点线程
三、示例代码和配置
3.1 完整配置示例
3.1.1 主配置文件
下面这份配置用于开启 sysstat 长期采样,保证故障发生后能回看 CPU、IO、网络的历史走势。线上机器不留历史指标,很多 CPU 故障最后只能靠猜。
# 文件路径:/etc/sysconfig/sysstat HISTORY=28 COMPRESSAFTER=7 SADC_OPTIONS="-S DISK -S XDISK -S INT -S IPV6" SA_DIR=/var/log/sa YESTERDAY=no
CentOS 7/8 系列还要确认定时任务开启:
# 文件路径:/usr/lib/systemd/system/sysstat-collect.timer [Unit] Description=Run system activity accounting tool every 10 minutes [Timer] OnCalendar=*:00/10 AccuracySec=1min Persistent=true [Install] WantedBy=timers.target
启用方式:
sudo systemctl daemon-reload sudo systemctl enable --now sysstat sudo systemctl enable --now sysstat-collect.timer sudo systemctl enable --now sysstat-summary.timer
3.1.2 辅助脚本
这份脚本用来在 CPU 异常时自动抓取快照,适合挂在定时任务、Prometheus Alertmanager webhook 或值班手册里。脚本目标不是“自动修复”,而是把最容易丢的现场第一时间保存下来。
#!/usr/bin/env bash
# 文件名:/usr/local/bin/cpu_hotspot_snapshot.sh
# 功能:采集 CPU 异常现场,保留进程、线程、网络、磁盘和内核证据
set -euo pipefail
BASE_DIR="/var/log/cpu-hotspot"
TS="$(date +%F_%H%M%S)"
OUT_DIR="${BASE_DIR}/${TS}"
mkdir -p "${OUT_DIR}"
echo "[INFO] snapshot dir: ${OUT_DIR}"
{
echo "===== basic ====="
date
hostname -f
uptime
uname -a
echo
echo "===== top ====="
top -b -n 1 | head -40
echo
echo "===== mpstat ====="
mpstat -P ALL 1 3
echo
echo "===== vmstat ====="
vmstat 1 5
echo
echo "===== sar cpu ====="
sar -u ALL 1 3
echo
echo "===== sar net ====="
sar -n DEV,EDEV,TCP,ETCP 1 3
echo
echo "===== iostat ====="
iostat -xz 1 3
} > "${OUT_DIR}/system.txt" 2>&1
ps -eo pid,ppid,user,psr,stat,%cpu,%mem,lstart,cmd --sort=-%cpu | head -50
> "${OUT_DIR}/top_processes.txt"
pidstat -u -r -d -t -h 1 5 > "${OUT_DIR}/pidstat.txt" 2>&1 || true
ss -s > "${OUT_DIR}/ss_summary.txt" 2>&1 || true
cat /proc/softirqs > "${OUT_DIR}/softirqs.txt" 2>&1 || true
cat /proc/interrupts > "${OUT_DIR}/interrupts.txt" 2>&1 || true
dmesg -T | tail -200 > "${OUT_DIR}/dmesg_tail.txt" 2>&1 || true
TOP_PID="$(ps -eo pid,%cpu --sort=-%cpu | awk 'NR==2 {print $1}')"
if [[ -n "${TOP_PID}" && "${TOP_PID}" =~ ^[0-9]+$ ]]; then
ps -Lp "${TOP_PID}" -o pid,tid,psr,pcpu,stat,comm --sort=-pcpu
> "${OUT_DIR}/pid_${TOP_PID}_threads.txt" 2>&1 || true
timeout 20 perf record -F 49 -p "${TOP_PID}" -g -- sleep 15
> "${OUT_DIR}/perf_record.log" 2>&1 || true
perf report --stdio > "${OUT_DIR}/perf_report.txt" 2>&1 || true
fi
find "${BASE_DIR}" -maxdepth 1 -type d -mtime +7 -exec rm -rf {} ; || true
echo "[INFO] snapshot completed"
部署建议:
sudo install -o root -g root -m 0750 cpu_hotspot_snapshot.sh /usr/local/bin/cpu_hotspot_snapshot.sh echo '*/5 * * * * root /usr/local/bin/cpu_hotspot_snapshot.sh >/dev/null 2>&1' | sudo tee /etc/cron.d/cpu-hotspot
如果机器数量多,别每 5 分钟全量抓。正确做法是只在告警触发时抓,或者给脚本加条件判断,例如最近 1 分钟 CPU 大于 85% 才采样。
3.2 实际应用案例
案例一:Java 线程死循环把单核打满
场景描述:某订单服务发布后 RT 从 30 ms 拉高到 800 ms,监控上整机 CPU 只有 45%,但 1 个核心长期 100%,业务侧以为是数据库慢,实际上根因在应用线程。
排查过程:
top -H -p 28461 ps -Lp 28461 -o pid,tid,pcpu,comm --sort=-pcpu | head printf '0x%x ' 28513 jstack 28461 | grep -A 20 6f61 sudo perf top -p 28461 -g
关键现象:
线程 28513 持续占用 99% 单核
jstack 显示线程卡在业务规则引擎的循环判断里
perf 热点集中在字符串拆分和正则匹配
实现代码:
public void calculateDiscount(Listrules) { while (true) { for (String rule : rules) { if (rule.matches(".*VIP.*")) { doSomething(rule.split(":")[1]); } } } }
这段代码在测试环境不容易暴露问题,因为规则量小、线程少。线上规则列表扩到 3 万条之后,死循环加正则匹配直接把单核打穿。
修复动作:
先在注册中心把异常实例摘流
回滚到上一版本,确认 CPU 回落
用缓存和预编译规则替换循环内的 matches
给线程执行逻辑加退出条件和超时保护
运行结果:
修复前:单核 99%,实例 P99 RT 1.2s,订单超时率 8.7% 修复后:热点线程消失,整机 CPU 下降到 31%,P99 RT 恢复到 85ms
案例二:软中断堆积导致网关节点系统态 CPU 飙升
场景描述:某 API 网关节点在晚高峰出现 %sys 70% 以上,Nginx Worker 进程 CPU 并不高,但机器整体不可用,请求连接超时明显增多。
排查过程:
top -b -n 1 | head -10 cat /proc/softirqs | column -t | egrep 'NET_RX|NET_TX' sar -n DEV,EDEV,TCP,ETCP 1 5 ss -s ethtool -S eth0 | egrep 'drop|miss|queue'
关键现象:
NET_RX 软中断在 CPU0、CPU1 上远高于其他核心
ksoftirqd/0、ksoftirqd/1 持续占用 CPU
网卡多队列开了,但中断绑核不均,流量基本都压在前两个核心
处理脚本:
#!/usr/bin/env bash
# 文件名:rebalance_irq.sh
set -euo pipefail
SERVICE="irqbalance"
NIC="eth0"
sudo systemctl enable --now "${SERVICE}"
sudo ethtool -L "${NIC}" combined 8
for irq in $(grep "${NIC}" /proc/interrupts | awk -F: '{print $1}'); do
echo 0f | sudo tee "/proc/irq/${irq}/smp_affinity" >/dev/null
done
运行结果:
调整前:sys 72%,NET_RX 在 CPU0/1 明显倾斜,请求超时率 5.4% 调整后:sys 下降到 28%,各核心软中断分布趋于均衡,请求超时率降到 0.3%
这个案例里,如果只盯业务进程,基本看不出问题。CPU 真正被打满的是内核网络收包路径。
四、最佳实践和注意事项
4.1 最佳实践
4.1.1 性能优化
优化点一:常驻历史采样,别等出事再装工具
sar 历史数据对 CPU 问题非常关键,尤其是那些“现在已经恢复,但昨晚 21:10 出过抖动”的故障。生产环境建议至少保留 28 天数据。
sudo sed -i 's/^HISTORY=.*/HISTORY=28/' /etc/sysconfig/sysstat sudo systemctl enable --now sysstat sudo systemctl restart sysstat
优化点二:给热点业务做合理的线程上限
线程数不是越大越好。CPU 密集型业务线程池开太大,只会把上下文切换和锁竞争一起抬高。我们团队的经验值是:CPU 密集型服务先按 CPU 核数 * 1~2 起步,再压测修正。
nproc pidstat -w -p1 5
如果 cswch/s 和 nvcswch/s 持续很高,同时 CPU 利用率却上不去,线程数大概率已经过量。
优化点三:网络型节点优先处理软中断绑核
网关、L4/L7 代理、日志采集节点,CPU 打满很多时候不是应用逻辑,而是收包路径失衡。实测下来,双 10G 网卡机器如果中断集中在 1-2 个核上,业务峰值流量一来就会明显抖。
irqbalance --debug 2>/dev/null | head cat /proc/interrupts sudo systemctl enable --now irqbalance
4.1.2 安全加固
安全措施一:限制调试工具使用权限
perf、strace、gdb 都是排障利器,但也能带来信息泄露风险。生产环境建议只给运维值班组和 SRE 小范围授权。
getfacl /usr/bin/perf sudo chmod 750 /usr/bin/perf
安全措施二:对 CPU 调优动作做审计
改 sysctl、改 IRQ 绑核、改容器 quota 都要进变更记录。没有审计的“临时优化”,一个月后基本没人记得动过什么。
sudo auditctl -w /etc/sysctl.conf -p wa -k sysctl_change sudo auditctl -w /etc/security/limits.conf -p wa -k limits_change
安全措施三:对采样脚本做最小权限执行
快照脚本尽量只读,不要顺手把“自动 kill 高 CPU 进程”塞进去。自动止血可以做,但必须经过白名单和二次确认,否则误伤概率很高。
4.1.3 高可用配置
HA 方案一:业务实例接入负载均衡健康检查,出现 CPU 热点时支持秒级摘流
HA 方案二:关键服务采用多可用区部署,避免单台热点机器拖垮整个业务面
备份策略:所有 CPU 调优相关配置在修改前先做版本化备份,尤其是 sysctl、网卡参数、服务线程配置
建议把下面这些动作标准化:
sudo cp -a /etc/sysctl.conf /etc/sysctl.conf.$(date +%F_%H%M%S).bak sudo sysctl -a > /var/backups/sysctl-all.$(date +%F_%H%M%S).txt tar czf /var/backups/service-config-$(date +%F_%H%M%S).tar.gz /etc/systemd/system /etc/nginx /etc/myapp 2>/dev/null
4.2 注意事项
4.2.1 配置注意事项
警告:CPU 问题没有确认根因前,不要直接重启、扩容或改线程池。动作虽然能短时压住症状,但会把现场打散,后面再复盘就只能靠猜。
注意事项一:load average 高不等于 CPU 高。很多人看到负载 30 就判定 CPU 打满,最后发现是磁盘卡住
注意事项二:容器 CPU 100% 先换算成限额视角再判断,500m 配额下 100% 和整机 100% 不是一个量级
注意事项三:strace -f -p 对高并发服务有侵入,优先用 perf、pidstat、线程栈做轻量定位
4.2.2 常见错误
| 错误现象 | 原因分析 | 解决方案 |
|---|---|---|
| 看到 top 某进程 200% 就以为异常 | 多核环境下进程可以合法使用多个核 | 结合核数和线程数判断,查看线程级 CPU 分布 |
| 只看当前时刻,没有历史趋势 | 瞬时 CPU 回落后,现场已经丢失 | 常驻 sysstat、保留 Prometheus 历史数据、告警触发自动快照 |
| 只抓进程 CPU,不看 %sys/%soft/%steal | 根因可能在内核、中断或虚拟化层 | 先分层判断,再做针对性定位 |
4.2.3 兼容性问题
版本兼容:perf 最好和当前运行内核版本匹配,不匹配时符号解析会不准
平台兼容:在云厂商共享宿主机上,%steal 的参考价值高于物理机
组件依赖:容器环境下查看 cgroup 指标时,需要区分 cgroup v1 和 cgroup v2 的文件路径
五、故障排查和监控
5.1 故障排查
5.1.1 日志查看
CPU 飙高虽然是资源问题,但日志仍然能给出触发时间点、请求类型和错误放大路径。日志至少看三类:系统日志、应用日志、内核日志。
# 查看系统日志 sudo journalctl -S -30min # 查看应用日志 tail -f /var/log/myapp/app.log # 查看错误日志 grep -E "ERROR|Timeout|RejectedExecution|GC overhead" /var/log/myapp/app.log | tail -50
如果是 Java 服务,再补 GC 日志:
grep -E "Pause Young|Pause Full|Full GC" /var/log/myapp/gc.log | tail -50
5.1.2 常见问题排查
问题一:CPU 100%,但 top 看不到明显高进程
top -b -n 1 | head -10 mpstat -P ALL 1 5 cat /proc/softirqs cat /proc/interrupts
症状:整机 CPU 高,单个业务进程都不突出,%sys/%soft 偏高
诊断:多半是软中断、网卡队列、驱动或内核收包路径问题
解决:
检查 NET_RX 分布是否倾斜
检查 irqbalance 是否正常工作
检查是否存在小包洪峰、连接风暴或异常抓包任务
问题二:应用进程 CPU 高,但业务量并不大
ps -eo pid,pcpu,pmem,cmd --sort=-pcpu | head top -H -psudo perf record -F 99 -p -g -- sleep 20 sudo perf report --stdio | head -60
症状:请求量一般,实例仍持续吃满单核或多核
诊断:常见于代码死循环、热点 key、异常重试、自旋锁、正则或 JSON 开销过大
解决:
先摘流,保证实例不继续放大影响
抓热点线程和调用栈
回滚版本或关闭问题开关
补压测用例,避免同类逻辑再次进生产
问题三:CPU 高伴随 RT 高,%steal 明显抬升
症状:业务 RT 波动明显,整机 CPU 看起来不算离谱,但 %steal 持续超过 5%
排查:mpstat -P ALL 1 5、云平台宿主机事件、节点资源争抢记录
解决:迁移实例、切换独享型规格、和云平台确认宿主机抖动
5.1.3 调试模式
线上调试优先轻量模式,不要一上来抓全量 heap dump 或 gcore。
# 实时查看热点函数 sudo perf top -p-g # 20 秒采样 sudo perf record -F 49 -p -g -- sleep 20 # 查看调试信息 sudo perf report --stdio | head -80
如果是 Java 服务,补一个只读线程栈:
jstack -l> /tmp/jstack.$(date +%s).log
5.2 性能监控
5.2.1 关键指标监控
# CPU使用率 mpstat -P ALL 1 3 # 进程维度 CPU pidstat -u -p ALL 1 3 # 网络连接 ss -s # 磁盘IO iostat -xz 1 3
建议重点盯这些指标:
node_cpu_seconds_total 按 mode 拆分后的 user/system/iowait/steal/softirq
单机 1 分钟和 5 分钟 load average
进程级 CPU Top N
网络重传、丢包、SYN backlog overflow
容器 throttled_periods_total
5.2.2 监控指标说明
| 指标名称 | 正常范围 | 告警阈值 | 说明 |
|---|---|---|---|
| 整机 CPU 使用率 | 日常峰值低于 70% | 连续 5 分钟高于 85% | 先看用户态还是系统态 |
| 单核心 CPU 使用率 | 波峰可到 80% | 连续 3 分钟高于 95% | 单线程热点更依赖这个指标 |
| steal 比例 | 低于 1% | 连续 3 分钟高于 5% | 云主机资源争抢的典型信号 |
| softirq 比例 | 低于 10% | 连续 3 分钟高于 25% | 网卡中断、连接洪峰常见 |
| 进程 CPU Top1 | 随业务波动 | 单进程长期高于 200% | 多核进程需结合线程视角判断 |
| 容器节流次数 | 接近 0 | 5 分钟内持续增长 | 说明 CPU quota 不够 |
5.2.3 监控告警配置
# 文件路径:prometheus/rules/cpu_hotspot.yml
groups:
- name: cpu-hotspot
rules:
- alert: HostCpuHigh
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 5m
labels:
severity: critical
annotations:
summary: "主机 CPU 持续过高"
description: "{{ $labels.instance }} CPU 连续 5 分钟高于 85%"
- alert: HostSoftIrqHigh
expr: avg by(instance) (rate(node_cpu_seconds_total{mode="softirq"}[5m])) * 100 > 25
for: 3m
labels:
severity: warning
annotations:
summary: "主机软中断占比过高"
description: "{{ $labels.instance }} 软中断 CPU 持续高于 25%"
- alert: ContainerCpuThrottlingHigh
expr: sum by(pod, namespace) (rate(container_cpu_cfs_throttled_periods_total[5m])) > 20
for: 5m
labels:
severity: warning
annotations:
summary: "容器 CPU 节流明显"
description: "{{ $labels.namespace }}/{{ $labels.pod }} 近 5 分钟 CPU throttling 持续升高"
5.3 备份与恢复
5.3.1 备份策略
任何 CPU 调优动作之前,先备份配置。尤其是 sysctl、IRQ 绑核、服务线程池、JVM 参数,改错了很容易引发更大的抖动。
#!/usr/bin/env bash
# 文件名:/usr/local/bin/backup_cpu_related_configs.sh
set -euo pipefail
BACKUP_DIR="/var/backups/cpu-tuning/$(date +%F_%H%M%S)"
mkdir -p "${BACKUP_DIR}"
cp -a /etc/sysctl.conf "${BACKUP_DIR}/"
cp -a /etc/sysctl.d "${BACKUP_DIR}/" 2>/dev/null || true
cp -a /etc/security/limits.conf "${BACKUP_DIR}/" 2>/dev/null || true
cp -a /etc/systemd/system "${BACKUP_DIR}/systemd" 2>/dev/null || true
sysctl -a > "${BACKUP_DIR}/sysctl-all.txt"
cat /proc/interrupts > "${BACKUP_DIR}/interrupts.txt"
cat /proc/softirqs > "${BACKUP_DIR}/softirqs.txt"
tar czf "${BACKUP_DIR}.tar.gz" -C "$(dirname "${BACKUP_DIR}")" "$(basename "${BACKUP_DIR}")"
echo "backup saved to ${BACKUP_DIR}.tar.gz"
5.3.2 恢复流程
停止临时调优动作:回滚脚本、关闭异常定时任务、撤销临时限流或绑核
恢复配置文件:从最近一次备份还原 sysctl、服务配置、线程池参数
验证完整性:执行 sysctl --system,检查服务配置语法和启动状态
重启或热加载服务:在低峰期执行,并用监控观察至少 10 分钟
六、总结
6.1 技术要点回顾
先分层,再深入:整机、进程、线程、调用栈四层顺着走,判断不会跑偏
先分态,再取证:先区分 %usr/%sys/%soft/%steal,再决定抓进程还是抓内核路径
先保现场,再做动作:快照比重启更重要,没有现场很难闭环
线程级定位价值最高:很多 CPU 问题真正的根因都藏在热点线程和调用栈里
系统态 CPU 不能只盯应用:软中断、网卡队列、连接风暴经常才是真正的根因
容器环境要看节流和 steal:CPU 不够用不一定是程序写差,也可能是配额和宿主机资源争抢
6.2 进阶学习方向
深入 Linux 调度器和 CFS 配额机制
学习资源:Linux kernel documentation、sched 相关内核文档
实践建议:在测试环境复现 CPU quota 节流、绑核和调度延迟问题
掌握 perf、eBPF、bcc 工具链
学习资源:Brendan Gregg 的性能分析资料、bcc-tools
实践建议:先从 perf top、perf record、runqlat、profile 这类轻量工具入手
建立故障自动取证体系
学习资源:Prometheus Alertmanager webhook、企业内巡检平台
实践建议:把 CPU、高负载、连接风暴的快照采集做成统一脚本和 SOP
6.3 参考资料
Linux kernel CPU documentation - Linux 内核 CPU、调度与性能分析文档
sysstat official site - sar、mpstat、pidstat 工具说明
perf wiki - perf 使用方法和常见问题
Brendan Gregg Blog - Linux 性能分析实战材料
附录
A. 命令速查表
top -H -p# 查看进程内线程 CPU pidstat -u -t -p 1 5 # 观察线程级 CPU 趋势 mpstat -P ALL 1 3 # 查看每个核心使用率 sar -u ALL 1 5 # 查看 CPU 各 mode 分布 sar -n DEV,EDEV,TCP,ETCP 1 5 # 查看网络与 TCP 指标 iostat -xz 1 5 # 查看磁盘队列与 IO 延迟 cat /proc/softirqs # 查看软中断分布 cat /proc/interrupts # 查看硬中断分布 perf top -p -g # 实时看热点函数 perf record -F 99 -p -g -- sleep 20 # 采样留证据
B. 配置参数详解
kernel.perf_event_paranoid
作用:控制普通用户使用 perf 的权限
建议:生产环境默认保持严格,排障时临时下调,结束后恢复
HISTORY
作用:控制 sysstat 历史数据保留天数
建议:不少于 28 天,双十一、618 这类业务建议保留 60 天
cpu.max / cpu.cfs_quota_us
作用:容器 CPU 配额限制
建议:对低延迟业务慎用过小配额,避免频繁 throttling
smp_affinity
作用:控制中断可在哪些 CPU 上处理
建议:高流量网关节点关注这个参数,避免中断只压在少数核心
C. 术语表
| 术语 | 英文 | 解释 |
|---|---|---|
| 用户态 | user mode | CPU 在应用代码上消耗的时间 |
| 系统态 | system mode | CPU 在内核代码、系统调用、中断处理上消耗的时间 |
| 软中断 | softirq | Linux 内核为网络、块设备等延后处理设计的轻量机制 |
| 节流 | throttling | 容器因 CPU quota 不足被强制限制执行 |
| 窃取时间 | steal time | 虚拟机本该获得 CPU,但被宿主机拿去服务其他虚机的时间 |
全部0条评论
快来发表一下你的评论吧 !