前言

当前我们如果碰到系统变慢的情况,最直观的方法就是看uptime/top查看系统平均负载,那么如何根据平均负载最终排查出最终的祸源呢?

平均负载

平均负载,具体是指系统指单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数,它和 CPU 使用率并没有直接关系。

  • 可运行状态:正在使用或等待CPU的进程,即使用ps命令看到的处于R状态的进程
  • 不可中断状态:正处于内核关键流程中的进程,并且这些流程是不可打断的,即ps命令看到的D状态的进程

那么对于平均进程数,理想的状态就是每个CPU上都有一个运行的进程。

查看当前机器有几个CPU可以通过命令grep 'model name' /proc/cpuinfo当关注到平均负载的数量已经大于CPU的个数的时候,系统这时候就已经出现了过载。

  • 如果 1 分钟、5 分钟、15 分钟的三个值基本相同,或者相差不大,那就说明系统负载很平稳。
  • 但如果 1 分钟的值远小于 15 分钟的值,就说明系统最近1 分钟的负载在减少,而过去 15 分钟内却有很大的负载。
  • 反过来,如果 1 分钟的值远大于 15 分钟的值,就说明最近1 分钟的负载在增加,这种增加有可能只是临时性的,,也有可能还会持续增加下去,所以就需要持续观察。一旦 1 分钟的平均负载接近或超过了 CPU 的个数,就意味着系统正在发生过载的问题,这时就得分析调查是哪里导致的问题,并要想想办法优化了。

平均负载与CPU使用率的关系、

平均负载可以理解为平均活跃的进程数,那就包括正在使用 CPU 的进程,还包括等待 CPU 和等待 I/O的进程

CPU使用率主要包括单位时间内CPU的繁忙程度

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

检测系统负载的工具

  • uptime: 当前时间系统运行时间正在登录用户数、过去1分钟5分钟15分钟的平均负载
[root@k8s-master ~]# uptime
 21:31:07 up 11:33,  2 users,  load average: 0.09, 0.07, 0.06
  • mpstat
# -P ALL 表示监控所有 CPU,后面数字 5 表示间隔 5 秒后输出一组数据
[root@k8s-master ~]# mpstat -P ALL 5
Linux 3.10.0-957.27.2.el7.x86_64 (k8s-master)   2019年10月09日     _x86_64_    (2 CPU)

21时33分07秒  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
21时33分12秒  all    0.30    0.00    0.30    0.00    0.00    0.00    0.00    0.00    0.00   99.40
21时33分12秒    0    0.60    0.00    0.40    0.00    0.00    0.00    0.00    0.00    0.00   99.00
21时33分12秒    1    0.00    0.00    0.20    0.00    0.00    0.00    0.00    0.00    0.00   99.80
  • pidstat
# 间隔 5 秒输出一组数据,-u表示 CPU 指标
[root@k8s-master ~]# pidstat -u 5 1
Linux 3.10.0-957.27.2.el7.x86_64 (k8s-master)   2019年10月09日     _x86_64_    (2 CPU)

21时35分16秒   UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
21时35分21秒     0       705    0.20    0.00    0.00    0.00    0.20     0  vmtoolsd
21时35分21秒    27      1448    0.00    0.20    0.00    0.00    0.20     0  mysqld
21时35分21秒     0     41577    0.00    0.20    0.00    0.00    0.20     0  kworker/0:2
21时35分21秒     0     45159    0.00    0.20    0.00    0.00    0.20     1  pidstat

针对不同case的选择

CPU密集型case

mpstat -P ALL 5 可以看到CPU的%USER会很高,但是%iowait较低
pidstat 定位到具体是哪个进程

IO密集型

mpstat -P ALL 5 这是会观察到%iowait和%user都很高
通过pidstat -d查看

大量进程case

通过pidstat -u 观察%wait的值,较高的进程很多,说明大量进程处于非资源上下文切换,同样会影响平均负载

上下文切换

现实情况中,我们最多遇到的是有大量的进程占用CPU的情况,为何进程等待CPU的调度也会导致平均负载的上升呢?那么主要的原因就是CPU的上下文切换

Linux 是一个多任务操作系统,它支持远大于 CPU 数量的任务同时运行。当然,这些任务实际上并不是真的在同时运行,而是因为系统在很短的时间内,将 CPU 轮流分配给它们,造成多任务同时运行的假象。

也就是说每当任务之前,CPU 都需要知道任务从哪里加载、又从哪里开始运行,也就是说,需要系统事先帮它设置好 CPU 寄存器和程序计数器。也就是CPU在运行任务之前需要以来的环境叫做CPU上下文。

通俗来说CPU 上下文切换,就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

根据任务的不同,分为进程切换上下文,线程切换上下文,和中断切换上下文

进程切换上下文

Linux根据特权等级,分为用户态和内核态,分别对应着Ring0和Ring3

  • Ring0可以访问所有资源
  • Ring3则必须通过系统调用,才能访问受限的资源,例如内存等硬件设备

