04 不可中断睡眠对容器的影响

一、重点知识回顾

储备知识1:linux进程状态

一、死了的进程:
进程在调用 do_exit() 退出的时候,有两个状态
(1)一个是 :EXIT_DEAD,也就是进程在真正结束退出的那一瞬间的状态;
(2)另外一个是:EXIT_ZOMBIE 状态,这是进程在 EXIT_DEAD 前的一个状态,即僵尸进程,

二、活着的进程
1、运行着:运行+就绪,统称TASK_RUNNING)
运行着的进程分为两种:一种是真的拿到cpu资源真的在运行,另外一种就是在运行队列里、随时可以运行,处于R状态的进程泛指这两种情况
2、睡眠着:
睡眠着是指,进程需要等待某种资源而进入的状态,要等的资源可以是一个信号量Semaphore), 或者是磁盘 I/O,这个状态的进程会被放入到 wait queue 队列里。

睡眠着的进程具体还包括两个子状态:
(1)一个是不可被打断的(TASK_UNINTERRUPTIBLE),用 ps 查看进程,就显示为 D stat。---》正在进行的io
什么时候进程会处于不可中断睡眠状态,例如,当一个进程向磁盘读写数据时,为了保证数据的一致性,在得到磁盘回复前,
它是不能被其他进程或者中断打断的,这个时候的进程就处于不可中断状态。如果此时的进程被打断了,就容易出现磁盘数据与进程数据不一致的问题。
所以,不可中断状态实际上是系统对进程和硬件设备的一种保护机制

(2)一个是可以被打断的(TASK_INTERRUPTIBLE),我们用 ps 查看到的进程,显示为 S stat。--》正在等待,
就好比是你网购完东西后你知道会有快递会送给你,但是什么时候送你根本不知道,那你肯定不必在原地傻等着

举例:
我们的程序有三行代码,运行到第二行要把一个视频文件读入内存,第三行才能处理,此时因为磁盘速度太慢了,导致io积压,
比如在虚拟机里压缩一个大文件,该压缩进程就有可能进入D状态,总之D与S是有本质区别的,S状态类似python中input接收输入的代码,
代表程序睡眠阻塞住了,什么时候有内容输入并不知道,而D状态代表明确正在做io,

储备知识2:关于cpu的使用情况有两个参考指标

1、关于cpu使用率:
某个用户进程对cpu的利用率 = 用户进程占用的cpu时间(包括用户态us+内核态sy) / cpu经历的这段总时间

进程对cpu的利用率为100%代表使用1颗cpu
进程对cpu的利用率为200%代表使用2颗cpu

如果宿主机只有4颗cpu,那么某个进程对cpu的利用率最多400%

2、关于load average:
在某段时间内平均活跃的进程数(包含系统处于可运行状态以及不可中断状态的平均进程数)

为何不可中断睡眠也属于活跃的进程
    一个进程内要做的事可以分为两大类
    1、计算任务---》cpu负责运行
    2、io任务-----》磁盘、网卡负责处理

    只要该进程正在被处理着,那它就属于活跃的进程
    cpu在执行该进程的计算机任务,肯定属于活跃
    磁盘在处理该进程的io任务,那肯定也属于活跃
    总之有事做就属于活跃

    而S状态,在等待用户输入内容,而此时用户什么也没有输,即io操作啥事也没做,计算任务也肯定没有
    整个进程就是不活跃的

如果你的物理机有1颗cpu,那么满负载为1,代表可以同时运行1个进程,超过1就代表超载,小于1就代表空闲

如果你的物理机有4颗cpu,那么满负载为4,代表可以同时运行4个进程,超过4则代表超载,小于4就代表空闲

以1颗cpu为例,如果处理器上有一个R的进程,同时在系统的进程可运行队列里有9个进程,那么1分钟的load average=1+9

如果宿主机有4颗cpu,那么平均负责是可以超过4的

3、结论
有可能会出现工作量很大,但是利用率很低,比如每个员工手里都有很多活要做,但实际上你问问每个活的进度是啥大家都告诉这些活都在进行着,但是都处于
等待的状态,那怒了,你傻啊,等待的过程你不会干别的事啊,员工也很冤枉,说不行啊老板,我这个io是不可中断的io,不能被中断
必须等着对方送过来数据才行,你别看我很闲,但是这件事确实是正在进行的事情

二、引入今日问题

我们可以使用cpu cgroup对容器的cpu资源进行限制,但是cpu cgroup并非是万能的,它无法限制Load Average,没有这个限制,会影响我们对资源的合理调度,进而导致系统变得很慢

例如:

有时你会遇到所有容器进程、以及宿主机的cpu使用率都很低,但是容器里的应用运行速度非常慢,并且查看宿主机的Load Average的平均负载会发现非常高。

这到底是因为什么导致的呢?
其实这与linux系统的Load Average的统计方式有关
在linux操作系统中,Load Average把不可中断睡眠状态的进程也当成是活跃的进程,并计算到了Load Average里
所以如果系统中有大量进程处于D状态,会导致Load Average增高,而D状态作为一种睡眠状态是不会消耗cpu的,所以cpu利用率会很低

问题展示

先来查看一下初始的load average,cpu的剩余很充足,load average也不高

然后我来模拟一下,cpu idle剩余率很多、cpu很充足,但是load average非常高

linux系统中也存在容易捕捉的TASK_UNINTERRUPTIBLE状态。
执行vfork系统调用后,父进程将进TASK_UNINTERRUPTIBLE状态,
直到子进程调用exit或exec 通过下面的代码就能得到处于TASK_UNINTERRUPTIBLE状态的进程:

