讲解如何排查Linux高负载是一个老生常谈的话题,但大多数文章都只关注几个点,缺乏整体排查思路的介绍。所谓“授人以鱼不如授人以渔”。本文试图建立一种方法和套路,帮助读者更全面地了解排查高负载问题。从消除误解开始,没有基线的负载是不可靠的负载。从接触Unix/Linux系统管理的第一天开始,很多人就接触到了SystemLoadAverage这个监控指标。然而,并不是每个人都知道这个指标。的真正含义。一般来说,经常会听到以下的误解:HighLoad就是高CPU负载……传统的Unix和Linux在设计上是不同的。在Unix系统中,高Load是由更多可运行的进程引起的,但对于Linux则不然。对于Linux,Load高可能有两种情况:R态系统进程数增加导致D态系统进程数增加导致Loadavg值大于一定的值,肯定有问题……Loadavg的值是相对值受CPU和IO设备数量,甚至一些软件定义的虚拟资源的影响。高负载的判断需要基于一定的历史基线(Baseline),不能没有原则地跨系统比较负载。Loadhighsystemmustbeverybusy.....Loadhighsystemcanbeverybusy,例如CPUloadishigh,CPUbusy。但是如果负载很高,系统并不总是很忙。比如IO负载高,磁盘可以很忙,但是CPU可以相对空闲,比如iowait高。这里注意iowait本质上是一种特殊的CPU空闲状态。还有一种负载高,CPU和磁盘外设可能空闲,这可能是锁竞争导致的。这时候在cpu时间iowait不高,idle高。BrendanGregg在他最近的博客[LinuxLoadAverages:SolvingtheMystery](http://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html)中讨论了Unix和Linux平均负载回到24年前Linux社区的讨论,找出当时Linux修改UnixLoadAverage定义的原因。文章认为,正是由于Linux引入了D态线程的计算方式,导致高Load的原因变得模糊。因为系统中D状态切换的原因太多了,肯定不是IO负载,锁竞争就这么简单!正是因为这种模糊性,才更难跨系统、跨应用类型比较Load的值。所有负载级别的基础应基于历史基线。这个微信公众号也写了一篇相关的文章,大家可以参考一下LinuxLoadAverage的那些事。如何排查负载高的问题上面说到,因为在Linux操作系统中,load是一个定义不明确的指标,所以排查loadavg高的问题是一个非常复杂的过程。基本思路是根据R状态任务的增加还是D状态任务的增加是负载变化的根本原因进入不同的流程。这里给出一个检查Load增加的通用例程,仅供参考:在Linux系统中,读取/proc/stat文件,获取系统中处于R状态的进程数;但是D状态下的任务数量可能是最直接的方法,使用ps命令更方便。/proc/stat文件中的procs_blocked显示了等待磁盘IO的进程数:通过简单区分是R态任务多还是D态任务多,我们就可以进入不同的排查流程。下面我们就对这张大图的排错思路做一个简单的梳理。R状态任务的增加通常称为高CPU负载。排查定位此类问题的主要思路是分析系统、容器、进程的运行时间,找到CPU上的热点路径,或者分析CPU的运行时间主要在哪一段代码上。CPUuser和systime的分布通常可以帮助人们快速定位是与用户态进程有关还是与内核有关。此外,CPU的运行队列长度、调度等待时间、非自愿上下文切换次数等,都有助于大致了解问题场景。因此,如果要将问题场景与相关代码关联起来,通常需要借助perf、systemtap、ftrace等动态跟踪工具。关联代码路径后,在下面的代码时间分析过程中,代码中一些无效的运行时间也是分析中首要关注的,比如用户态和内核态的自旋锁(SpinLock)。当然,如果CPU正在运行非常有意义和高效的代码,唯一需要考虑的是负载是否真的太大了。D态任务增加根据Linux内核的设计,D态任务本质上是TASK_UNINTERRUPTIBLE导致的主动休眠,所以有多种可能性。但是由于Linux内核CPU空闲时间对于IO栈导致的休眠有专门的定义,即iowait,所以iowait成为了在D状态分类中定位高Load是否为IO导致的重要参考。当然,如前所述,/proc/stat中procs_blocked的变化趋势也可以作为判断iowait导致的高Load的一个很好的参考。CPUiowait高很多人通常对CPUiowait有一个误解,认为iowait高是因为此时CPU忙于做IO操作。其实恰恰相反,当iowait为高时,CPU处于空闲状态,没有任务运行。只是因为此时已经有磁盘IO发出,所以此时的idle状态被标记为iowait,而不是idle。但是此时如果我们使用perfprobe命令,我们可以清楚的看到处于iowait状态的CPU其实是运行在pid为0的idle线程上的:idle分别是LinuxIO栈和文件系统的代码,会调用io_schedule等待磁盘IO完成。这时候计算cpu时间的原子变量rq->nr_iowait会在sleep之前自增。请注意,在调用io_schedule之前,通常调用者会显式地将任务设置为TASK_UNINTERRUPTIBLE状态:CPUidleishigh。上面说了相当多的coreblock,也就是TASK_UNINTERRUPTIBLEsleep,和等待磁盘IO无关,比如内核中的锁竞争,再比如内存直接页回收的休眠,再比如内核中某些代码路径上的主动阻塞,等待资源。在BrendanGregg最近的博客【LinuxLoadAverages:SolvingtheMystery】(http://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html)中,使用了perf命令生成的TASK_UNINTERRUPTIBLE睡眠的火焰图,很好地展示了高CPU空闲造成的多样性。本文不再赘述。所以分析highCPUidle本质上就是分析内核的代码路径造成阻塞的主要原因。通常,我们可以使用perfinject来处理perfrecord记录的上下文切换事件,关联进程从CPU退出(swtichout)和再次切入(switchin)的内核代码路径,生成所谓的关闭CPU火焰图。当然,对于锁竞争这种比较简单的问题,OffCPU火焰图已经足够一步定位问题了。但对于更复杂的被D状态阻塞的延迟问题,也许OffCPU火焰图只能给我们一个调查的起点。比如当我们看到OffCPU火焰图的主要休眠时间是epoll_wait等待导致的。那么,我们继续考察的应该是网络栈的延迟,也就是本文大图中的NetDelay部分。到这里,你可能会发现CPUiowait和idle高性能分析的本质是delay分析。Thisisbigpicture根据内核中资源管理的大方向,将延迟分析细化为六大延迟分析:CPU延迟、内存延迟、文件系统延迟、IO栈延迟、网络栈延迟、锁和同步原语competition,TASK_UNINTERRUPTIBLE由以上任何一个代码路径引起的Sleep就是我们要分析的对象!以一个问题结束由于篇幅所限,本文涉及的细节难以展开,因为看完这里,你可能会发现原来分析高Load其实是对系统的综合负载分析。难怪它被称为系统负载。这就是负载分析难以在一篇文章中完全涵盖的原因。