用户态往内核态的转变,需要通过系统调用来实现,比如,当我们查看文件内容时,就需要多次系统调用来完成:首先调用 open() 打开文件,然后调用 read() 读取文件,并调用 write() 将内容写到标准输出,最后再调用 close() 关闭文件。

系统调用可以成为单进程的CPU上下文切换,首先将用户态的的指令位置进行保存,然后将寄存器更新为内核态指令的新位置,接着再执行内核态的任务。
所以,一次系统调用的过程,其实是发生了两次 CPU 上下文切换

对于进程上下文切换,由于进程所有的调度都是通过内核来调度完成的,所以进程的切换只发生在内核态。所以,进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。

因此,进程的上下文切换就比系统调用时多了一步:在保存当前进程的内核状态和 CPU 寄存器之前,需要先把该进程的虚拟内存、栈等保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。

所以对于每次上下文切换对会消耗几十纳秒到微秒的时间,如果出现大量的上下文切换,会导致CPU消耗大量的时间在寄存器、内存栈等资源的保存和恢复上,真正进程运行的时间其实会很短。同时虚拟内存的更新会导致TLB的更新,内存的访问也会变慢,对于多核处理器,缓存被多个核心所共享,可能还会影响其他核心的处理能力。

总结进程上下文切换

  • 系统调用(特权模式切换):一个进程用户态与内核态之间的互相转变
  • 上下文切换:从一个进程切换到另一个进程运行
    • 虚拟内存、栈、全局变量等进程用户态资源
    • 内核堆栈、寄存器等进程内核态资源

CPU会根据进程的等待时间和优先级安排进程执行的先后顺序。触发进程调度的情况会分为以下几种情况

  • 为了保证所有进程可以得到公平调度,CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,就会被系统挂起,切换到其它正在等待 CPU 的进程运行。
  • 进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行。
  • 当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度。
  • 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行。
  • 发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序。

线程上下文切换

线程与进程最大的区别在于,线程是调度的基本单位,而进程则是资源拥有的基本单位。说白了,所谓内核中的任务调度,实际上的调度对象是线程;而进程只是给线程提供了虚拟内存、全局变量等资源。

  • 当进程只有一个线程时,可以认为进程就等于线程。
  • 当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。
  • 另外,线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。

线程上下文的切换分为以下两种情况
- 前后两个线程属于不同进程。此时,因为资源不共享,所以切换过程就跟进程上下文切换是一样。
- 前后两个线程属于同一个进程。此时,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据。

所以对于进程的切换,同进程的不同线程的上下文切换的消耗是要小于进程的上下文切换的。

中断上下文切换

中断只发生在内核态,所以对于中断引起的上下文切换,会打断进程的正常调度和执行,同时它即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。

对于同一个CPU,中断处理比进程拥有更高的优先级。所以中断上下文切换并不会与进程上下文切换同时发生。中断处理同样会消耗CPU,当出现大量的中断时,我们需要关注是否会给进程带来影响,甚至引起严重的性能问题。

检测上下文切换的工具

  • vmstat
    • 重点关注4列内容
      • cs(context switch) 表示每秒上下文切换的次数
      • in(interrupt)表示每秒中断次数
      • r(Running or Runnable)表示就绪队列的长度,也就是正在运行和等待CPU的进程数
      • b(Blocked)表示处于不可中断睡眠状态的进程数
# 每 1 秒输出一组数据
[root@k8s-master ~]# 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
 1  0            0      2350460         2080      1123024    0    0     8     3  256  359   1   1  98   0   0
 0  0            0      2350460         2080      1123024    0    0     0     0   65  120   0   0 100   0   0
 0  0            0      2350460         2080      1123024    0    0     0     2   82  142   0   0 100   0   0
 0  0            0      2350460         2080      1123024    0    0     0     0   66  122   0   0 100   0   0
 0  0            0      2350460         2080      1123024    0    0     0     0   70  128   1   0 100   0   0
 0  0            0      2350460         2080      1123024    0    0     0     0   63  121   0   0 100   0   0
  • pidstat -w
    • 需要特别关注两列内容
      • cswch 表示每秒自愿上下文切换的次数
      • nvcswch 表示每秒非自愿上下文切换的次数
        • 自愿上下文切换:进程无法获取资源,IO、内存等资源不足
        • 非自愿上下文切换:进程由于时间片已到,被系统强制调度,例如大量进程争抢CPU
# 每隔 5 秒输出一组数据 -w 表示输出进程切换指标 -t 可以输出线程的上下文切换指标
[root@k8s-master ~]# pidstat -w 5
Linux 3.10.0-957.27.2.el7.x86_64 (k8s-master)   2019年10月09日     _x86_64_    (2 CPU)

