性能分析

Posted by 大雁小鱼的博客 on December 12, 2018

性能分析

性能有多个组成部分,包括磁盘IO的性能,CPU的性能,网络的性能,面试的时候面试官单纯地问你性能问题是不妥当的,一定要具体到某一个点去问或者回答,因为不同的性能问题,其排查思路、解决方法是完全不同的。

这个博客我打算聊一聊磁盘IO性能排查和CPU的使用率过高的性能排查。

首先说一说磁盘IO性能问题
通过top命令可以查看系统的整体性能,在第三行可以看到几个数值,其中有一个wa,意思是iowait,即等待IO时间的意思。 要说清这个问题,就需要搞清楚什么是进程状态。

Linux进程大致可分为6种状态,分别是可中断睡眠(S)、不可中断睡眠(D)、运行中或可运行状态(R)、空闲(I)、暂停或跟踪状态(T)、僵尸进程(Z),在这里我们需要重点说一说不可中断状态。 不可中断状态,一般表示进程正在跟硬件交互,并且此交互过程不允许被其他进程或者中断打断,在交互期间的时间就是iowait的时间。一般情况下与硬件交互的时间是很短暂的,很快就会结束,所以一般情况下我们在ps下是看不到状态为D进程的,但如果磁盘读写很频繁或者磁盘读写有性能瓶颈,那么这个交互时间就会比较长,iowait的数值就会比较高了。

如果发现iowait的时间比较高,有2种情况,这里分别讨论:

情况1:系统中几乎全部是IO密集型应用,自然会频繁的读写磁盘,所以这个时候出现iowait数值过高可能属于正常现象,在这里只需要运维人员平时多注意一下它是否超过了可承受的极限范围即可,如果超过了,就意味着是不是可以把这些程序部署在不同的机器上,或者是提升机器硬件性能。

情况2:系统中只有很少的IO密集型应用,或者干脆就没有。此时出现iowait数值过高就属于不正常现象。排查的思路如下: 首先需要确定是否存在什么进程在不断地读写磁盘,这时可以使用dstat 1命令,它从系统的纬度告诉我们当前磁盘读写的情况,如下图所示,磁盘的读写如果很大,比如1秒内读写60M那就可以确定确实有进程在大量读写磁盘。

接着需要确定是哪个进程在拼命读写磁盘。刚才的命令只能从系统纬度,要想从进程纬度我们可以使用pidstat -d 1命令,它会打印出每个读写磁盘的进程每秒钟读写磁盘的情况。如下图所示,可以看到有一个进程的每秒读写很大,那这个进程就很可疑,是我们重点关注的对象。

当我们确定了是哪个进程之后,最后一步就是确定该进程中的哪句或哪几句代码引起磁盘IO过高,这是一个统计意义上的问题,所以可以使用perf record -g命令记录CPU的时钟周期信息,以此来确定热点函数。这里要多说一句,判断是哪个函数引发性能问题需要积累一定的Linux内核知识,另外加上丰富的实战经验,只有多积累经验我们才能一眼看出症结。根据经验当发现频繁调用blkdev_direct_IO,即直接对磁盘进行读写,绕过了系统缓存,会出现iowait升高现象。

说完了磁盘IO性能问题,再来说说CPU性能问题。top命令的第三行有一个CPU使用百分比,它表示的是CPU的使用率,但要注意,这个使用率是所有CPU使用率的平均值,这个时候只要按下数字键1,就可以查看每颗CPU的使用率情况。(插曲一下,这个方法也可以用来查看系统有几颗CPU)

在第三行,还有2个百分比比较重要,就是us和sy,他们分别表示用户态的CPU使用率和内核态的CPU使用率,他们也是一个均值。

如果某个CPU的使用率达到100%,就说明存在CPU性能问题。那么CPU的性能问题是怎么来的呢?下面我们分情况讨论。注意,我的主要语言是Java,所以下面的例子我会从Java程序员的角度去分析如何处理CPU升高的问题。

第一种,用户态CPU使用率升高,从而导致系统整体的CPU使用率升高。

出现这种情况说明我们的应用程序代码有问题,比如出现了死循环或者使用了一个非常耗性能的函数。pidstat可以帮助我们找到究竟是哪个应用程序的CPU使用率升高,比如使用下面的命令

pidstat -u 1

这个命令会输出每一个执行了CPU时间片的进程的CPU使用率情况,通过它我们立刻就能锁定目标,找到CPU使用率高的进程。 找到了进程,接下来就是定位代码。这里我选择使用一款阿里巴巴出品的Java诊断利器arthas(https://github.com/alibaba/arthas),它能帮助我们输出当前CPU使用率最高的线程的堆栈信息。

thread -n 1

第二种,内核态CPU使用率升高,从而导致系统整体的CPU使用率升高。

出现这种情况的原因一般是频繁地使用系统调用,一般这种情况比较少,如果出现,只需要检查在哪个函数内部调用系统调用过于频繁就你能立刻定位,在此不展开叙述。

第三种,线程过多,引起频繁的CPU上下文切换,从而导致系统整体CPU使用率升高。

当前的操作系统都是多任务的,能做到这一点主要是操作系统将CPU的使用切割成了很小的时间片,分给每一个要使用CPU的线程。在从线程A切换到线程B的过程中,会发生CPU上下文切换,这个动作在当前大多数的系统中是很短暂的,但是如果切换过于频繁,系统的大多数时间都花在了上下文切换上,真正留给线程执行的时间就变少了。所以线程过多也不是什么好事,可以进行代码审查,看看有没有不必要的线程被创建了。