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应用导致的僵尸进程。进一步查看并修复。

    

  

posted @ 2022-02-17 10:00  MXC肖某某  阅读(863)  评论(0编辑  收藏  举报