[root@test04 ~]# cat test.c 
void main() {  
if (!vfork()) while (1) {sleep(1);};  
}
[root@test04 ~]# gcc -o test test.c 

[root@test04 ~]# for i in `seq 1 100`;do (./test &) ;done  # 启动100个D状态的进程

让子弹飞一会之后,再次查看你会发现cpu剩余率量大很充足,但是平均负载却很高

接下来问题就来

1、我们查看一个cpu的使用情况,到底是以cpu usage为准,还是以load average为准呢?

2、D状态的进程为何会导致load average升高呢?

三、Load Average的统计方式

top命令可以看到load average,分别对应1分钟、5分钟、15分钟的cpu平均负载

为何linux操作系统会把不可中断睡眠也计算到Load Average里
——————–》答案
linux起源于unix,早在unix系统里,就有load average的概念了,但是在unix系统里没有把不可中断睡眠D状态的进程数计算到Load Average里,而在linux系统里却把不可中断睡眠D状态的进程数计算到Load Average里了

  • 1、在大多数unix发行版里:
    load average = 正在运行的进程数 + 处于就绪的进程数(即已经存在于操作系统的进程可运行队列里,只要申请到cpu资源即可投入运行的进程)

  • 2、而在linux系统里:
    load average=正在运行的进程数 + 处于就绪的进程数 + 休眠队列中处于不可中断睡眠D状态的进程数

linux系统为何要这么做呢???
要解答这个疑问,你必须要搞明白Load Average这个指标到底要反应什么?

打个比方,
为了思考方便,先假设你的机器就一个cpu

  • 一个进程都是向前行进的一辆车
  • 既然一个进程可以被比喻为一辆往前行进的车,那负责运行(或者说承载)进程的cpu就相当于一条单行车道(同一时间只允许通行一辆车)
  • 而load average反应的就是整个系统的交通情况,具体是指所有条路的单位时间通过的车辆数(即活跃的进程数)
  • D状态的进程就好比是车辆在等待红灯,而且是不可中断的等,毫无疑问这辆车应该被认为是处于活跃状态,处于D状态,虽然不消耗cpu、不会统计到cpu占用率里,但是它一定是活跃的进程
  • S状态的进程就好比是车辆停在你加车库里呢

你仔细思考一下,如果我们要反应路况情况,肯定是需要把等待红灯的情况即D状态也考虑进去,这种统计更为科学,一条路的路况肯定是需要考虑红绿灯情况的,想想就明白了,哪怕有一条路很空(cpu利用率很低),但是红绿特别多,汽车也需要排队,不可能开快。如果这辆车是出租车你作为客户坐在车里的直观感受就是车开的很慢

所以说Load Average反应的是整个系统的任务量/负载量情况
而cpu利用率反应的则是每个cpu的利用情况

如果我们把整个系统比喻为一家公司,load average反应的就是这家公司的办事效率,而cpu反应的就是每个员工的偷懒程度,正常情况是,每个员工的偷懒很少、利用率高,同时这家公司的办事效率也高
不正常情况是,在开月底总结大会的时候,老板发现公司正在办的事很多,但是每个员工都很闲,此时可能的原因就是大量的事情都处于不可中断的等待过程中,这是需要优化的,不然哪怕这家公司每员工的能力都是超人pro,那这家公司的整体运作效率也不高

综上,如果 IO 设备出现瓶颈,势必会造成大量的进程处于等待 IO 的状态,这种情况下,虽然不关 CPU 什么事,整个系统的处理能力其实已经出现的很大的瓶颈,
所以把 D 状态的进程算在平均负载里也还算合理。当系统 load average 比较高时,首先我们需要去甄别,到底是 CPU 的问题还是 IO 的问题。

有4颗cpu,满负载任务数就为4,超了就要分析原因了,看看是否是D状态带来的影响。

因为当D状态过多,就可能程序出一种效果:所有进程的cpu占用率非常低,但是cpu的1分钟5分钟15分钟负载非常高,这个会影响k8s的调度

示例:在系统没有大量的D状态进程时,linux系统的load average的增长与cpu利用率趋于一致

[root@test04 ~]# cat libdead_loop.c 
void DeadLoop() {
     while (1) {
         ; 
     }
} 
[root@test04 ~]# gcc libdead_loop.c -fPIC -shared -o /opt/libdead_loop.so
[root@test04 ~]# cat test.py 
#coding:utf-8
from ctypes import cdll
from threading import Thread
import sys

if __name__ == "__main__":
    if not (len(sys.argv) == 2 and sys.argv[1].isdigit()):
        print("Usage: python xxx.py cpu个数")
        exit()

    num = int(sys.argv[1])
    lib = cdll.LoadLibrary("/opt/libdead_loop.so")
    for i in range(num):
        Thread(target=lib.DeadLoop).start()

[root@test04 ~]# python test.py 2 &
[1] 67657

我们启动一个占用2颗cpu的的进程,会发现一分钟负责为2.08与我们的cpu usage吻合,此外我们有4颗cpu,你占用2颗,所以总使用率为50%

但如果我们启动了大量的D进程,load average便不会与cpu usage一致,而是会超过它

四、D状态导致性能下降分析

联系管理员微信tutu19192010,注册账号

上一篇
下一篇
Copyright © 2022 Egon的技术星球 egonlin.com 版权所有 帮助IT小伙伴学到真正的技术