3、CPU瓶颈定位:CPU使用率分析、上下文切换、中断处理、负载均衡
说到CPU瓶颈,我见过太多人一上来就盯着CPU使用率看。其实这玩意儿就像体温计——能告诉你发烧了,但说不清是病毒感染还是伤口发炎。今天咱们就把CPU这层皮扒开,看看里面到底在忙什么。
3.1 CPU使用率:别被数字骗了
CPU使用率100%就一定有问题吗?不一定。我遇到过一台服务器,CPU跑满100%,但业务延迟反而正常。后来发现是某个批处理任务在“偷跑”,把空闲时间全占了。所以,关键要看“谁在用”和“怎么用”。
Linux下我习惯用top和mpstat来拆解CPU时间片:
# 查看CPU时间分布
$ mpstat -P ALL 1 5
Linux 5.4.0 (node01) 03/15/2024 _x86_64_ (8 CPU)
10:32:01 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
10:32:02 all 65.2 0.0 12.3 8.5 0.0 2.1 0.0 0.0 11.9
10:32:02 0 72.1 0.0 10.5 6.2 0.0 1.8 0.0 0.0 9.4
10:32:02 1 58.3 0.0 14.1 10.8 0.0 2.4 0.0 0.0 14.4
这里有几个关键指标:
- %usr:用户态CPU时间。如果这个高,说明你的应用代码在疯狂计算。
- %sys:内核态CPU时间。这个高了要小心——可能是系统调用太频繁。
- %iowait:CPU等I/O的时间。说白了就是CPU在“摸鱼”,等磁盘或网络。
- %steal:虚拟化环境被偷走的时间。云服务器上这个高,说明邻居在抢资源。
我的经验法则:如果%usr高,优化代码;如果%sys高,减少系统调用;如果%iowait高,换SSD或加内存缓存。
3.2 上下文切换:看不见的性能杀手
上下文切换,说白了就是CPU从一个任务切到另一个任务。每次切换都要保存寄存器、刷新TLB、加载新进程状态。你想想看,如果每秒切换几万次,CPU光干这个就累死了。
我曾在压测一个高并发服务时,发现CPU使用率只有30%,但吞吐量上不去。用vmstat一看,上下文切换(cs列)高达每秒8万次:
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
8 0 0 2048000 1024 8192000 0 0 0 0 1200 82000 25 45 30 0 0
看到没?cs(context switch)高达82000,sy(系统CPU)占了45%。这明显是上下文切换把CPU吃掉了。
为什么会这样?我排查后发现是线程池开得太大,每个请求都创建新线程。后来改用协程,上下文切换直接降到2000以下。
避坑指南:我曾经在Nginx上遇到类似问题,后来发现是开启了太多worker进程,每个worker又绑定独立CPU。其实对于I/O密集型服务,worker数等于CPU核数就够了,多了反而因为上下文切换拖慢性能。
3.3 中断处理:别让硬件打断你的节奏
中断是硬件通知CPU“有事找你”的方式。但中断太多,CPU就得频繁停下手中的活去处理。我见过一个极端案例——某台机器网卡中断每秒超过10万次,CPU的%irq和%soft加起来占了60%。
查看中断分布用/proc/interrupts:
$ cat /proc/interrupts | grep eth0
CPU0 CPU1 CPU2 CPU3
24: 12345678 23456789 34567890 45678901 IR-PCI-MSI eth0-TxRx-0
25: 87654321 98765432 09876543 10987654 IR-PCI-MSI eth0-TxRx-1
这里每个CPU处理的中断数一目了然。如果某个CPU的中断数特别高,说明中断没有均衡分布。
解决办法有两个:
- 中断亲和性(IRQ affinity):把中断绑定到特定CPU,避免所有中断挤在一个核上。
- 多队列网卡(RSS):现代网卡支持多队列,每个队列可以绑定不同CPU,自动均衡。
注意:别把中断和业务进程绑在同一个CPU上。否则业务进程被中断打断,延迟会飙升。我一般把中断绑在CPU 0-1,业务进程跑在CPU 2-7。
3.4 负载均衡:让每个CPU都忙起来
负载不均衡是常见问题。比如8核CPU,一个核跑满100%,其他核闲着。这就像8个人干活,一个人累死,七个人聊天。
检查负载均衡用mpstat -P ALL或者pidstat:
$ pidstat -p ALL -t 1
Linux 5.4.0 (node01) 03/15/2024 _x86_64_ (8 CPU)
10:35:01 UID TGID TID %usr %system %guest %wait %CPU CPU Command
10:35:02 0 1234 - 85.2 2.1 0.0 0.0 87.3 0 myapp
10:35:02 0 1235 - 2.3 0.5 0.0 0.0 2.8 1 myapp
10:35:02 0 1236 - 1.8 0.3 0.0 0.0 2.1 2 myapp
看到没?CPU列显示所有线程都挤在CPU 0上。这就是典型的软中断或锁竞争导致的问题。
我常用的调优手段:
- 设置CPU亲和性:用
taskset或numactl把线程绑定到不同CPU。 - 调整内核调度策略:比如把
/proc/sys/kernel/sched_migration_cost_ns调小,让调度器更积极地迁移任务。 - 使用cgroups隔离:给不同业务分配CPU配额,防止互相干扰。
我的一个教训:有一次数据库CPU不均衡,我折腾了半天亲和性,最后发现是NUMA架构下内存访问延迟导致的。说白了,线程在CPU 0上跑,但数据在CPU 1的内存里,访问一次要绕远路。后来用numactl --membind把内存和CPU绑定,问题就解决了。
知识体系总览
下面这张图总结了CPU瓶颈定位的核心逻辑。你可以把它当作排查时的“地图”:
嗯,这张图基本把CPU瓶颈的排查路径串起来了。实际工作中,我一般按这个顺序来:先看使用率是不是异常,再查上下文切换有没有过高,接着检查中断是否均衡,最后才做负载均衡调优。每一步都有对应的工具和指标,别跳步。
小技巧:如果你在排查时觉得无从下手,先跑一个perf top看看热点函数在哪。很多时候,热点函数会直接告诉你瓶颈在用户态还是内核态,省去很多猜测时间。
想要更多资料,联系可可