Linux CPU调度、负载与性能分析实战
CPU问题不只表现为使用率100%。系统可能CPU使用率不高却延迟很大,也可能Load Average很高但CPU仍空闲。排障必须区分运行队列、不可中断I/O、软中断、锁竞争、上下文切换、虚拟化Steal、频率降级和NUMA远端访问。
一、先理解Load Average
Linux Load Average统计可运行任务以及不可中断睡眠任务的平均数量,不等于CPU利用率。
uptime
cat /proc/loadavg
判断Load需结合CPU数量、运行队列和D状态任务。8核主机Load 8可能刚好满载,也可能由I/O等待造成;Load 2也可能让单线程关键服务达到瓶颈。
二、CPU时间分类
mpstat -P ALL 1
top
常见字段:
- user:用户态;
- system:内核态;
- iowait:CPU空闲且有任务等待I/O;
- irq/soft:硬中断和软中断;
- steal:虚拟机等待宿主机CPU;
- guest:运行虚拟CPU;
- idle:空闲。
总CPU平均值会掩盖单核热点,应始终查看每个CPU。
三、快速基线
mpstat -P ALL 1
pidstat -u -w -t 1
vmstat 1
ps -eo pid,ppid,psr,stat,comm,%cpu --sort=-%cpu | head -30
同时记录业务QPS、P95/P99、错误率、线程池、GC和下游延迟。CPU指标离开业务吞吐没有意义。
四、运行队列与调度等待
vmstat 的 r 表示可运行任务数。长期明显高于可用CPU说明竞争,但短时突发可能正常。
cat /proc/schedstat
cat /proc/<pid>/sched
关注:
- 运行等待时间;
- 上下文切换;
- 进程迁移;
- 单线程热点;
- Cgroup CPU限额;
- CPU亲和性。
五、上下文切换
pidstat -w 1
vmstat 1
自愿切换通常是等待锁、I/O或条件变量;非自愿切换通常是时间片耗尽或被更高优先级任务抢占。
上下文切换高可能来自线程过多、锁竞争、短任务、频繁系统调用或超细粒度并发。不要简单把线程数调得更大。
六、找热点线程
top -H -p <pid>
pidstat -t -p <pid> 1
ps -L -p <pid> -o pid,tid,psr,stat,pcpu,comm
Java线程ID常以十进制显示,线程Dump中可能是十六进制,需要转换关联。高CPU线程可能是业务计算、GC、JIT、加密、压缩或死循环。
七、perf方法
perf stat -p <pid> sleep 10
perf top -p <pid>
perf record -F 99 -g -p <pid> -- sleep 30
perf report
perf stat可观察cycles、instructions、cache miss、branch miss和上下文切换。IPC低可能来自内存等待、分支预测失败或锁竞争。
采样会带来开销,生产应短时、限制频率,并确认安全策略允许获取符号。
八、火焰图
火焰图宽度代表采样占比,不代表时间顺序。需要:
- 正确符号和调试信息;
- 合适采样窗口;
- 覆盖异常时段;
- 区分CPU火焰图、Off-CPU和内存火焰图;
- 与业务请求关联。
CPU火焰图看不到等待锁和I/O的时间,需配合Off-CPU分析。
九、软中断与网络热点
cat /proc/softirqs
cat /proc/interrupts
mpstat -P ALL 1
ethtool -l <interface>
单核softirq高会限制网络吞吐。检查IRQ亲和性、RSS/RPS/XPS、多队列、网卡驱动和包大小。不要在不了解NUMA和队列映射时随意手工绑中断。
十、D状态与高Load
ps -eo state,pid,ppid,wchan:32,comm | awk '$1 ~ /D/'
cat /proc/<pid>/stack
D状态常表示不可中断等待,可能来自磁盘、NFS、块设备、驱动或内核锁。杀进程通常不能立即结束,需处理底层等待原因。
十一、虚拟化Steal
虚拟机的steal升高表示宿主机没有及时分配物理CPU,常见于超分或邻居噪声。
排查:
- 云监控或虚拟化平台CPU争用;
- vCPU数量与NUMA;
- 宿主机CPU Ready;
- Burst积分;
- 迁移到其他宿主机;
- 实例规格。
虚拟机内部调优无法解决宿主机争用。
十二、CPU频率与温度
lscpu
cpupower frequency-info
grep -E 'cpu MHz' /proc/cpuinfo
sensors
高温、功耗限制、BIOS电源策略和虚拟化配置可能让频率下降。CPU利用率相同但频率不同,业务吞吐会明显变化。
生产服务器要结合性能、功耗和延迟目标选择Governor,不盲目锁定最高频率。
十三、NUMA与CPU亲和性
numactl --hardware
numastat
taskset -pc <pid>
绑核可以减少迁移,但也可能把任务限制在少数CPU或造成中断和应用争抢。CPU亲和性要与内存、网卡、NVMe和GPU所在NUMA节点共同设计。
十四、Cgroup CPU限制
Cgroup v2:
cat /sys/fs/cgroup/<group>/cpu.max
cat /sys/fs/cgroup/<group>/cpu.stat
cat /sys/fs/cgroup/<group>/cpu.pressure
nr_throttled和throttled_usec持续增加说明受到CPU quota限制。容器看似CPU没有打满,也可能每个调度周期都被节流。
Kubernetes中请求影响调度和权重,限制触发节流。对延迟敏感服务要通过压测决定是否设置过紧limit。
十五、PSI CPU
cat /proc/pressure/cpu
CPU PSI表示任务因等待CPU而停顿的时间。它比单纯CPU利用率更接近资源压力,可用于弹性伸缩和过载保护,但仍需结合吞吐和延迟。
十六、锁竞争
高CPU不一定都是有效计算。自旋锁、互斥锁和原子竞争会消耗CPU。
工具:
perf lock;perf c2c;- eBPF lock工具;
- 语言运行时Profile;
- Java Flight Recorder;
- Go mutex/block profile。
治理可能是缩小共享状态、分片锁、批处理、减少线程或改变算法。
十七、调度策略与优先级
普通任务通常使用CFS。nice影响权重,不保证固定CPU比例。实时调度 SCHED_FIFO/RR 可饿死普通任务,必须限制权限、CPU和运行时间。
chrt -p <pid>
renice -n 5 -p <pid>
不要用实时优先级解决普通业务延迟。
十八、语言运行时
Java
区分业务线程、GC、JIT和VM线程。结合GC日志、JFR、线程Dump和async-profiler。
Go
结合CPU pprof、Goroutine、Block和Mutex Profile。高Goroutine不一定高并发能力。
Node.js
单事件循环可能单核打满,检查同步CPU任务、JSON、正则和Native扩展。Worker Thread不是默认万能解法。
Python
CPU密集线程受GIL影响,考虑多进程、Native库或任务队列,同时注意进程复制内存。
十九、常见场景
CPU 100%
定位到进程和线程,区分user/system/softirq,采样热点;确认业务吞吐是否同步上升。若吞吐不升,重点看锁、GC、重试和死循环。
CPU不高但延迟高
检查单核、quota节流、Steal、D状态、锁等待、下游和队列。总CPU平均值最容易误导。
Load高但CPU空闲
检查D状态、I/O、NFS、块设备和内核栈。
扩容CPU后无改善
检查单线程瓶颈、锁、NUMA、频率、数据库或下游限制,确认应用能否并行。
二十、监控与上线清单
- [ ] 每核user/system/iowait/softirq/steal受监控;
- [ ] Load与运行队列按CPU数量解释;
- [ ] 上下文切换、线程数和迁移有基线;
- [ ] PSI、Cgroup节流和容器limit可见;
- [ ] IRQ、网卡队列和NUMA拓扑合理;
- [ ] CPU频率、温度和功耗限制受监控;
- [ ] 语言运行时Profile工具已准备;
- [ ] perf采样和符号访问有安全边界;
- [ ] 高CPU、D状态、Steal和quota场景有Runbook;
- [ ] 调优前后以业务吞吐和P99验证。
二十一、结语
CPU性能分析不能停留在top截图。要把利用率、运行队列、调度等待、上下文切换、软中断、Cgroup和语言运行时联系起来。先确定时间花在计算、排队还是等待,再选择扩容、限流、绑核、算法优化或修复下游,才能真正改善业务延迟。
参考资料
- Linux Scheduler:https://docs.kernel.org/scheduler/
- perf Documentation:https://perf.wiki.kernel.org/
- PSI:https://docs.kernel.org/accounting/psi.html