平均负载
1. 理解平均负载
平均负载对很多人来说既熟悉又陌生,那我们如何理解和观测这个最常见、也是最重要的系统指标呢?
平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数量,也就是平均活跃进程数。
可运行状态:指正在使用或者正在等待 CPU 的进程,也就是我们常用 ps 命令看到的,处于 R 状态(Running 或 Runnable)的进程。
不可中断状态:指的是正处于内核态关键流程中的进程,并且这些流程是不可打断的,比如最常见的是等待硬件设备的 I/O 响应,也就是我们在 ps 命令中看到的 D 状态(Uninterruptible Sleep,也称为 Disk Sleep)的进程。不可中断状态实际上是系统对进程和硬件设备的一种保护机制,比如当一个进程向磁盘写入数据时,在得到磁盘回复之前,它是不能被其他进程或者中断打断的,此时进程就处于不可中断状态,否则会处于磁盘数据和进程数据不一致的情况。
因此,你可以简单的将平均负载理解为平均活跃进程数量,那么最理想的状态,就是每个CPU上刚好运行一个进程,这样每个CPU都得到了充分利用。在实际的情况中,平均负载为多少时合理呢?多大说明系统负载高?多小说明负载很低呢?
一般情况下,当平均负载高于CPU数量的70%的时候,你就应该分析排查负载高的问题了,一旦负载变高,就可能导致进程响应变慢,进而影响正常的功能,当然这个数字并不是绝对的,最推荐的方式,还是监控系统的平均负载,然后根据更多的历史数据,判断负载的变化趋势,当发现负载有明显的升高趋势时,再去分析和调查。
现实工作中,我们会把平均负载和CPU使用率混淆,让我们重新回顾一下平均负载的概念:平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数量。也就是说它不仅包含正在使用CPU的进程,还包括等待CPU和等待IO的进程。而CPU使用率表示单位时间内,非空闲状态所占的比例,比如:
-
CPU密集型,使用大量的CPU会使得平均负载升高。 -
IO密集型,等待IO也会导致平均负载升高,但是此时CPU使用率不一定高。 - 大量等待
CPU进程调度也会导致平均负载升高,此时的CPU使用率也会比较高。
2. 平均负载分析
在分析平均负载之前,先简单的介绍一下 sysstat,它包含了常用的Linux 性能工具,用来监控和分析系统的性能,我们会用到其中的 mpstat 和 pidstat。
mpstat是一个常用的CPU性能分析工具,用来实时查看每个CPU的性能指标, 以及所有CPU的平均指标。
pistat是一个常用的进程性能分析工具,用来实时查看进程的CPU、内存、IO以及上下文切换等性能指标。
在分析平均负载之前,我们可以通过 lscpu 命令查看当前机器的CPU信息。
$ lscpu
CPU(s): 1
...
1. 场景一: CPU密集进程
- 通过
uptime命令查看平均负载的变化情况(过去 1 分钟、5 分钟、15 分钟),由下图终端的结果来看,平均负载时逐步增高的。
$ watch -d uptime
..., load average: 1.00, 0.75, 0.39
- 通过
mpstat查看CPU使用率的变化情况,由下图执行结果来看,有个线程的CPU占用率为100%, 但是iowait只有 0 ,说明平均负载的升高是因为CPU的利用率较高。
$ mpstat -P ALL 5 1
08:42:30 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
08:42:35 PM all 0.50 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
08:42:35 PM 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
08:42:35 PM 1 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
- 通过
pidstat查看进程的性能指标如下,我们可以看到到底为哪个进程导致CPU利用率升高。
$ pidstat -u 5 1
08:51:25 PM UID PID %usr %system %guest %CPU CPU Command
08:51:31 PM 1002 1024 100.00 0.00 0.00 100.00 1 ***
分析步骤总结
- 确定服务器的核心数量,通过
uptime命令观察平均负载的趋势走向。 - 通过
mpstat命令服务器的运行情况, 包括cpu利用率、io等待、用户态、系统态、争用CPU所占时间 等各个指标所占比例等等。 - 通过
pidstat命令查看哪个进程所占的cpu或者iowait高,又或者是系统运行进程超出了cpu的运行能力, 导致cpu过载。
CPU上下文切换
Linux 是一个多任务操作系统,它支持远大于 CPU 数量的任务同时运行。当然,这些任务实际上并不是真的在同时运行,而是因为系统在很短的时间内,将 CPU 轮流分配给它们,而每个人任务执行之前,CPU都必需知道该任务所需的数据从哪里加载,又从哪里开始运行(CPU寄存器和程序计数器)。CPU上下文切换实际上就是把前一个任务的上下文保存起来,然后加载当前正在准备执行的任务的上下文到寄存器和计数器中,运行新任务。
实际CPU的上下文切换可以分为几个不同的场景,进程上下文切换、线程上下文切换以及中断上下文切换。
进程上下文切换
Linux 特权等级,将进程的运行空间分为内核空间和用户空间,内核空间具有最高的访问权限;而用户空间只能访问受限的资源,不能直接访问内存等硬件设备,必需通过系统调用陷入内核中,才能访问这些特权资源。从用户态到内核态的转变,是通过系统调用完成的。比如我们的程序访问文件内容就需要进行系统调用,首先通过系统调用打开文件,将内容读取到内核空间,拷贝到用户空间之后才能被程序处理。
由此可见,系统调用的时候会发生了CPU上下文切换。 CPU寄存器里原来用户态程序的上下文信息需要先保存起来,为了执行内核态代码,加载内核代码的上下文,运行内核任务,系统调用结束之后,需要切换到用户空间。所以,一次系统调用,会发生两次CPU上下文切换。
系统调用过程中,并不会涉及到虚拟内存等进程用户态的资源,也不会切换进程。这跟我们通常所说的进程上下文切换是不一样的,进程上下文切换指的是从一个进程切换到另一个进程,而系统调用一直是一个进程,所以通常被称为特权模式切换,而不是上下文切换。
进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以,进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。因此进程上下文切换比系统调用多了一步,保存当前进程的内核状态和CPU寄存器之前,需要先把该进程的虚拟内存、栈等保存起来。
究竟什么时候会切换进程上下文呢?
- 操作系统为了保证公平调度,
CPU时间被划分为一段段的时间片,然后轮流分配给各个进程,当某个进程的时间片耗尽之后,会被系统挂起,切换到其他等待CPU的进程运行。 - 进程在资源不足的情况下,要等到资源满足之后才可以运行,这个时候也会被挂起,由系统调度其他进程。
- 进程通过
sleep函数将自己主动挂起。 - 优先级较高的进程运行时,为了保证高优先级的先被运行,当前进程会被挂起。
- 发生硬件中断时,
CPU进程会中断挂起,转而执行内核中的中断服务进程。
线程上下文切换
线程是调度的基本单位,而进程则是资源拥有的基本单位。内核中的任务调度,实际调度的对象时线程,而进程只是给线程提供了虚拟内存、全局变量等资源。
- 当进程只有一个线程时,进程等于线程。
- 当进程有多个线程时,全局变量等线程公共资源是不用修改的,而线程自己的私有数据,比如栈和寄存器,也是需要保存的。
如果为第一种,线程上下文切换过程和进程切换相同,对于第二种,由于不需要保存公共资源,所以同进程内的线程切换,要比多进程间的切换消耗更小。
中断上下文切换
为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件,而在打断进程的正常调度之前,需要保存当前进程的状态。跟进程上下文不同,中断并不涉及进程的用户态,所以不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。
对同一个 CPU 来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生。由于中断会打断正常进程的调度和执行,所以大部分中断处理程序都短小精悍,以便尽可能快的执行结束,但是中断上下文切换也需要消耗 CPU,切换次数过多也会耗费大量的 CPU,甚至严重降低系统的整体性能。所以,当你发现中断次数过多时,就需要注意去排查它是否会给你的系统带来严重的性能问题。
查看上下文切换情况
过多的上下文切换,会将资源耗费在寄存器、内核栈以及虚拟内存的数据的保存和恢复上,缩短进程真正的执行时间,而上下文的切换包括两种:
- 自愿上下文切换:指进程无法获取所需要的资源,导致的上下文切换,比如
IO、内存等系统资源不足。 - 非自愿上下文切换:指的是进程的时间片已经到期,被系统强制调度发生。
我们主要通过vmstat查询系统的上下文切换情况, 主要特别关注以下四列内容:
-
r: 就绪队列的长度,表示运行或者等待CPU的进程数量。 -
b:表示不可中断睡眠状态的进程数。 -
in:每秒中断次数。 -
cs: 每秒上下文切换的次数。
场景: 案例分析
- 首先通过
vmstat工具 观察系统的上下文切换情况:
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
11 0 0 323460 140952 1159544 0 0 2 8 74 65 1 1 99 0 0
9 0 0 323460 140952 1159544 0 0 0 0 1550 942091 39 61 0 0 0
关注内容:上下文切换次数:由 65 升高为 942091;有序队列的长度 8 已经远远超过了系统的CPU个数(2);CPU使用率(us + sy)已经到达了 100%;中断次数也提升到了 1550 次。
综合这些指标我们可以知道系统的就绪队列过长,导致大量的上下文切换,而上下文切换又导致了CPU 占用率的升高。
- 通过
pidstat查看CPU和进程上下文切换情况:
$ pidstat -w -u 1
02:22:06 PM UID PID %usr %system %guest %wait %CPU CPU Command
02:22:07 PM 0 1058 1.00 0.00 0.00 0.00 1.00 0 AliYunDun
02:22:07 PM 0 17861 38.00 61.00 0.00 0.00 99.00 0 sysbench
02:22:06 PM UID PID cswch/s nvcswch/s Command
02:22:07 PM 0 11 1.00 0.00 ksoftirqd/0
02:22:07 PM 0 12 42.00 0.00 rcu_sched
02:22:07 PM 0 22 2.00 0.00 kcompactd0
02:22:07 PM 0 145 5.00 0.00 load_calc
02:22:07 PM 32 498 1.00 0.00 rpcbind
02:22:07 PM 1003 569 1.00 0.00 memcached
02:22:07 PM 0 732 1.00 0.00 php-fpm
02:22:07 PM 1002 790 10.00 0.00 redis-server
02:22:07 PM 0 1058 11.00 0.00 AliYunDun
02:22:07 PM 0 1120 31.00 0.00 AliYunDunMonito
02:22:07 PM 0 5201 10.00 0.00 AliSecGuard
02:22:07 PM 0 17678 1.00 0.00 sshd
02:22:07 PM 0 17857 1.00 0.00 kworker/u4:1-events_unbound
02:22:07 PM 0 17860 6.00 0.00 kworker/0:1-events
02:22:07 PM 0 17890 1.00 0.00 pidstat
从输出发现,CPU使用率的升高是因为 sysbench导致的,但是上下文切换是来自其他的进程,不过图中输出的上下文切换次数,明显比 942091 小的多,讲到的几种上下文切换场景。其中提到, Linux 调度的基本单位实际上是线程,那么,是不是 pidstat 忽略了线程的数据呢?通过 man pidstat 的描述,实际上默认展示的是进程数据,所以我们需要再加上 -t 参数:
$ pidstat -wt 1
02:30:37 PM UID TGID TID cswch/s nvcswch/s Command
02:30:39 PM 0 - 17944 12110.71 74812.50 |__sysbench
02:30:39 PM 0 - 17945 13327.68 71582.14 |__sysbench
02:30:39 PM 0 - 17946 10166.96 76762.50 |__sysbench
02:30:39 PM 0 - 17947 5872.32 88392.86 |__sysbench
02:30:39 PM 0 - 17948 14217.86 68983.93 |__sysbench
……
虽然 sysbench 进程的上下文切换次数不多,但是它的子进程的上下文次数却又很多,所以罪魁祸首就是过多的 sysbench 线程。那四个关注指标中的中断次数又如何解释呢?到底是什么类型的中断上升了呢?
我们可以通过 /proc/interrupts 这个只读文件中读取中断发生的类型,通过下列命令观察中断的变化情况:
$ watch -d cat /proc/interrupts
分析步骤和总结
1. 通过 vmstat 查看服务的总体状态,包括CPU 使用率、上下文切换次数、有序队列和中断次数(四个关键指标)。
2. 通过 pidstat 查看进程的上下文切换情况,定位到进程或者线程。
3. 通过查看/proc/interrupts 文件确定中断发生的类型和次数。
自愿上下文切换次数变多,说明进程在等待资源,有可能发生了 io或者其他问题;非自愿上下文切换次数变多,说明进程都在争抢CPU,说明CPU变成了瓶颈;中断次数变多,说明 CPU被中断处理程序占用,需要查看/proc/interrupts 文件分析具体的中断类型。
CPU达到100%应该怎么办
如何查看CPU使用率
查看CPU的使用率,第一反应肯定是top和 ps工具:
-
top:现实系统总体的CPU和内存情况,以及各个进程的资源使用情况。 -
ps:显示每个进程的资源使用情况。
比如下列就为top命令的输出情况:
$ top
top - 15:29:09 up 3 days, 19:13, 2 users, load average: 0.02, 0.02, 0.49
Tasks: 135 total, 3 running, 132 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.0 us, 6.7 sy, 0.0 ni, 93.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 1887.8 total, 304.7 free, 300.5 used, 1282.5 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 1400.1 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 104512 12152 9112 S 0.0 0.6 0:04.57 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.02 kthreadd
其中第三行就为系统的 CPU使用率,不过默认展示的所有 CPU的平均值,只需要按下数字 1,就可以切换到每个CPU的使用率了。下方空白行之后是进程的实时信息,每个进程都有一个 %CPU列,标识该进程的 CPU使用情况,不过 top工具并没有细分进程的用户态CPU还是内核态CPU,可以使用 pidstat工具查看更加详细的信息。
$ pidstat 1 5
进程号 用户态 系统态 运行虚拟机 等待CPU 总的
03:40:28 PM UID PID %usr %system %guest %wait %CPU CPU Command
03:40:29 PM 0 1058 1.00 0.00 0.00 0.00 1.00 0 AliYunDun
03:40:29 PM 0 1120 1.00 0.00 0.00 0.00 1.00 0 AliYunDunMonito
03:40:29 PM 0 21088 0.00 1.00 0.00 0.00 1.00 0 pidstat
CPU使用率过高怎么办
通过top、ps、pidstat等工具,可以轻松的找到CPU使用率较高的进程,那如何找到占用 CPU到底是代码里的哪个函数呢?可以通过 perf 工具来完成,它是一个基于性能事件采样为基础,不仅可以分析系统的各种事件和内核性能,还可以分析指定应用程序的性能问题。
CPU使用率过高,但是无法找到高CPU的应用
系统的CPU使用率,不仅包含进程用户态和内核态的运行,还包括中断处理、等待IO以及内核线程等,所以当系统的CPU过高时,不一定能找到对应的高CPU使用率的进程。
所以,如果通过top命令发现系统的整体CPU命令很高,但是无论是根据下方进程列表的展示还是通过其他的工具(pidstat)查看,都找不到高CPU使用率的进程时,有可能为下面这些情况:
- 应用里直接调用了其他二进制程序,这些程序的通常运行时间比较短,通过
top工具也不容易发现。 - 应用本身在不停的崩溃重启,而启动过程的资源初始化,很可能会占用相当多的
CPU。 - 这些进程都为短时进程,运行很短的时间就会结束,很难通过
top这种间隔时间较长的工具发现。
分析步骤和总结
1. 通过top命令,观察就绪队列中的进程数量(第二行tasks中running字段的值)和下方展示的进程列表中运行态的进程数量是否存在较大的误差。
2. 通过pidstat命令查看可能出现问题的活跃进程的信息。
3. 观察top命令的输出,下方的进程列表中某个进程的pid是否不断变化。
4. 通过pstree命令,找出该进程的父进程,然后进入app进行内部分析,寻找不断创建子进程的原因所在。
在得到猜测的结果之后,我们需要验证该猜测到底是否正确,所以我们可以使用perf工具或者专门为短时进程设计的工具execsnoop来验证猜想。execsnoop通过ftrace实时监控进程的exec()行为,并输出短时进程的基本信息,包括进程pid、父进程pid、命令行参数以及执行的结果。
系统中出现了大量的不可中断进程和僵尸进程
在top命令的 s(进程状态)列中包含以下几种状态:
d: 不可中断睡眠(uninterruptible sleep), 表示进程正在和硬件交互,不允许被其他进程打断。
I: 空闲(idle)。
R: 运行(running)。
S: 可中断睡眠(sleep),表示进程正在等待某个事件而被系统挂起。当进程等待的事件发生时,它会被唤醒并进入R状态。
Z: 僵尸进程 (Zombile),表示进程已经结束,但是父进程还没有回收它的资源(比如进程描述符、PID等)
T/t: 忽略
僵尸进程是多进程应用容易碰到的问题。正常情况下,当一个进程创建了子进程后,它应该通过系统调用 wait() 或 waitpid() 等待子进程结束,回收子进程的资源;而在子进程结束时,会向父进程发送SIGCHLD信号,所以父进程还可以注册SIGCHLD信号的处理函数,异步回收资源。
如果父进程没有处理子进程的状态,子进程就提前退出,这是子进程就会变成僵尸进程。通常情况下,僵尸进程的持续时间较短,在父进程回收它的资源就会消亡;或者由init进程回收后也会消亡。
一旦父进程没有处理子进程的终止,还一直保持运行状态,那么子进程就会一直处于僵尸状态。大量的僵尸进程会用尽PID进程号,导致不能创建新进程。
分析步骤和总结:
大量不可中断进程
- 通过
top命令,观察平均负载、进程各个状态的数量、CPU使用率、iowait等指标。 - 通过
dstat查看iowait升高时,磁盘读写情况。 - 通过
top命令观察处于D(不可中断)状态的所有进程。 - 通过
pidstat命令分析进程的读写情况,如果该读写较高时,着重分析该进程的调用堆栈。
僵尸进程
- 通过
pstree命令找出僵尸进程的父进程。 - 着重分析该进程的运行情况。