uptime
每次发现系统变慢时,我们通常做的第一件事情,就是执行top
或者uptime
命令,来了解系统的负载情况,比如:
oceanstar@oceanstar:~$ uptime
22:59:50 up 3 min, 1 user, load average: 0.96, 0.95, 0.42
- 当前时间 : 22:59:50
- 系统已运行的时间: up 3 min
- 当前在线用户: 1 user
- 平均负载: 0.96, 0.95, 0.42,最近1分钟、5分钟、15分钟系统的平均负载( load average)
平均负载可以说是最常见,也是最重要的系统指标。那么什么是平均负载呢?
什么是平均负载
平均负载,是指单位时间内,系统处于可运行状态和不可中断状态的平均进程树,也就是平均活跃进程数,他和CPU使用率并没有直接关系
- 所谓可运行状态的进程,是指正在使用CPU和正在等待CPU的进程,也就是
ps
命令看到的,处于R状态(Running、Runnable)的进程 - 所谓不可中断状态的进程,是指正处于内核态关键流程,并且这些流程是不可打断的
- 比如最常见的是等待硬件设备的IO响应,也就是
ps
命令看到的处于D状态(Uninterruptible Sleep,也称为Disk Sleep)的进程 - 比如,当一个进程向磁盘读写数据时,为了保证数据的一致性,在得到磁盘回复前,它是不能被其他进程或者中断的,这个时候的进程就处于不可中断的状态。如果此时的进程被打断了,就容易出现磁盘数据和进程数据不一致的情况。
- 所以,不可中断状态实际上是系统对进程和硬件设备的一种保护机制
- 比如最常见的是等待硬件设备的IO响应,也就是
因此,你可以理解为,平均负载其实就是平均活跃进程数。平均活跃进程数,直观上的理解就是单位时间内的活跃进程数,但它实际上是活跃进程数的指数衰减平均值。这个“指数衰减平均”的详细含义不用去计较,这只是系统的一种更快速的计算方式,把它直接当初活跃进程数的平均值即可。
既然平均的是活跃进程数量,那么最理想的是,每个CPU上都刚好运行着一个进程,这样每个CPU都得到了充分利用。比如,当平均负载为2时,意味着什么呢?
- 在只有2个CPU的系统上,意味着所有的CPU都刚好被完全占用
- 在4个CPU的系统上,意味着CPU有50%的空闲
- 在只有1个CPU的系统上,意味着有一半的进程竞争不到CPU
平均负载为多少时合理
问题是:怎么判断出,在
uptime
命令的结果里,那三个时间段的平均负载数,多大的时候能说明系统负载高?多小的时候能说明系统负责很低呢?
我们知道,平均负载最理想的情况是等于CPU个数。所以在评判瓶颈负载的时候,第一步是需要知道系统有几个CPU,这可以通过top
命令或者从/proc/cpuinfo
中读取,比如:
$ grep 'model name' /proc/cpuinfo | wc -l
4
有了CPU个数,我们就能判断出,当平均负载比CPU个数大的时候,系统已经出现了过载
第二个问题:平均负载有三个值,到底应该参考哪一个呢?
实际上,都要看。打个⽐⽅,就像初秋时北京的天⽓,如果只看中午的温度,你可能以为还在7⽉份的⼤夏天呢。但如果你结合了早上、中午、晚上三个时间点的温度来看,基本就可以全⽅位了解这⼀天的天⽓情况了
这上不同时间间隔的平均值,可以让我们分析系统负载趋势:
- 如果1分钟、5分钟、15分钟的三个值基本相同,或者相差不⼤,那就说明系统负载很平稳。
- 但如果1分钟的值远⼩于15 分钟的值,就说明系统最近1分钟的负载在减少,⽽过去15分钟内却有很⼤的负载。
- 反过来,如果1分钟的值远⼤于 15 分钟的值,就说明最近1分钟的负载在增加,这种增加有可能是临时性的,也有可能还会持续增加下去,所以就需要持续观察。一旦1分钟的平均负载接近或者超过了CPU的个数,就意味着系统正在发生过载的问题,这时就得分析调查是哪里导致的问题,并要想办法优化
举个例子,假设我们在一个单CPU系统上看到平均负载为1.73、0.60、7.98,那么说明在过去1分钟内,系统有73%的超载,而在15分钟内,有698%的超载,从整体趋势来看,系统的负载在降低。
第三个问题:在实际生成环境中,平均负载多高时,需要我们中断关注呢?
当平均负载高于CPU数量70%的时候,你就应该分析排查负载高的问题了。一旦负载过高,就可能导致进程响应变慢,进而影响服务的正常功能
但70%这个数字并不是绝对的,最推荐的⽅法,还是把系统的平均负载监控起来,然后根据更多的历史数据,判断负载的变化
趋势。当发现负载有明显升⾼趋势时,⽐如说负载翻倍了,你再去做分析和调查。
平均负载 VS CPU使用率
- 平均负载是指单位时间内,出于可运行状态和不可中断状态的进程数。所以,它不仅包括正在使用CPU的进程,还包括等待CPU和等待IO的进程
- CPU使用率,是指单位时间内CPU繁忙时间的统计,跟平均负载不一定完全对应,比如:
- CPU密集型进程,使用大量CPU会导致平均负载升高,此时这两者是一致的
- IO密集型进程,等待IO也会导致平均负载升高,但CPU使用率不一定很高
- 大量等待CPU的进程调度也会导致平均负载升高,此时的CPU使用率也会比较高
案例
下⾯,我们以三个示例分别来看这三种情况,并⽤ iostat、mpstat、pidstat 等⼯具,找出平均负载升⾼的根源。
准备
- 机器配置:4 CPU,8GB 内存
- 预先安装 stress 和 sysstat 包,如 apt install stress sysstat。
stress
:是一个Linyx系统压力测试工具,这里我们用作异常进程模拟平均负载升高的场景sysstat
包含了常用的Linux性能⼯具,⽤来监控和分析系统的性能。我们的案例会⽤到这个包的两个命令 mpstat 和pidstat。mpstat
是⼀个常⽤的多核 CPU 性能分析⼯具,⽤来实时查看每个 CPU 的性能指标,以及所有CPU的平均指标。pidstat
是⼀个常⽤的进程性能分析⼯具,⽤来实时查看进程的 CPU、内存、I/O 以及上下⽂切换等性能指标
此外,每个场景都需要你开三个终端,登录到同⼀台 Linux 机器中。
另外要注意,下⾯的所有命令,我们都是默认以 root ⽤户运⾏。
如果上⾯的要求都已经完成了,你可以先⽤ uptime 命令,看⼀下测试前的平均负载情况:
$ uptime
... load average: 0.04, 0.02, 0.00
场景一:CPU密集型进程
⾸先,我们在第⼀个终端运⾏ stress 命令,模拟⼀个 CPU 使⽤率 100% 的场景:
# --cpu 1: 指的是测试1个cpu --- 产生1个进程 每个进程都反复不停的计算随机数的平方根
# -t --timeout 600 : 指定运行600秒后停止
# stress测试cpu,是在用户态将cpu 耗尽。
$ stress --cpu 1 --timeout 600
stress: info: [3629] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd
接着,在第⼆个终端运⾏uptime查看平均负载的变化情况:
# -d 参数表示⾼亮显示变化的区域
$ watch -d uptime
..., load average: 1.00, 0.75, 0.39
最后,在第三个终端运⾏mpstat查看 CPU 使⽤率的变化情况:
# -P ALL 表示监控所有CPU,后⾯数字5表示间隔5秒后输出⼀组数据
$ mpstat -P ALL 5
Linux 5.8.0-48-generic (oceanstar) 2021年10月20日 _x86_64_ (4 CPU)
23时58分58秒 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
23时59分03秒 all 25.15 0.00 0.00 0.00 0.00 0.05 0.00 0.00 0.00 74.80
23时59分03秒 0 0.00 0.00 0.00 0.00 0.00 0.20 0.00 0.00 0.00 99.80
23时59分03秒 1 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
23时59分03秒 2 0.80 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 99.20
23时59分03秒 3 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
从终端二可以看到,1分钟的平均负载会慢慢增加到1.00,而从终端三中还可以看到,正好有一个CPU的使用率为100%,但是它的iowait只有0。这说明,平均负载的升高正是由于CPU使用率为100%
那么,到底是哪个进程导致了 CPU 使⽤率为 100% 呢?你可以使⽤ pidstat
来查询:
# 间隔5s后输出一组数据,-u表示CPU指标
$ pidstat -u 5 1
Linux 5.8.0-48-generic (oceanstar) 2021年10月21日 _x86_64_ (4 CPU)
19时28分26秒 UID PID %usr %system %guest %wait %CPU CPU Command
19时28分31秒 0 679 0.00 0.20 0.00 0.00 0.20 3 vmtoolsd
19时28分31秒 1000 2187 0.40 1.00 0.00 0.40 1.40 3 Xorg
19时28分31秒 1000 2318 1.20 0.80 0.00 0.20 2.00 1 gnome-shell
19时28分31秒 1000 2659 0.60 0.20 0.00 0.20 0.80 3 gnome-terminal-
19时28分31秒 0 8133 0.20 0.00 0.00 0.00 0.20 1 mpstat
19时28分31秒 0 8153 100.00 0.00 0.00 0.00 100.00 2 stress
19时28分31秒 1000 8231 0.00 0.20 0.00 0.00 0.20 0 pidstat
平均时间: UID PID %usr %system %guest %wait %CPU CPU Command
平均时间: 0 679 0.00 0.20 0.00 0.00 0.20 - vmtoolsd
平均时间: 1000 2187 0.40 1.00 0.00 0.40 1.40 - Xorg
平均时间: 1000 2318 1.20 0.80 0.00 0.20 2.00 - gnome-shell
平均时间: 1000 2659 0.60 0.20 0.00 0.20 0.80 - gnome-terminal-
平均时间: 0 8133 0.20 0.00 0.00 0.00 0.20 - mpstat
平均时间: 0 8153 100.00 0.00 0.00 0.00 100.00 - stress
平均时间: 1000 8231 0.00 0.20 0.00 0.00 0.20 - pidstat
从这里可以明显看到,stress进程的CPU使用率为100%
场景二:IO密集型进行
首先开始运行stress命令,但这次模拟IO压力,即不停的执行sync:
$ stress -i 1 --timeout 600
stress: info: [8830] dispatching hogs: 0 cpu, 1 io, 0 vm, 0 hdd
还是在第二个终端指针uptime查看平均负载的变化情况:
$ watch -d uptime
load average: 1.06, 0.90, 0.64
然后在第三个终端运行upstat查看CPU使用率变化情况:
# -P ALL 表示监控所有CPU,后⾯数字5表示间隔5秒后输出⼀组数据
$ mpstat -P ALL 5
19时38分29秒 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
19时38分34秒 all 1.30 0.00 24.96 0.00 0.00 0.00 0.00 0.00 0.00 73.73
19时38分34秒 0 0.00 0.00 0.20 0.00 0.00 0.00 0.00 0.00 0.00 99.80
19时38分34秒 1 0.81 0.00 0.81 0.00 0.00 0.00 0.00 0.00 0.00 98.39
19时38分34秒 2 3.41 0.00 96.39 0.00 0.00 0.00 0.00 0.00 0.00 0.20
19时38分34秒 3 1.00 0.00 2.40 0.00 0.00 0.00 0.00 0.00 0.00 96.61
从这⾥可以看到,1 分钟的平均负载会慢慢增加到 1.06,其中⼀个 CPU 的系统CPU使⽤率升⾼到了 96.39 ,疑惑:为什么这里的iowait还是0,感觉这次CPU的升高是由于系统调用引起的????????
回答:
- iowait无法升高的问题:是因为stress使用的是sync()系统调用,它的作用是刷新缓冲区内存到磁盘中。对于新安装的虚拟机,缓冲区可能比较小,无法产生大的IO压力,这样大部分就是系统调用的消耗了,所以你会看到只有系统CPU使用率升高。
- 解决方法:使用stress的下一代stress-ng,它支持更丰富的选项,比如stress-ng -i --hdd 1 --tiimeout 600 (–hdd表示读写临时文件)
看一下到底是哪个进程导致了CPU使用率急剧上升:
# 间隔5s后输出一组数据,-u表示CPU指标
$ pidstat -u 5 1
Linux 5.8.0-48-generic (oceanstar) 2021年10月21日 _x86_64_ (4 CPU)
19时41分28秒 UID PID %usr %system %guest %wait %CPU CPU Command
19时41分33秒 0 679 0.20 0.00 0.00 0.00 0.20 3 vmtoolsd
19时41分33秒 1000 2187 0.00 0.40 0.00 0.00 0.40 0 Xorg
19时41分33秒 1000 2318 0.40 0.40 0.00 0.40 0.80 1 gnome-shell
19时41分33秒 1000 2470 0.00 0.20 0.00 0.00 0.20 3 vmtoolsd
19时41分33秒 1000 2659 0.00 0.20 0.00 0.20 0.20 0 gnome-terminal-
19时41分33秒 0 2898 0.00 0.20 0.00 0.00 0.20 2 kworker/2:1-events
19时41分33秒 0 8831 3.59 96.21 0.00 0.00 99.80 2 stress
19时41分33秒 1000 9422 0.00 0.20 0.00 0.00 0.20 1 pidstat
平均时间: UID PID %usr %system %guest %wait %CPU CPU Command
平均时间: 0 679 0.20 0.00 0.00 0.00 0.20 - vmtoolsd
平均时间: 1000 2187 0.00 0.40 0.00 0.00 0.40 - Xorg
平均时间: 1000 2318 0.40 0.40 0.00 0.40 0.80 - gnome-shell
平均时间: 1000 2470 0.00 0.20 0.00 0.00 0.20 - vmtoolsd
平均时间: 1000 2659 0.00 0.20 0.00 0.20 0.20 - gnome-terminal-
平均时间: 0 2898 0.00 0.20 0.00 0.00 0.20 - kworker/2:1-events
平均时间: 0 8831 3.59 96.21 0.00 0.00 99.80 - stress
平均时间: 1000 9422 0.00 0.20 0.00 0.00 0.20 - pidstat
从上面可以看到,还是因为stress
场景三:大量进程的场景
当系统中运行进程超出CPU运行能力时,就会出现等待CPU的进程。
首先开始运行stress命令,但这次模拟了16个进程:
$ stress -c 16 --timeout 600
stress: info: [10197] dispatching hogs: 16 cpu, 0 io, 0 vm, 0 hdd
由于系统只有4个CPU,明显比16个进程要少得多。因此,系统的CPU处于严重过载的状态(6.67):
$ uptime
19:50:26 up 1:47, 1 user, load average: 6.67, 2.08, 1.13
# mpstat -P ALL 5
Linux 5.8.0-48-generic (oceanstar) 2021年10月21日 _x86_64_ (4 CPU)
19时51分03秒 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
19时51分08秒 all 98.88 0.00 1.02 0.00 0.00 0.10 0.00 0.00 0.00 0.00
19时51分08秒 0 98.38 0.00 1.62 0.00 0.00 0.00 0.00 0.00 0.00 0.00
19时51分08秒 1 98.98 0.00 0.82 0.00 0.00 0.20 0.00 0.00 0.00 0.00
19时51分08秒 2 99.19 0.00 0.81 0.00 0.00 0.00 0.00 0.00 0.00 0.00
19时51分08秒 3 98.99 0.00 0.81 0.00 0.00 0.20 0.00 0.00 0.00 0.00
接在运行pidstat查看进程的情况:
# 间隔5s后输出一组数据,-u表示CPU指标
$ pidstat -u 5 1
Linux 5.8.0-48-generic (oceanstar) 2021年10月21日 _x86_64_ (4 CPU)
19时51分46秒 UID PID %usr %system %guest %wait %CPU CPU Command
19时51分51秒 0 11 0.00 0.20 0.00 0.00 0.20 0 rcu_sched
19时51分51秒 0 679 0.20 0.00 0.00 0.00 0.20 1 vmtoolsd
19时51分51秒 1000 2187 0.80 4.38 0.00 0.60 5.18 3 Xorg
19时51分51秒 1000 2318 0.40 0.80 0.00 1.99 1.20 3 gnome-shell
19时51分51秒 1000 2659 0.60 3.59 0.00 0.00 4.18 0 gnome-terminal-
19时51分51秒 0 10198 23.90 0.00 0.00 75.90 23.90 1 stress
19时51分51秒 0 10199 23.90 0.20 0.00 76.10 24.10 0 stress
19时51分51秒 0 10200 23.51 0.40 0.00 75.70 23.90 0 stress
19时51分51秒 0 10201 23.51 0.20 0.00 75.90 23.71 2 stress
19时51分51秒 0 10202 25.30 0.40 0.00 74.30 25.70 3 stress
19时51分51秒 0 10203 31.08 0.20 0.00 68.33 31.27 1 stress
19时51分51秒 0 10204 22.71 0.00 0.00 76.69 22.71 1 stress
19时51分51秒 0 10205 23.11 0.80 0.00 76.10 23.90 0 stress
19时51分51秒 0 10206 28.49 0.40 0.00 70.72 28.88 3 stress
19时51分51秒 0 10207 22.31 0.20 0.00 77.49 22.51 1 stress
19时51分51秒 0 10208 22.51 0.40 0.00 77.49 22.91 3 stress
19时51分51秒 0 10209 23.90 0.20 0.00 76.10 24.10 3 stress
19时51分51秒 0 10210 22.71 0.40 0.00 76.89 23.11 2 stress
19时51分51秒 0 10211 22.31 0.00 0.00 77.49 22.31 2 stress
19时51分51秒 0 10212 22.11 0.40 0.00 77.29 22.51 0 stress
19时51分51秒 0 10213 22.71 0.00 0.00 77.29 22.71 2 stress
19时51分51秒 1000 10219 0.00 0.20 0.00 0.00 0.20 1 pidstat
可以看出,16个进程在争抢4个CPU,每个进程等待CPU的时间(%wait列)高达75%。这些超出CPU计算能力的进程,最终导致CPU过载
pidstat输出中没有%wait的问题:
- 因为centos默认的sysstat稍微有点老,源码或者RPM升级到11.5.5版本之后就可以看到了。
- 而unbuntu中的包一般都比较新,没有这个问题
mpstat无法观测的问题:
- 案例中是等待5s后输出1次结果就停止了,更好的做法是持续监控一段时间,比如持续观测20次: mpstat -P ALL 5 20
总结
平均负载提供了一个快速查看系统整体性能的手段,反映了整体的负载情况。但只看平均负载本身,我们并不能直接发现,到底是哪里出了问题。所以,在理解平均负载时,也要注意:
- 平均负载高有可能是CPU密集型进程导致的
- 平均负载高并不一定代码CPU使用率高,还可能是IO更繁忙了
- 当发现负载高的时候,你可以使用mpstat、pidstat等工具,辅助分析负载的来源
其他
mpstat
语法:
mpstat [-P {cpu|ALL}] [internal [count]]
其中,各参数含义如下:
参数 | 含义 |
---|---|
-P {cpu l ALL} | 表示监控哪个CPU, cpu在[0,cpu个数-1]中取值 |
internal | 相邻的两次采样的间隔时间 |
count | 采样的次数,count只能和delay一起使用 |
输出参数含义
参数 | 释义 | 从/proc/stat获得数据 |
---|---|---|
CPU | 处理器ID | |
%usr | 在internal时间段里,用户态的CPU时间(%),不包含 nice值为负进程 | usr/total*100 |
%nice | 在internal时间段里,nice值为负进程的CPU时间(%) | nice/total*100 |
%sys | 在internal时间段里,核心时间(%) | system/total*100 |
%iowait | 在internal时间段里,硬盘IO等待时间(%) | iowait/total*100 |
%irq | 在internal时间段里,硬中断时间(%) | irq/total*100 |
%soft | 在internal时间段里,软中断时间(%) | softirq/total*100 |
%steal | 显示虚拟机管理器在服务另一个虚拟处理器时虚拟CPU处在非自愿等待下花费时间的百分比 | steal/total*100 |
%guest | 显示运行虚拟处理器时CPU花费时间的百分比 | guest/total*100 |
%gnice | gnice/total*100 | |
%idle | 在internal时间段里,CPU除去等待磁盘IO操作外的因为任何原因而空闲的时间闲置时间(%) | idle/total*100 |
CPU总的工作时间:
total_cur = user + system + nice + idle + iowait + irq + softirq
total_pre = pre_user + pre_system + pre_nice + pre_idle + pre_iowait + pre_irq + pre_softirq
user = user_cur – user_pre
total = total_cur - total_pre
其中_cur 表示当前值,_pre表示interval时间前的值。上表中的所有值可取到两位小数点。
实例
直接使用mpstat命令:
mpstat #当mpstat不带参数时,输出为从系统启动以来的平均值。
mpstat -P ALL 5 2 #表示每5秒产生一个报告,总共产生2个
上图表示每5秒产生了2个关于处理器的统计数据报告,一共产生2个interval 的信息,然后再给出这2个interval的平均信息。默认时,输出是按照CPU 号排序。第一个行给出了从系统引导以来的所有活跃数据。接下来每行对应一个处理器的活跃状态。