heroyf @ heroyf.com

cpu 平均负载解析

Jun 8 · 15minlinux性能优化

当发现系统变慢时,我们通常做的第一件事,就是执行 top 或者 uptime 命令,来了解系统的负载情况。比如像下面这样,我在命令行里输入了 uptime 命令,系统也随即给出了结果。

$ uptime
 18:01:31 up 680 days,  3:09,  4 users,  load average: 0.01, 0.05, 0.10

前面几列含义

18:01:31              //当前时间
up 680 days,  3:09    //系统运行时间
4 user                //正在登录用户数

而最后三个数字呢,依次则是过去 1 分钟、5 分钟、15 分钟的平均负载(Load Average)。

平均负载的定义

有人会说,平均负载不就是单位时间内的 CPU 使用率吗?上面的 0.63,就代表 CPU 使用率是 63%。其实并不是这样。可以通过man uptime命令,文档中做出了如下解释

Note

System load averages is the average number of processes that are either in a runnable or uninterruptable state. A process in a runnable state is either using the CPU or waiting to use the CPU. A process in uninterruptable state is waiting for some I/O access, eg waiting for disk. The averages are taken over the three time intervals. Load averages are not normalized for the number of CPUs in a system, so a load average of 1 means a single CPU system is loaded all the time while on a 4 CPU system it means it was idle 75% of the time.

简单来说,平均负载是指单位时间内,系统处于可运行状态不可中断状态的平均进程数,也就是平均活跃进程数,它和 CPU 使用率并没有直接关系。这里我先解释下,可运行状态和不可中断状态这俩词。

  1. 可运行状态的进程,是指正在使用 CPU 或者正在等待 CPU 的进程,也就是我们常用 ps 命令看到的,处于 R 状态(Running 或 Runnable)的进程。

  2. 不可中断状态的进程则是正处于内核态关键流程中的进程,并且这些流程是不可打断的,比如最常见的是等待硬件设备的 I/O 响应,也就是我们在 ps 命令中看到的 D 状态(Uninterruptible Sleep,也称为 Disk Sleep)的进程。

    比如,当一个进程向磁盘读写数据时,为了保证数据的一致性,在得到磁盘回复前,它是不能被其他进程或者中断打断的,这个时候的进程就处于不可中断状态。如果此时的进程被打断了,就容易出现磁盘数据与进程数据不一致的问题。所以,

因此,你可以简单理解为,平均负载其实就是平均活跃进程数。平均活跃进程数,直观上的理解就是单位时间内的活跃进程数,但它实际上是活跃进程数的指数衰减平均值。这个 "指数衰减平均" 的详细含义你不用计较,这只是系统的一种更快速的计算方式,你把它直接当成活跃进程数的平均值也没问题。

既然平均的是活跃进程数,那么最理想的,就是每个 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
8

三个不同时间间隔的平均值,其实给我们提供了分析系统负载趋势的数据来源,让我们能更全面、更立体地理解目前的负载状况。

  • 如果 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% 这个数字并不是绝对的,最推荐的方法,还是把系统的平均负载监控起来,然后根据更多的历史数据,判断负载的变化趋势。当发现负载有明显升高趋势时,比如说负载翻倍了,你再去做分析和调查。

平均负载与CPU使用率

那么平均负载和CPU使用率的差异点到底在哪儿呢?

可能你会疑惑,既然平均负载代表的是活跃进程数,那平均负载高了,不就意味着 CPU 使用率高吗?

我们还是要回到平均负载的含义上来,平均负载是指单位时间内,处于可运行状态和不可中断状态的进程数。所以,它不仅包括了正在使用 CPU 的进程,还包括等待 CPU等待 I/O 的进程。

而 CPU 使用率,是单位时间内 CPU 繁忙情况的统计,跟平均负载并不一定完全对应。比如:

  • 对于CPU密集型的程序,使用大量 CPU 会导致平均负载升高,此时这两者是一致的;
  • I/O 密集型进程,等待 I/O 也会导致平均负载升高,但 CPU 使用率不一定很高;
  • 大量等待 CPU 的进程调度也会导致平均负载升高,此时的 CPU 使用率也会比较高。

平均负载案例分析

利用 iostat、mpstat、pidstat 等工具,找出平均负载升高的根源。

环境工具准备

  • 机器配置:2 CPU,8GB 内存
  • 预先安装 stress 和 sysstat 包,如 apt/yum install stress sysstat

