不知道大家有没有遇到过服务器的CPU使用率达到了100%的情况,在实际生产环境中如果遇到了这种情况我们该怎么办?
接下来我就跟大家探讨一下:当CPU的使用率达到了100%的时候我们该如何排查、定位、找出问题根源。
CPU使用率
Linux作为一个多任务操作系统,将每个CPU的时间划分为很短的时间片,然后在通过调度器轮流分配给各个任务使用,因此造成多任务同时进行的错觉。
CPU使用率:就是CPU处在非空闲状态的时间占总CPU时间的百分比
而CPU使用率中也有很多相关的重要指标,我们可以通过 top 命令简单列举出来
- user(缩写为 us),代表用户态 CPU 时间。注意,它不包括下面的 nice 时间,但包括了 guest 时间。
- nice(缩写为 ni),代表低优先级用户态 CPU 时间,也就是进程的 nice 值被调整为 1-19 之间时的 CPU 时间。这里注意,nice 可取值范围是 -20 到 19,数值越大,优先级反而越低
- system(缩写为 sys),代表内核态 CPU 时间。
- idle(缩写为 id),代表空闲时间。注意,它不包括等待 I/O 的时间(iowait)。
- iowait(缩写为 wa),代表等待 I/O 的 CPU 时间。
- irq(缩写为 hi),代表处理硬中断的 CPU 时间。
- softirq(缩写为 si),代表处理软中断的 CPU 时间。
- steal(缩写为 st),代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU 时间。
如何查看CPU使用率
在介绍查看CPU使用率的工具之前,我们先来想一个问题:这些性能工具是怎么计算CPU使用率的?
事实上,为了计算CPU使用率,大多数性能工具一般都会取间隔一段时间(比如3秒)的两次值作差后,再计算出这段时间内的平均CPU使用率
这个公式,就是大多数性能工具所看到的CPU使用率的实际计算方法。所以,在使用这些工具的时候我们要注意间隔时间的设置
top 工具和 ps 工具
top 和 ps 是最常用的性能分析工具,其中:
top 显示系统总体的CPU和内存使用情况,以及各个进程的资源使用情况
ps 显示每个进程的资源使用情况
sysstat 工具
除了上面这两个最常用的工具之外,我们还可以使用 sysstat 工具中的 pidstat 命令来查看进程的CPU使用率
格式:
pidstat [ 选项 ] [ <时间间隔> ] [ <次数> ]
# 常用选项
-u:默认的参数,显示各个进程的cpu使用统计
-r:显示各个进程的内存使用统计
-d:显示各个进程的IO使用情况
-p:指定进程号
-w:显示每个进程的上下文切换情况
-t:显示选择任务的线程的统计信息外的额外信息
上面这些工具你可以轻松找出哪个CPU使用率较高的进程,但是找到进程还不够,我们还想找出是哪个具体进程或者具体函数占用了如此高的CPU时间,这样才能进行更好的优化、
这里我推荐一个可以在第一时间分析进程的CPU问题的工具——perf
perf top
perf 是一种 Linux 内置的性能分析工具。它以性能事件采样为基础,不仅可以分析系统的各种事件和内核进程,还可以用来分析指定应用程序的性能问题
perf top 类似于 top ,它能够实时显示占用CPU时钟最多的函数或者指令,因此可以查找出热点函数
$ perf top
Samples: 833 of event 'cpu-clock', Event count (approx.): 97742399
Overhead Shared Object Symbol
7.28% perf [.] 0x00000000001f78a4
4.72% [kernel] [k] vsnprintf
4.32% [kernel] [k] module_get_kallsym
3.65% [kernel] [k] _raw_spin_unlock_irqrestore
从输出结果我们可以看出:
第一行包含三个数据:采样数(Samples)、事件类型(event)和事件总数量(Event count)。这个例子中 perf 总共采集了833个CPU时钟时间,而总事件数为97742399
接着我们从列的角度来看:
- 第一列:Overhead ,是该符号的性能事件在所有采样中的比例,用百分比来表示
- 第二列::Shared ,是该函数或指令所在的动态共享对象(Dynamic Shared
Object),如内核、进程名、动态链接库名、内核模块名等。 - 第三列 :Object 是动态共享对象的类型。比如 [.] 表示用户空间的可执行程序、或者动态链接库,而 [k] 则表示内核空间。
- 第四列:Symbol 是符号名,也就是函数名。当函数名未知时,用十六进制的地址来表示。
perf record 、perf report
perf top 虽然实时展示了系统的性能信息,但是它并不能保存数据,就意味着无法用于离线或者后续的分析
而perf record 则提供了保存数据的功能,保存后的数据,需要你用 perf report 解析展示。
$ perf record # 按Ctrl+C终止采样
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.452 MB perf.data (6093 samples) ]
$ perf report # 展示类似于perf top的报告。有时候还会加上-g参数来开启调用关系的采样
案例
下面我将用极客时间里的一个例子,来展现当我们发现CPU使用率过高的问题后,要怎么使用各种性能工具找出异常的进程,又要怎么利用各种工具找出引发性能问题的函数
这次案例里面,我们预先安装了 sysstat、perf、ab 等工具
左边这台用作 Web 服务器,来模拟性能问题;右边用作客户端,来给 Web 服务器增加压力请求
首先运行我们的 web 服务器,运行之后验证一下 Nginx 是否正常开启
$curl 192.168.1.1:80
It works!
接下来我们在客户端测试一下 web 服务器的性能
# 并发10个请求测试Nginx性能,总共测试100个请求
$ ab -c 10 -n 100 http://192.168.1.1:80
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd,
...
Requests per second: 11.63 [#/sec] (mean)
Time per request: 859.942 [ms] (mean)
...
从ab的输出可以看到,Nginx能承受的每秒平均请求数只有11.63。这个结果说明Nginx 目前的性能不尽人意,我们先用top和pidstat看一下到底是哪里出了问题
运行ab命令,持续给Nginx压力,方便我们使用性能分析工具
#并发10个请求,总共10000个请求
$ ab -c 10 -n 10000 http://192.168.1.1:80
运行 top 命令,按下数字1,切换到每个CPU使用情况(单核系统不需要按)
$ top
...
%Cpu0 : 98.7 us, 1.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 99.3 us, 0.7 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
...
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
21514 daemon 20 0 336696 16384 8712 R 41.9 0.2 0:06.00 php-fpm
21513 daemon 20 0 336696 13244 5572 R 40.2 0.2 0:06.08 php-fpm
21515 daemon 20 0 336696 16384 8712 R 40.2 0.2 0:05.67 php-fpm
21512 daemon 20 0 336696 13244 5572 R 39.9 0.2 0:05.87 php-fpm
21516 daemon 20 0 336696 16384 8712 R 35.9 0.2 0:05.61 php-fpm
这里我们可以看到:
系统中这几个php-fpm进程的CPU使用率加起来将近200%;而每个CPU的用户态使用率(us)也已经超过了98%
所以我们可以得出结论:用户空间的php-fpm进程,导致了CPU使用率骤升
接下来我们需要找出 php-fpm 进程里面哪个函数导致了CPU使用率的升高
首先运行我们的 perf top 命令,实时分析进程的CPU问题
# -g参数开启调用关系分析,-p指定php-fpm的进程号21515
$ perf top -g -p 21515
按方向键切换到php-fpm,再按下回车键开php-fpm的调用关系,你会发现,调用关系最终到了sqrt 和add_ function函数。
找出了函数后我们再查看源码并修改优化
优化后我们再测试一下
$ ab -c 10 -n 10000 http://192.168.1.1:80
...
Complete requests: 10000
Failed requests: 0
Total transferred: 1720000 bytes
HTML transferred: 90000 bytes
Requests per second: 2237.04 [#/sec] (mean)
Time per request: 4.470 [ms] (mean)
Time per request: 0.447 [ms] (mean, across all concurrent requests)
Transfer rate: 375.75 [Kbytes/sec] received