一、负载的查看方式
w/uptime/top # 都是读取的 /proc/loadavg 这个文件
load average: 0.00, 0.01, 0.05
意思分别是1分钟、5分钟、15分钟内系统的平均负载
二、多核心处理器与多处理器的区别
从性能的角度上理解,一台主机拥有多核心的处理器与另台拥有同样数目的处理性能基本上可以认为是相差无几。
当然实际情况会复杂得多,不同数量的缓存、处理器的频率等因素都可能造成性能的差异。
但即便这些因素造成的实际性能稍有不同,其实系统还是以处理器的核心数量计算负载均值。
三、对负载的理解
CPU核心
如果系统中有N个核心,那么负载为N的时候,就意味着 CPU 在全力运转。
# 查看 CPU 的核心数
grep -c 'model name' /proc/CPUinfo
当系统负荷持续大于0.7N,你必须开始调查了,问题出在哪里,防止情况恶化。
当系统负荷持续大于1.0N,你必须动手寻找解决办法,把这个值降下来。
当系统负荷达到5.0N,就表明你的系统有很严重的问题,长时间没有响应,或者接近死机了,你不应该让系统达到这个值。
如果只有1分钟的系统负荷大于1.0,其他两个时间段都小于1.0,这表明只是暂时现象,问题不大。
如果15分钟内,平均系统负荷大于1.0(调整CPU核心数之后),表明问题持续存在,不是暂时现象。
所以,你应该主要观察"15分钟系统负荷",将它作为电脑正常运行的指标。
进程状态
在Linux中,进程分为三种状态:
阻塞的进程 blocked process,进程会等待I/O设备的数据或者系统调用。
正在等待可运行的进程 runnable process,处在一个运行队列run queue中,与其他可运行进程争夺CPU时间。
正在运行的进程running process
系统的load是指正在运行running one和可运行runnable one的进程的总数。
比如现在系统有2个正在运行的进程,3个可运行进程,那么系统的load就是5,load average就是一定时间内的load数量。
在linux内核中无法区别正在运行的状态和可运行的等待状态,所以都称为running状态。
sleep进程
Linux上的load average除了包括正在运行CPU的进程数量和正在等待可运行CPU的进程数量之外,还包括uninterruptible sleep的进程数量。
通常等待IO设备、等待网络的时候,进程会处于uninterruptible sleep状态。
Linux设计者的逻辑是,uninterruptible sleep应该都是很短暂的,很快就会恢复运行,所以被等同于runnable。
然而uninterruptible sleep即使再短暂也是sleep,何况现实世界中uninterruptible sleep未必很短暂,大量的、或长时间的uninterruptible sleep通常意味着IO设备遇到了瓶颈。
众所周知,sleep状态的进程是不需要CPU的,即使所有的CPU都空闲,正在sleep的进程也是运行不了的。
所以sleep进程的数量绝对不适合用作衡量CPU负载的指标,Linux把uninterruptible sleep进程算进load average的做法直接颠覆了load average的本来意义。
所以在Linux系统上,load average这个指标基本失去了作用,因为你不知道它代表什么意思,当看到load average很高的时候,你不知道是runnable进程太多还是uninterruptible sleep进程太多,也就无法判断是CPU不够用还是IO设备有瓶颈。
平均负载与CPU使用率
平均负载是指单位时间内,处于可运行状态和不可中断状态的进程数,所以,他不仅包扩了正在使用CPU的进程,还包括等待CPU和等待I/O的进程。
而CPU使用率,是单位时间内CPU繁忙情况的统计,和平均负载并不一定完全对应。
CPU密集型进程,使用大量CPU会导致平均负载升高,此时这两者是一致的。
I/O 密集型进程, 等待I/O也会导致平均负载升高,但是CPU使用率不一定很高。
大量等待CPU的进程调用也会导致平均负载升高,此时的CPU使用率也会比较高。
如果把系统负载比作一条高速公路,进程比作车,CPU使用率高只能说车跑的快,和高速公路是否拥挤(负载是否过高)没有关系。
四、CPU使用率低负载高的原因分析
一句话总结就是:等待磁盘I/O完成的进程过多,导致进程队列长度过大,但是CPU运行的进程却很少。
什么是负载
负载就是CPU在一段时间内正在处理以及等待CPU处理的进程数之和的统计信息,也就是CPU使用队列的长度统计信息,这个数字越小越好如果超过CPU核心*0.7系统就会不稳定。
负载分为两大部分:
CPU负载
例如,假设有一个进行大规模科学计算的程序,虽然该程序不会频繁地从磁盘输入输出,但是处理完成需要相当长的时间。
因为该程序主要被用来做计算、逻辑判断等处理,所以程序的处理速度主要依赖于CPU的计算速度。
此类CPU负载的程序称为“计算密集型程序”。
IO负载
还有一类程序,主要从磁盘保存的大量数据中搜索找出任意文件。这个搜索程序的处理速度并不依赖于CPU,而是依赖于磁盘的读取速度,也就是输入输出input/output,I/O.磁盘越快,检索花费的时间就越短。此类I/O负载的程序,称为“I/O密集型程序”。
什么是多任务操作系统
Linux操作系统能够同时处理几个不同名称的任务。但是同时运行多个任务的过程中,CPU和磁盘这些有限的硬件资源就需要被这些任务程序共享。即便很短的时间间隔内,需要一边在这些任务之间进行切换到一边进行处理,这就是多任务。
运行中的任务较少的情况下,系统并不是等待此类切换动作的发生。但是当任务增加时,例如任务A正在CPU上执行计算,接下来如果任务B和C也想进行计算,那么就需要等待CPU空闲。也就是说,即便是运行处理某任务,也要等到轮到他时才能运行,此类等待状态就表现为程序运行延迟。
什么是进程调度
进程调度也被一些人称为CPU上下文切换意思是:CPU切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态:当前运行任务转为就绪或者挂起、中断状态,另一个被选定的就绪任务成为当前任务。
进程调度包括保存当前任务的运行环境,恢复将要运行任务的运行环境。
在linux内核中,每一个进程都存在一个名为“进程描述符”的管理表。
该进程描述符会调整为按照优先级降序排序,已按合理的顺序运行进程任务。这个调整即为进程调度器的工作。
进程的状态区别:
状态 | 说明 |
---|---|
运行态running | 只要CPU空闲,任何时候都可以运行 |
可中断睡眠interruptible | 为恢复时间无法预测的长时间等待状态。如,来自于键盘设备的输入。 |
不可中断睡眠:uninterruptible | 主要为短时间时的等待状态。例如磁盘输入输出等待。被IO阻塞的进程。 |
就绪态runnable | 响应暂停信号而运行的中断状态。 |
僵死态zombie | 进程由父进程创建并销毁;在父进程中没有销毁子进程被销毁的时候,子进程就会转变为僵死态。 |
CPU低而负载高也就是说等待磁盘I/O完成的进程过多,就会导致队列长度过大,这样就体现到负载过大了,但实际是此时CPU被分配去执行别的任务或空闲,具体场景有如下几种。
场景一:磁盘读写请求过多就会导致大量I/O等待
CPU的工作效率要高于磁盘,而进程在CPU上面运行需要访问磁盘文件,这个时候CPU会向内核发起调用文件的请求,让内核去磁盘取文件,这个时候会切换到其他进程或者空闲,这个任务就会转换为不可中断睡眠状态。当这种读写请求过多就会导致不可中断睡眠状态的进程过多,从而导致负载高,CPU低的情况。
场景二:MySQL中存在没有索引的语句或存在死锁等情况
我们都知道MySQL的数据是存储在硬盘中,如果需要进行sql查询,需要先把数据从磁盘加载到内存中。
当在数据特别大的时候,如果执行的sql语句没有索引,就会造成扫描表的行数过大导致I/O阻塞,或者是语句中存在死锁,也会造成I/O阻塞,从而导致不可中断睡眠进程过多,导致负载过大。具体解决方法可以在MySQL中运行show full processlist命令查看线程等待情况,把其中的语句拿出来进行优化。
场景三:外接存储故障,常见有挂了NFS,但是NFS server故障
比如系统挂载了外接硬盘如NFS共享存储,经常会有大量的读写请求去访问NFS存储的文件,如果这个时候NFS服务器故障,那么就会导致进程读写请求一直获取不到资源,从而进程一直是不可中断状态,造成负载很高。
五、负载分析工具
这里会用到2个工具,stress和sysstat stress是一个Linux系统压力测试工具,这里我们用作异常进程模拟平均负载升高的场景。
sysstat是一个linux性能工具,用来监控和分析系统的性能,以下案例中会用到这个包的2个命令mpstat和pidstat。
- mpstat 是一个常用的多核CPU性能分析工具用来实时查看每个CPU的性能指标,一级所有CPI的平均指标。
- pidstat 是一个常用的进程性能分析工具,用来实时查看进程的CPU、内存、I/O以及上下文切换等性能指标。
# CPU 一个核心满载运行 600s
stress --cpu 1 --timeout 600
# 查看 CPU 各个核心的负载情况,每隔 5s 刷新一次
mpstat -P ALL 5
# 报告 CPU 利用率,间隔 5s ,展示 1 次
pidstat -u 5 1
# 模拟IO负荷
stress -i 1 --timeout 600
六、CPU过高排查思路
1、定位到占用CPU高的进程
top (SHIFT + P 按 CPU 使用率排序)
2、查看进程中的线程CPU情况
top -Hp $进程ID
# 找到CPU高的线程 $thread_id
3、线程ID转换为十六进制
printf "%x\n" $thread_id
4、从堆栈定位代码块
jstack $进程ID |grep $十六进制 -A 30