• Linux性能优化实战CPU篇(二)


     

    正文

    一、CPU使用率过高

    1,CPU使用率

    a>节拍率

      为了维护CPU时间,Linux通过事先定义的节拍率(内核中表示为HZ),触发时间中断,并使用全局变量Jiffies记录开机以来的节拍数。每发生一次时间中断,Jiffies的值就加1

      节拍率HZ是内核的可配置选项

    #查看当前系统的节拍率为每秒钟250次时间中断
    grep 'CONFIG_HZ=' /boot/config-$(uname -r)
    CONFIG_HZ=250

      同时内核还提供了一个用户空间节拍率USER_HZ,固定值为100,也就是1/100秒

    b>/proc虚拟文件系统

    复制代码
    cpu 2032004 102648 238344 167130733 758440 15159 17878 0
    cpu0 1022597 63462 141826 83528451 366530 9362 15386 0
    cpu1 1009407 39185 96518 83602282 391909 5796 2492 0
    intr 303194010 212852371 3 0 0 11 0 0 2 1 1 0 0 3 0 11097365 0 72615114 6628960 0 179 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    ctxt 236095529
    btime 1195210746
    processes 401389
    procs_running 1
    procs_blocked 0
    复制代码

      第一行的数值表示的是CPU总的使用情况,所以我们只要用第一行的数字计算就可以了。下表解析第一行各数值的含义:

    复制代码
    参数                    解析(单位:jiffies)
    user(2032004)          从系统启动开始累计到当前时刻,用户态的CPU时间,不包含 nice值为负进程。
    nice(102648)          从系统启动开始累计到当前时刻,nice值为负的进程所占用的CPU时间
    system (238344)        从系统启动开始累计到当前时刻,核心时间
    idle (167130733)       从系统启动开始累计到当前时刻,除IO等待时间以外其它等待时间
    iowait (758440)        从系统启动开始累计到当前时刻,IO等待时间
    irq (15159)            从系统启动开始累计到当前时刻,硬中断时间
    softirq (17878)        从系统启动开始累计到当前时刻,软中断时间
    复制代码

    c>CPU使用率

      CPU在t1到t2时间段即时利用率 =  1 - CPU空闲使用时间 / CPU总的使用时间

      CPU在t1到t2时间段空闲使用时间 = (idle2 - idle1)

      CPU在t1到t2时间段总的使用时间 = ( user2+ nice2+ system2+ idle2+ iowait2+ irq2+ softirq2) - ( user1+ nice1+ system1+ idle1+ iowait1+ irq1+ softirq1)

    2,如何查看CPU使用率

    a>top

      top默认使用3秒时间间隔,它显示了系统总体的CPU和内存使用情况,以及各个进程的资源使用情况

    复制代码
    top - 09:52:06 up 2 days, 53 min,  1 user,  load average: 0.38, 0.69, 0.88
    任务: 445 total,   1 running, 353 sleeping,   0 stopped,   4 zombie
    %Cpu(s):  2.3 us,  1.4 sy,  0.0 ni, 95.8 id,  0.0 wa,  0.0 hi,  0.5 si,  0.0 st
    KiB Mem : 16163124 total,   588480 free, 11530672 used,  4043972 buff/cache
    KiB Swap:  2097148 total,  1243900 free,   853248 used.  2415292 avail Mem 
    
    PID USER      PR  NI    VIRT    RES    SHR �  %CPU %MEM     TIME+ COMMAND                                                                                                                                 
    11775 mi        20   0 4872384 499200 134812 S  11.5  3.1  42:41.12 gnome-shell                                                                                                                             
    11318 mi        20   0 1355336 261508 226128 S   7.6  1.6  53:12.32 Xorg                                                                                                                                    
    21215 mi        20   0 3407932 604084  27340 S   6.6  3.7 135:50.48 WeChat.exe                                                                                                                              
    21220 mi        20   0   10068   6080    684 S   6.2  0.0 120:01.52 wineserver.real                                                                                                                         
     9586 mi        20   0  693000  36112  21156 S   4.6  0.2   0:19.49 gnome-terminal-                                                                                                                         
     5907 mi        20   0 13.246g 3.962g  29032 S   3.9 25.7 632:20.02 java 
    复制代码

      第三行%Cpu就是系统的CPU使用率,具体每列的含义与/proc类似

    b>ps

       ps (英文全拼:process status)命令用于显示当前进程的状态,进程的整个生命周期

    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  0.0  0.0 225968  8288 ?        Ss   2月07   1:57 /lib/systemd/systemd --system --deserialize 19
    root         2  0.0  0.0      0     0 ?        S    2月07   0:00 [kthreadd]

      具体含义

    复制代码
    USER: 行程拥有者
    PID: pid
    %CPU: 占用的 CPU 使用率
    %MEM: 占用的记忆体使用率
    VSZ: 占用的虚拟记忆体大小
    RSS: 占用的记忆体大小
    TTY: 终端的次要装置号码 (minor device number of tty)
    STAT: 该行程的状态:D: 无法中断的休眠状态 (通常 IO 的进程); R: 正在执行中; S: 静止状态; T: 暂停执行; Z: 不存在但暂时无法消除; W: 没有足够的记忆体分页可分配; <: 高优先序的行程; N: 低优先序的行程; L: 有记忆体分页分配并锁在记忆体内 (实时系统或捱A I/O)
    START: 行程开始时间
    TIME: 执行的时间
    COMMAND:所执行的指令
    复制代码

    c>pidstat

    pidstat [ 选项 ] [ <时间间隔> ] [ <次数> ]

      可以指定时间间隔来输出CPU使用率

    复制代码
    #每隔 1 秒输出一组数据,共输出 5 组
    mi@mi:~$ pidstat 1 5
    10时08分51秒   UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command
    10时08分52秒  1000       641    0.99    0.00    0.00    0.00    0.99     9  chrome
    10时08分52秒  1000       996    0.00    0.99    0.00    0.00    0.99     4  chrome
    10时08分52秒  1000      4387    0.99    0.99    0.00    0.00    1.98     0  pidstat
    10时08分52秒  1000      5907    3.96    0.00    0.00    0.00    3.96     2  java
    ...
    平均时间: UID PID %usr %system %guest %wait %CPU CPU Command
    平均时间: 0 11 0.00 0.20 0.00 0.00 0.20 - rcu_sched
    平均时间: 1000 548 0.20 0.20 0.00 0.00 0.40 - chrome
    平均时间: 0 1745 0.00 0.20 0.00 0.00 0.20 - kworker/u24:4-events_unbound
    平均时间: 0 2568 0.00 0.20 0.00 0.00 0.20 - kworker/u24:2-i915
    平均时间: 0 3545 0.00 0.20 0.00 0.00 0.20 - kworker/9:2-events
    复制代码
    • %usr 用户态CPU使用率

    • %system 内核态CPU使用率

    • %guest 运行虚拟机CPU使用率

    • %wait 等待CPU使用率

    • %CPU 总的CPU使用率

    • 最后的Average,则为计算了5组数据的平均值

    3,CPU使用率过高怎么办?

      perf 火焰图

    a>采用top、ps、pidstat等工具,找到CPU使用率较高的进程

    b>使用perf top 展示热点函数

      

      输出结果中,第一行包含三组数据,分别是采样数(Samples)、事件类型(event)和事件总数量(Event count)。比如当前perf采集了833个CPU时钟事件,总事件数为97742399。

      采样数需要尽可能的多,这样才有参考价值。解析表格样式的数据为:

    • Overhead,是该符号的性能事件在所有采样中的比例,用百分比来表示
    • Shared,是该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、进程名、动态链接库名、内核模块名等。
    • Object,是动态共享对象的类型。比如 [.] 表示用户空间的可执行程序、或者动态链接库,而  [k] 则表示内核空间
    • Symbol,函数名。当函数名未知时,用十六进制的地址来表示

      从上图可以看出CPU时钟最多的是perf工具自身,其比例也只有7.28%,说明系统并没有CPU性能问题。

    c>perf record 和 perf report

      perf top 虽然实时展示了系统的性能信息,但他的缺点是并不保存数据,无法用于离线或后续的分析

      perf record 则提供了保存数据的功能,保存后的数据,需要用perf report解析展示

    复制代码
    //按 ctrl + c 终止采样
    root@xc:~# perf record
     [ perf record: Woken up 1 times to write data ]
     [ perf record: Captured and wrote 3.679 MB perf.data (26683 samples) ]
    //展示类似于 perf top的报告
    root@xc:~# perf report
    复制代码

      在实际使用中,我们经常使用perf top 和 perf record 加上 -g 参数,开启调用关系的采样,方便调用链的分析

    4,案例一

      使用两台虚拟机,其中一台用作Web服务器,来模拟性能问题;另一台用作Web服务器的客户端,来给Web服务器增加压力请求。使用两台虚拟机是为了相互隔离,避免“交叉感染”。

      

    VM1上启动应用

      

    VM2上测试并压测

      

      启动成功,进行性能压测

      

      从ab输出结果可以看到,Nginx能承受的每秒平均请求数只有11.63。为了方便分析,我们将请求数增到10000,继续压测

      

    VM1上分析原因

      top,可以看到php-fpm的CPU使用率加起来将近200%;每个CPU的使用率均已超过98%

      

      perf top ,使用-g 开启调用关系分析, -p 指定 php-fpm的进程号 21515

      

      发现调用关系最终到sqrt和add_function出现占用的CPU时钟较高。

      

    5,案例二(定位CPU升高的应用)

      使用两台虚拟机,其中一台用作Web服务器,来模拟性能问题;另一台用作Web服务器的客户端

       

    VM1上启动应用

      

    VM2上测试并压测:

       

       启动成功,进行压测

      

      从ab结果可以看到,Nginx能承受的每秒平均请求数,只有87左右。接下来,将并发请求数改成5,延长请求时长为10分钟,方便vm1定位分析问题

      

    VM1上分析原因

      top分析,发现CPU使用最高的进程才2.7%,并不特别高。然而在CPU使用率(%Cpu),可以看到用户CPU使用率为80%,系统CPU为15.1%,而空闲CPU为2.8%

       

      pidstat,观察发现CPU的使用率也都不高

      

       

      再次采用top观察,发现Tasks中有个6个Running状态的进程,而观察咱们启动的php-fpm确都处于Sleep(S)状态,真正处于Running状态的时stress进程

                      

       采用pidstat进一步观察其中的一个stress进程,发现无输出

      

      采用ps查看这个stress进程,任然无输出。

       

      再次采用top命令查看,发现stress进程的进程号都已经发生变化

      

      分析原因

    • 进程在不断地崩溃重启,比如因为配置错误等,进程在退出后可能又被监控系统自动重启
    • 进程为短时进程,也就是在其他应用内部通过exec调用的外部命令。这些命令一般都只运行很短的时间就会结束,也很难通过top这种间隔时间较长的工具发现

      通过pstree查找真实的stress进程的父进程,这里可以看到stress是被php-fpm调用的子进程,并且进程数量不止一个(这里是3个)

      

       采用perf record -g命令,等待一会儿之后按Ctrl+C退出,再用perf report查看报告

      

                       

      发现 stress占用了CPU时钟事件的77%,而stress调用栈中比例较高的为函数random(),再进行具体的优化,就可以解决。

    6,execsnoop                     

      execsnoop-专门用于为追踪短时进程(瞬时进程)设计的工具;它通过 ftrace 实时监控进程的 exec() 行为,并输出短时进程的基本信息,包括进程 PID、父进程 PID、命令行参数以及执行的结果。

      github地址: https://github.com/brendangregg/perf-tools/blob/master/execsnoop

      如何安装使用:将上面的github的内容复制,然后写入execsnoop文件,并且加上x权限即可;

      

       可以发现大量的stress进程在不停启动

    二、僵尸进程      

    1,进程的状态

      以top为例,在top中有一列为S列(也就是Status列),可以查看到有T、D、Z、S、I等几个状态

    • R是Running或Runnable的缩写,表示进程在CPU的就绪队列中,正在运行或者正在等待运行
    • D是Disk Sleep的缩写,也就是不可中断状态睡眠(Uninterruptible Sleep),一般表示进程正在跟硬件交互,并且交互过程不允许被其他进程中断
    • Z是Zombie的缩写,它表示僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID等)
    • S是Interruptible Sleep的缩写,也就是可中断状态睡眠,表示进程因为等待某个事件而被系统挂起。当进程等待的事件发生时,它会被唤醒并进入R状态
    • I是Idle的缩写,也就是空闲状态,用在不可中断睡眠的内核线程上。前面说了,硬件交互导致的不可中断进程用D表示,但对某些内核线程来说,它们有可能实际上并没有任何负载,用Idle正是为了区分这种情况。因为,D状态的进程会导致平均负载升高,I状态的进程却不会
    • T(t)是Stopped或Traced的缩写,表示进程处于暂停或跟踪状态。
    • X是Dead的缩写,表示进程已经消亡,不能在top或ps中看到

      当一个进程创建了子进程后,它应该通过系统调用wait()或waitpid()等待子进程结束,回收子进程的资源;而子进程在结束时,会向它的父进程发送SIGCHLD信号,所以父进程也可以注册SIGCHLD信号的处理函数,异步回收资源。

      如果父进程没这么做,或子进程执行太快,父进程还没有来得及处理子进程状态,子进程就已经提前退出,那么这时的子进程就会变成僵尸进程。通常,僵尸进程持续的时间都比较短,在父进程回收它的资源后就会消亡;或者在父进程退出后,由init进程回收后也会消亡。

      一旦父进程没有处理子进程的终止,还一直保持运行状态,那么子进程就会处于僵尸状态。大量的僵尸进程会用尽PID进程号,导致新进程不能创建,所以这种情况需要避免。

    2,案例分析

      准备Ubuntu 18.04 机器配置 2CPU,8GB内存,预先安装docker、sysstat、dstat等工具

    启动应用

      

    ps查看应用为正常启动

      

      Ss+和D+。其中S表示可中断睡眠,D表示不可中断睡眠。s表示这个进程是一个会话的领导进程,+表示前台进程组 

    top查看

      

    • 第一行看平均负载(load average),过去1分钟、5分钟和15分钟的平均负载在依次缩小,说明平均负载在升高。1分钟内的平均负载为2,说明有可能已经达到性能瓶颈
    • 第二行看Tasks,有1个正在运行的进程,但是发现zombie僵尸进程比较多,而且在不停的增加,说明有子进程在退出时未被清理
    • 查看CPU的使用情况,发现用户CPU和系统CPU都不高,但是iowait分别时60.5%和94.6% 

    dstat分析iowait

       

       可以看到当iowait(wai)升高时,磁盘的读请求(read)都会很大。这说明iowait的升高跟磁盘的读请求有关,很可能就是磁盘读导致的。接下来需要进一步分析具体的哪个进程读磁盘呢?

    pidstat采用-d分析I/O和进程之间的情况

      

       可以发现app进程每秒读的数据为32MB,基本可以断定是app应用导致。接下来继续追踪app进程在执行什么I/O操作?

    strace追踪进程6082

       

     ps查看6082进程情况,发现6082已经是僵尸进程。

      

    目前iowait还在继续,但是top、pidstat这类工具已经不能给予帮助了,接下来采用perf查看

      

       

      其中swapper是内核中的调度进程,可以先忽略。可以看到在app中通过系统调用sys_read()读取数据。并且从new_sync_read和blkdev_read_iter能看出,进程正在对磁盘进行直接读,也就是绕过了系统缓存,每个读请求都会直接从磁盘读取,导致iowait升高。 定位到原因后就可以打开源码发现在app.c的文件出现了O_DIRECT选项直接打开磁盘,删除这个选项

       

    重启后,再使用top查看,发现iowait已经很低了只有0.3%。但是查看僵尸进程的数量还是在不断的增加

      

    采用pstree定位父进程,发现还是app应用导致的僵尸进程。进一步查看并修复。

        

      

  • 相关阅读:
    安达发|可视化APS高级排产系统实现精益制造
    Spring使用(三)
    【王道代码】【2.3链表】d2
    Go 的三种指针
    MQTT 主题通配符
    【计算机毕业设计】基于SpringBoot+Vue大学生心理健康管理系统的开发与实现
    浅谈接口自动化测试
    springboot工程项目作为子项目放入根项目,只需启动根项目
    HTTPS(超文本传输安全协议)工作过程
    自拟实现消息队列(MQ)基于Rabbit MQ(含概念和源码)巨详细!!!!!含思维导图
  • 原文地址:https://www.cnblogs.com/bbgs-xc/p/15873898.html