浅谈CPU高负载

2018-02-27 11:46:42来源:oschina作者:ericquan8人点击

分享
%CPU跟Load的区别

两个参数都用于描述CPU的负载程度,都能从top命令中查看。但其实两者有不同的含义,举个例子:有一公共澡房(CPU),1个人在里面洗澡,8个人在外面排队等洗澡。这样可以理解load=9,总共有9个任务(9个人)。如果澡房里的人一直在洗刷刷,那CPU的使用率就是100%;相反,如果他光占着澡房不洗澡(思考人生或者洗衣服),那%CPU为0,这种情况就是load很高,但%CPU很低,常见的就有资源竞争导致死锁的场景。


CPU的工作模式

CPU可理解成干活的工人,系统给他安排了很多任务,这些任务大体可分为两类:进程和中断,其中进程是我们平时比较关注的。而进程内部可以产生多个线程,线程就是CPU运行任务的最小单位;同一个进程的多个线程可以跑在不同的CPU任务队列上,并且线程不会固定在一个核上运行,而是根据CPU调度。可以做个试验:跑起两个死循环的线程,1分钟后可以看到load=2,该进程的CPU使用率为200%, 其中2个CPU的使用率为100%。现在的系统中,CPU核数一般小于正在运行线程的数,且CPU某一时刻只能运行一个线程,所以系统需要管理和分配CPU资源,给每个线程安排优不同的优先级,以决定哪个线程该使用CPU,哪个线程该等待;那些等待运行的线程就会被安排存放在一个可运行队列中,每个CPU都有一个独立的可运行队列。下面简单描述CPU的内部结构:



图中有一个4核CPU,每个核都维护自己的一个可运行队列;当前每个CPU都正在运行一个任务,且都有3个任务在可运行队列中等待运行;如果此时再有一个新的任务被安排运行,要看该任务的优先级,如果是高优先级的,系统可能会命令CPU1(CORE_1)先暂停正在执行任务(进程A_线程1),优先运行新任务;如果是低优先级的,那么就进入其中一个CPU的工作队列中等待运行。


另外,一个运行中的线程不会一直在使用CPU直到任务完成,如果该线程的任务耗时很长,那么其他线程就一直在等待,这就造成其他线程一直无法得到运行,表现上看就是某些进程没响应。系统为了避免这种状况,会给每个正在运行的线程分配一定的运行时间片。如果该时间片用完,系统会先保存当前线程的上下文,将该线程返回到工作队列中等待,再从队列中选另外一个线程,恢复其上下文数据,开始运行。


怎样的CPU才算忙?

Load的确可以作为衡量CPU忙不忙的指标之一,那么能不能单凭看某时刻的指标来判断负载高不高?像很多时候我们看到load大于5(或者更少),就认为系统负载高。实际上,load=5只代表最近一段时间有5个需要运行的任务,难道CPU跑5个任务都跑不起?Brendan Gregg大神就做过一个尝试,在一个双核机器上搭了一个邮件服务器,平均load在11~16,系统响应还是很迅速。要从load去判断系统忙不忙,只能依赖参照物做对比。最起码的参照物就是日常环境平时的load是多少,系统在这个load下的表现如何,响应能否符合预期。其实在top命令中,系统显示了最近1分钟、5分钟、15分钟的load值,如果load(1min)=10, load(5min)=30, load(15min)=30,我们只能确定最近系统的需要处理的线程数在下降,无法断定系统(进程)的当前性能状态,如果平时的load只有0.1呢?


理论上针对单个CPU看,CPU一直在运行同一个线程,CPU使用率达到100%,这是最高效率的表现,这算是CPU忙的其中一个表现;但现实中我们又不希望到100%的;当%CPU到一定水位(60%、70%、80%等)就要考虑开始扩容,这个由各自业务与公司风控规则决定。


从前文提到的CPU工作模式看,每个线程有运行时间片,如果时间片用完了,就要让出当前CPU,给另外一个线程使用,这就是我们说的上下文切换。如果一个系统的上下文切换很多(例如线程数很多),甚至达到每秒上万次,那CPU的主要工作时间都用于保存、恢复上下文,干正事的时间却没多少。此种情况,实际上CPU也是很忙,但我们不希望CPU以这种方式在忙着。


CPU负载高排查

作为一名Java搬砖工,先简单说下在Java应用层面,排查CPU负载高最常用的武器就是top + jstack, top找出哪个线程是消耗CPU大户,再结合jstack找出具体是什么线程。如果是线上,最好连续打3次jstack情况,这样方便排查。如果是比较棘手的问题,需要维度更细的手术刀,推荐杜琨的greys。


现在能排查出哪个线程在耗CPU了,基本能定位问题了。如果再想了解究竟这个线程耗了多少CPU,可以用Brendan Gregg大神写的Perl脚本,将CPU的使用率以火焰图形式展现出来。需要3个工具:JDK8,perf-map-agent、FlameGraph。perf-map-agent用于在perf的分析结果能看到Java代码中对应的方法和类。都安装完成后,开工:


cd path-to-your-FlameGraph
perf record -F 49 -a -g -- sleep 30; ./FlameGraph/jmaps
perf script > out.stacks11
cat out.stacks11 | ./FlameGraph/stackcollapse-perf.pl | grep -v cpu_idle | ./FlameGraph/flamegraph.pl --color=java --hash > out.stacks11.svg

%20

%20上图中绿色的就是Java代码,黄色是C++,红色的是系统内核代码。鼠标移上去Java那格子(右键在新窗口打开该图片),可以看到Java进程在这个采集的时间段中,占用了45%的CPU;点进去看详情,这45%的好用平均被3个concurrent包下的类给瓜分了。有时可能遇到load很高,但CPU使用率又很空闲的情况,这种情况就要想想是不是上下文切换惹的事。此时一般用top看不出什么异常数据,得上vmstat:%20从上图vmstat打出的数据看,看到系统cs(上下文)和in(中断)数都很高,但还无法定位哪个进程在搞事。此时再上systemtap,结合霸爷的监控脚本 从图中看,上下文切换都发生在Java(PID=76576),在不同的子线程之间循环切换;此时的CPU都在处理该线程的不同子线程。

以上是本人对CPU的浅谈,通过了解CPU工作原理,再结合排查工具与程序代码,让自己在日常问题排查、性能优化方面有更高的效率。

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台