21时59分23秒   UID       PID   cswch/s nvcswch/s  Command
21时59分28秒     0         1      0.80      0.00  systemd
21时59分28秒     0         3      0.40      0.00  ksoftirqd/0
21时59分28秒     0         9      6.99      0.00  rcu_sched
21时59分28秒     0        11      0.40      0.00  watchdog/0
21时59分28秒     0        12      0.40      0.00  watchdog/1
21时59分28秒     0        13      0.20      0.00  migration/1
21时59分28秒     0        14      0.40      0.00  ksoftirqd/1
21时59分28秒     0        98      0.60      0.00  kauditd
21时59分28秒     0       358     19.76      0.00  xfsaild/sda3
21时59分28秒     0       431      0.80      0.00  systemd-journal
21时59分28秒     0       666      0.60      0.00  auditd
21时59分28秒     0       668      0.60      0.00  audispd
21时59分28秒     0       670      0.60      0.00  sedispatch
21时59分28秒     0       699      0.80      0.00  rngd
21时59分28秒     0       705     10.58      0.00  vmtoolsd
21时59分28秒     0       714      0.40      0.00  abrt-watch-log
21时59分28秒     0       725      0.40      0.00  systemd-machine
21时59分28秒     0       731      0.40      0.00  systemd-logind
21时59分28秒    81       745      0.80      0.00  dbus-daemon
21时59分28秒     0       797      0.20      0.00  kworker/1:1H
21时59分28秒     0      3704      1.60      0.00  kworker/u256:2
21时59分28秒     0     21128      0.20      0.00  packagekitd
21时59分28秒     0     34728      0.40      0.00  sshd
21时59分28秒     0     45446      2.00      0.00  kworker/0:1
21时59分28秒     0     45742      2.59      0.00  kworker/1:0
21时59分28秒     0     46328      0.40      0.00  kworker/1:3
21时59分28秒     0     46429      0.20      0.00  pidstat
  • /proc/interrupts
[root@k8s-master ~]# watch -d cat /proc/interrupts
Every 2.0s: cat /proc/interrupts                                                                                                                                                                                                                                 Wed Oct  9 22:03:01 2019

            CPU0       CPU1
   0:        102          0   IO-APIC-edge  timer
   1:         11        631   IO-APIC-edge  i8042
   8:          1          0   IO-APIC-edge  rtc0
   9:          0          0   IO-APIC-fasteoi   acpi
  12:         17       2093   IO-APIC-edge  i8042
  14:          0          0   IO-APIC-edge  ata_piix
  15:        108      42473   IO-APIC-edge  ata_piix
  16:          4          0   IO-APIC-fasteoi   vmwgfx, snd_ens1371
  17:      12354      19967   IO-APIC-fasteoi   ehci_hcd:usb1, ioc0
  18:        144          0   IO-APIC-fasteoi   uhci_hcd:usb2
  19:      55986       8336   IO-APIC-fasteoi   eno16777736
  24:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  25:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  26:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  27:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  28:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  29:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  30:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  31:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  32:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  33:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  34:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  35:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  36:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  37:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  38:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  39:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  40:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  41:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  42:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  43:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  44:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  45:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  46:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  47:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  48:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  49:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  50:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  51:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  52:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  53:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  54:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  55:          0          0   PCI-MSI-edge  PCIe PME, pciehp
  56:       4376       2456   PCI-MSI-edge  vmw_vmci
  57:          0          0   PCI-MSI-edge  vmw_vmci
 NMI:          0          0   Non-maskable interrupts
 LOC:    3690103    4034641   Local timer interrupts
 SPU:          0          0   Spurious interrupts
 PMI:          0          0   Performance monitoring interrupts
 IWI:      24112      42486   IRQ work interrupts
 RTR:          0          0   APIC ICR read retries
 RES:    6940647    6995327   Rescheduling interrupts
 CAL:       6256       5802   Function call interrupts
 TLB:      72279      67144   TLB shootdowns
 TRM:          0          0   Thermal event interrupts
 THR:          0          0   Threshold APIC interrupts
 DFR:          0          0   Deferred Error APIC interrupts
 MCE:          0          0   Machine check exceptions
 MCP:        147        147   Machine check polls
 ERR:          0
 MIS:          0
 PIN:          0          0   Posted-interrupt notification event
 NPI:          0          0   Nested posted-interrupt event
 PIW:          0          0   Posted-interrupt wakeup event

可以看到如果中断发生很多,RES(重调度中断)变化速度最快,这个中断类型表示唤醒相对空间状态CPU来调度新的任务运行。

根据上下文切换的类型做具体的分析

  • 自愿上下文切换变多,说明进程都在等待资源,有可能发生了IO、内存等其他问题
  • 非自愿上下文切换变多,说明大量进程在争抢CPU自愿,从而进程都在被CPU强制调度,说明CPU成为瓶颈
  • 中断次数变多,说明CPU被中断处理程序所占用,通过/proc/interrupts查看具体是哪种中断类型

版权声明:本文为原创文章,版权归 heroyf 所有。
本文链接: https://www.heroyf.club/2019/10/10/sysuptime/


“苹果是给那些为了爱选择死亡的人的奖励”