sysstat 包含了常用的 Linux 性能工具,用来监控和分析系统的性能。我们的案例会用到这个包的两个命令 mpstat 和 pidstat。

  • mpstat 是一个常用的多核 CPU 性能分析工具,用来实时查看每个 CPU 的性能指标,以及所有 CPU 的平均指标。
  • pidstat 是一个常用的进程性能分析工具,用来实时查看进程的 CPU、内存、I/O 以及上下文切换等性能指标。

场景一:CPU密集型

模拟一个CPU使用率100%的情况

stress --cpu 1 --timeout 600

另一个终端运行uptime查看平均负载的变化情况

# -d 参数表示高亮显示变化的区域
$ watch -d uptime
...,  load average: 1.00, 0.75, 0.39

通过mpstat查看CPU使用率的变化情况

# -P ALL 表示监控所有CPU,后面数字5表示间隔5秒后输出一组数据
[root@VM-16-17-opencloudos ~]# mpstat -P ALL 5

mpstat输出结果

从终端二中可以看到,1 分钟的平均负载会慢慢增加到 1.00,而从终端三中还可以看到,正好有一个 CPU 的使用率为 100%,但它的 iowait 只有 0。这说明,平均负载的升高正是由于 CPU 使用率为 100% 。

那么,到底是哪个进程导致了 CPU 使用率为 100% 呢?你可以使用 pidstat 来查询:

# 间隔5秒后输出一组数据
[root@VM-16-17-opencloudos ~]# pidstat -u 5 1

pidstat输出结果

从这里可以明显看到,stress 进程的 CPU 使用率为 100%。

场景二:I/O密集型场景

模拟I/O压力,不停地执行sync:

$ stress -i 2 --timeout 600

通过第二个终端运行uptime查看平均负载的变化情况

$ watch -d uptime
...,  load average: 1.06, 0.58, 0.37

第三个终端运行mpstat查看CPU使用率的变化情况

# 显示所有CPU的指标,并在间隔5秒输出一组数据
$ mpstat -P ALL 5 1

mpstat输出结果

从这里可以看到,1 分钟的平均负载会慢慢增加到 1.06,其中一个 CPU 的系统 CPU 使用率升高到了 23.87,而 iowait 高达 53.80 %。这说明,平均负载的升高是由于 iowait 的升高。

Note

ps: 这里的iowait可能会出现无法升高的场景,是因为案例中stress使用的是 sync() 系统调用,它的作用是刷新缓冲区内存到磁盘中。对于新安装的虚拟机,缓冲区可能比较小,无法产生大的IO压力,这样大部分就都是系统调用的消耗了。所以,你会看到只有系统CPU使用率升高。解决方法是使用stress的下一代stress-ng,它支持更丰富的选项,比如 stress-ng -i 1 --hdd 1 --timeout 600(–hdd表示读写临时文件)。

那么到底是哪个进程,导致 iowait 这么高呢?我们还是用 pidstat 来查询:

# 间隔5秒后输出一组数据
[root@VM-16-17-opencloudos ~]# pidstat -u 5 1

pidstat输出结果

大量进程的场景

当系统中运行进程超出 CPU 运行能力时,就会出现等待 CPU 的进程。比如,我们还是使用 stress,但这次模拟的是 8 个进程:

$ stress -c 8 --timeout 600

由于系统只有2个CPU,明显比8个进程要少的多,因此CPU处于严重过载的情况

18:58:43 up 158 days, 21:38,  3 users,  load average: 6.69, 3.54, 2.70

通过pidstat查看进程情况

# 间隔5秒后输出一组数据
[root@VM-16-17-opencloudos ~]# pidstat -u 5 1

pidstat输出结果

可以看出,8 个进程在争抢 2 个 CPU,每个进程等待 CPU 的时间(也就是代码块中的 %wait 列)高达 75%。这些超出 CPU 计算能力的进程,最终导致 CPU 过载。

小结

平均负载提供了一个快速查看系统整体性能的手段,反映了整体的负载情况。但只看平均负载本身,我们并不能直接发现,到底是哪里出现了瓶颈。所以,在理解平均负载时,也要注意:

  • 平均负载高有可能是CPU密集型进程导致的
  • 平均负载高并不一定代表 CPU 使用率高,还有可能是 I/O 更繁忙了
  • 当发现负载高的时候,你可以使用 mpstat、pidstat 等工具,辅助分析负载的来源。