作为一名测试人员,难免需要维护业务线的测试环境。初时每当研发通知某某机器又挂了、某某机器太卡了,笔者完全两眼一抹黑,无从入手。而当了解了 linux 基本性能,熟悉其基本指标时,遇到问题往往心里有底,甚至得心应手。送人玫瑰,手有余香,特此整理,与君共享。
提起性能指标,最容易想到的是CPU使用率。linux 作为一个多任务系统,将每个 CPU 的时间划分为很短的时间片,通过调度器轮流分配给各个任务使用,造成多任务同时运行的错觉。而根据 CPU 上执行任务的不同,又可以细分为用户CPU、系统CPU、等待 I/O CPU、软中断CPU 和硬中断CPU 等。
CPU使用率描述了非空闲时间占总 CPU 时间的百分比。用公式表示为:
可以通过以下命令查看各个 CPU 的数据:
$ cat /proc/stat | grep ^cpu
# cpu us ni sys id wa hi si st guest gnice
cpu 42849699018 205001 4396958253 10689835442 115714471 51747 397324892 0 0 0
cpu0 1448897830 9918 260556249 620723421 17589437 11620 80282872 0 0 0
cpu1 1802849978 8539 182700339 444025341 2238152 1 5138950 0 0 0
cpu2 1846754519 9857 177641895 405553744 1799005 0 4945465 0 0 0
cpu3 1854741192 10151 175660372 399834166 1748613 0 4771745 0 0 0
...
这里输出的是一个表格,第一列为 CPU 的编号,如cpu0,而第一行没有 CPU 编号,表示所有 CPU 的累加。其他列表示不同任务下 CPU 的时间,每一列的顺序可以通过man proc 命令查看。通过这些数据就可以计算出当前机器的 CPU使用率。然而,得到的 CPU 使用率是开机以来的平均CPU使用率,一般不具有参考价值。性能工具一般会取间隔一段时间的两次差值,计算这段时间内的平均CPU使用率,即:
top和 ps 是常用的性能分析工具。top 能够显示系统总体的CPU和内存使用情况,以及各进程的资源使用情况。ps 显示各进程的资源使用情况。例如,top 的输出格式为:
$ top
top - 21:23:36 up 283 days, 10:41, 2 users, load average: 21.96, 14.46, 13.76
Tasks: 2009 total, 10 running, 1994 sleeping, 0 stopped, 5 zombie
Cpu(s): 32.5%us, 7.6%sy, 0.0%ni, 58.3%id, 0.6%wa, 0.0%hi, 0.9%si, 0.0%st
Mem: 98979248k total, 95372168k used, 3607080k free, 1479704k buffers
Swap: 0k total, 0k used, 0k free, 59440700k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
13159 user 20 0 9103m 6.0g 13m S 168 6.4 156503:16 mongod
31963 root 20 0 929m 882m 672 R 52 0.9 0:01.61 supervisord
...
其中,第三行 CPU 就是该机器的 CPU使用率,top 默认每 3 秒刷新一次。默认情况下显示的是所有 CPU 的平均值,如果想了解每个 CPU 的使用情况,只需按下数字 1 即可。例如:
Cpu0 : 17.0%us, 10.2%sy, 0.0%ni, 64.9%id, 2.3%wa, 0.0%hi, 5.6%si, 0.0%st
Cpu1 : 12.1%us, 5.5%sy, 0.0%ni, 82.1%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu2 : 24.5%us, 4.0%sy, 0.0%ni, 71.2%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
...
从上我们可以发现,top 并没有细分进程的用户态CPU 和内核态CPU,如果想了解每个进程的详细情况,可以使用 pidstat 工具。例如:
$ pidstat 1 1
Linux 3.2.0-23-generic (cs) 06/24/2021 _x86_64_ (24 CPU)
09:32:41 PM PID %usr %system %guest %CPU CPU Command
09:32:42 PM 1522 0.88 0.88 0.00 1.77 20 beam.smp
...
一般来说,每种类型的 CPU使用率高,都有不同导致的条件,下面列出了这几种导致不同 CPU使用率高的情况以及着重排查点:
第二能想到的就是平均负载。很多人会认为,平均负载不就是单位时间内的 CPU使用率吗?其实并非如此。下面我们来一起熟悉下什么是平均负载。
通俗地说,平均负载是指单位时间内系统处于可运行状态和不可中断状态的平均进程数,即平均活跃进程数。从定义我们可以解读到其实跟 CPU使用率无直接关系。
linux 将进程的状态划分为以下几种:
R 是 Running 的缩写,表示进程正在运行或者正在等待运行。
D 是 Disk Sleep 的缩写,为不可中断睡眠状态,进程正与硬件进行交互,交互过程不允许被其他进程打断,以防止数据丢失。
Z 是 Zombie 的缩写,表示僵尸进程,进程实际已经结束,但是父进程还没回收其资源,就会出现该状态。
S 是 Interruptible Sleep 的缩写,称为可中断睡眠状态,表示进程因等待某个事件而被系统挂起。
I 是 Idle 的缩写,为空闲状态,用于不可中断睡眠的内核线程上。
T 是 Traced 的缩写,表示进程处于暂停状态。
X 是 Dead 的缩写,表示进程已经消亡。
通常使用 uptime 或 top 命令查看。例如,uptime 的输出格式为:
$ uptime
15:22:50 up 284 days, 4:40, 3 users, load average: 10.06, 12.15, 12.33
输出分别表示为当前时间、系统运行时间、正在登录用户数以及过去 1 分钟,5 分钟,15 分钟的平均负载。输出这三个时间的平均负载有什么含义呢?
基于此就可以粗略判断当前的系统负载情况。上面的例子可以看到目前的系统负载下降的。
平均负载这一数据没有针对 CPU 个数作归一化处理,因此最理想情况是平均负载等于 CPU 的个数。在评判当前系统是否过载时,首先需要知道当前系统的 CPU 个数,可以通过 top 或者 lscpu 查看。例如:
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 24
...
当知道了 CPU 个数后,就可以结合平均负载来判断当前系统的运行情况了。一旦负载过高,会导致进程响应变慢,影响服务的正常功能。故当出现负载有明显升高趋势时,就需要我们去分析和调查了。通常认为平均负载低于 CPU 数量 70% 时为合理状态,但不是绝对的,还需要参考负载的变化趋势来作判断。
前面描述了 linux 系统会在很短的时间内把 CPU 轮流分配给任务(进程),造成多个任务同时运行的错觉。而这些任务都是从哪里加载以及开始运行的呢?这就引出了 CPU上下文。CPU寄存器和程序计数器被叫做 CPU 的上下文,那么,CPU寄存器和程序计数器分别有什么作用呢?
而 CPU上下文切换,就是把前一个任务的 CPU上下文保存起来,然后加载新任务的上下文到寄存器和程序计数器中,最后跳转到程序计数器所指位置,执行新任务。根据任务的不同,可以分为三种切换场景:进程上下文切换、线程上下文切换以及中断上下文切换。
提到进程上下文切换,就不得不提到系统调用。linux 按照特权等级,把进程的运行空间划分为内核空间和用户空间。
系统调用和普通函数调用非常相似,只不过,系统调用由操作系统核心提供,运行于内核态,而普通函数调用由函数库或用户提供,运行于用户态。比如,某一进程需要查看文件内容时,就需要系统调用来完成:首先调用 open() 打开文件,通过 read() 读取文件内容,并通过 write() 写到到标准输出,最后通过 close() 关闭文件。系统调用结束后,CPU寄存器恢复到原来保存的用户态,切换到用户空间,继续执行进程。
从上我们可以知道,系统调用是在进程内进行的,其与进程上下文切换有点不同,它不会涉及到虚拟内存等用户态资源。所以我们通常称系统调用为一种特权模式切换,而在系统调用用上下文切换是不可避免的,一次系统调用发生了两次上下文切换。
进程是由内核管理和调度的,进程的上下文切换在内核态进行,故其上下文切换要比系统调用多了一步:在保存当前进程的内核状态和 CPU寄存器之前,需要先把该进程的虚拟内存、栈等用户资源保存起来。
那么,什么时候需要进行上下文切换呢?
我们知道,线程是调度的基本单位,而进程是资源拥有的基本单位,即内核中的任务调度实际对象是线程,进程只是给线程提供了虚拟内存等资源。如果进程中有多个线程,则线程会共享虚拟内存等资源。因此,对于线程上下文切换就有两种情况:
由此我们知道同一进程的线程上下文切换,要比进程间的上下文切换耗费更少资源,这也是多线程代替多进程的一大优势。
中断要比进程的优先级更高,其会打断进程的正常调度,因此中断程序需要更短小精悍,以便尽可能快地结束。
中断上下文切换与进程不同,其不需要虚拟内存、全局变量等用户态资源,只需要如 CPU寄存器、硬件中断参数等内核态中断服务程序所需要的状态。
查看系统总体的上下文切换情况可以通过 vmstat 命令,其输出格式为:
$ vmstat 1 1
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
10 0 0 1653888 1477852 61630304 0 0 0 112 0 0 73 8 19 0
其中,需要关注以下几个参数:
而如果我们想要查看进程的上下文切换,可以通过 pidstat 查看,输出格式为:
# 通过参数 -w 可以查看进程每秒的上下文切换次数
$ pidstat -w 1 1
Linux 3.2.0-23-generic (cs) 06/26/2021 _x86_64_ (24 CPU)
03:19:25 PM PID cswch/s nvcswch/s Command
03:19:26 PM 1 0.85 0.00 init
03:19:26 PM 3 0.85 0.00 ksoftirqd/0
...
其中,需要关注两个参数:
如果我们想要查看线程间的上下文切换,还是可以通过 pidstat 查看,加上 -wt 参数选项,输出格式为:
$ pidstat -wt 1 1
linux 3.2.0-23-generic (cs7) 06/26/2021 _x86_64_ (24 CPU)
03:34:07 PM TGID TID cswch/s nvcswch/s Command
03:34:07 PM 2200 - 0.64 0.00 flush-8:48
03:34:07 PM - 2200 0.64 0.00 |__flush-8:48
03:34:07 PM - 2524 136.54 0.00 |__influxd
03:34:07 PM - 2537 26.92 0.00 |__influxd
03:34:07 PM - 3701 12.82 3.21 |__1_scheduler
03:34:07 PM - 3702 0.64 0.00 |__2_scheduler
03:34:07 PM - 3725 1.28 0.00 |__aux
...
据统计,每次上下文切换需要耗费几十纳秒到数微秒的CPU时间,这个时间还是可以接受的。但是过多的上下文切换,会把 CPU 时间都耗费在寄存器、内存栈以及虚拟内存的保存和恢复上,缩短进程真正运行的时间,导致系统性能大幅下降。那么,我们肯定想了解,多少的 CPU上下文切换为合理情况呢?
这取决于 CPU 的性能。如果上下文切换稳定,那么从数百到上万都是合理的。但是当 CPU上下文切换呈数量级增大时,或者数值已超过上万次且不断增长时,就可能出现了性能问题,需要我们着重排查。
CPU缓存是为了协调CPU处理速度和内存访问速度之间的差距而出现的空间,可分为 L1、L2 和 L3 三级缓存。离 CPU 核心越近,缓存的读写速度就越快,因此三级缓存的读写速度快慢分别是:L1 > L2 > L3.
如果 CPU 所操作的数据在缓存中,则可以直接读取,称为缓存命中。命中缓存可以带来很大的性能提升。因此,考虑 CPU 的缓存命中率对于 linux 性能也至关重要。
查看系统的内存情况,可通过 top 或 free 命令。比如,free 命令输出格式为:
$ free
total used free shared buffers cached
Mem: 98979248 97562252 1416996 0 1478000 62018692
Swap: 0 0 0
输出分别是物理内存 Mem 和交换分区 Swap 的使用情况。其中,每一列分别是:
查看 buffers 和 cache 的变化情况,可通过 vmstat 命令,输出格式:
$ vmstat 1 1
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
9 0 0 1571640 1478044 62034792 0 0 0 112 0 0 73 8 19 0
下面就目前机器出现的系统使用率过高的情况进行排查。
通过 top 可以查看当前的系统性能情况:
$ top
top - 16:41:29 up 285 days, 5:59, 2 users, load average: 9.82, 11.53, 12.92
Tasks: 2010 total, 9 running, 2000 sleeping, 0 stopped, 1 zombie
Cpu(s): 34.5%us, 6.5%sy, 0.0%ni, 57.6%id, 0.5%wa, 0.0%hi, 0.9%si, 0.0%st
Mem: 98979248k total, 97548032k used, 1431216k free, 1478084k buffers
Swap: 0k total, 0k used, 0k free, 62122812k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
13159 user 20 0 9128m 6.1g 13m S 90 6.5 160516:12 mongod
19638 root 20 0 929m 882m 520 R 58 0.9 0:01.81 supervisord
...
CPU用户使用率为 34.5%,系统使用率为 6.5%,空闲时间占 57.6%,系统使用流畅。可以看出,当前的系统是比较空闲的。
然后,我执行某些操作。再通过 top 命令查看当前的系统性能情况:
$ top
Tasks: 2020 total, 16 running, 2004 sleeping, 0 stopped, 0 zombie
Cpu(s): 64.5%us, 13.6%sy, 0.0%ni, 20.5%id, 0.2%wa, 0.0%hi, 1.1%si, 0.0%st
Mem: 98979248k total, 98003524k used, 975724k free, 1478128k buffers
Swap: 0k total, 0k used, 0k free, 62228740k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
13159 user 20 0 9128m 6.2g 13m S 249 6.5 160538:08 mongod
15108 root 20 0 929m 882m 620 R 73 0.9 0:02.24 supervisord
15121 root 20 0 929m 882m 620 R 66 0.9 0:02.03 supervisord
15122 root 20 0 929m 882m 620 R 66 0.9 0:02.03 supervisord
...
可以发现,系统使用率已经增至了原来的一倍。CPU使用率除了服务 mongod 最高以外,有几个 supervisord 启动的服务也出现了CPU使用率增高的情况。通过 pidstat 命令分析这些进程的系统资源占用情况:
$ pidstat -p 15108
Linux 3.2.0-23-generic (cs) 06/26/2021 _x86_64_ (24 CPU)
05:03:14 PM PID %usr %system %guest %CPU CPU Command
发现该进程并没有输出任何的资源使用信息。是 pidstat 指令出故障了?可以通过 ps 指令来交叉确认一下:
$ ps aux | grep 15108
2003 14211 0.0 0.0 8748 948 pts/2 S+ 17:07 0:00 grep --color=auto 15108
确实没有输出。现在发现问题了,原来是进程已经不存在了,所以 pidstat 没有任何输出。继续分析其他几个高 CPU使用率的进程,发现亦如此。既然进程没有了,那么性能问题应该已经没有了吧。通过 top 命令确认下:
$ top
Tasks: 2019 total, 18 running, 2001 sleeping, 0 stopped, 0 zombie
Cpu(s): 60.5%us, 13.4%sy, 0.0%ni, 24.7%id, 0.4%wa, 0.0%hi, 1.1%si, 0.0%st
Mem: 98979248k total, 98118260k used, 860988k free, 1478272k buffers
Swap: 0k total, 0k used, 0k free, 62353676k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
13159 user 20 0 9129m 6.2g 13m S 260 6.5 160559:54 mongod
27114 root 20 0 929m 882m 580 R 63 0.9 0:01.94 supervisord
27145 root 20 0 929m 882m 600 R 60 0.9 0:01.87 supervisord
...
好像问题依旧存在,系统CPU使用率依旧很高。仔细查看 top 的输出,发现 supervisord 进程的 pid 每次都有变化,现在已经变成了 27114。进程的 PID 号在变化,说明了什么?有两点:
supervisor 是一个进程管理程序,能够监控进程状态,异常退出时能自动重启。目前看来,极有可能是第一种情况。通过 supervisor 来查看服务状态:
从 uptime 更新时间长短可以发现,很多服务在不断地重启。可能是服务配置或者代码问题引起的。其实是批量的服务出现配置问题导致了服务启动失败,然后 supervisor 监控到服务没有成功启动后又自动重启。在解决完这些服务问题后,通过 top 再次看看性能情况:
$ top
Tasks: 2012 total, 7 running, 2002 sleeping, 0 stopped, 3 zombie
Cpu(s): 29.7%us, 5.4%sy, 0.0%ni, 63.5%id, 0.8%wa, 0.0%hi, 0.5%si, 0.0%st
Mem: 98979248k total, 96231348k used, 2747900k free, 1470892k buffers
Swap: 0k total, 0k used, 0k free, 60741708k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
13159 user 20 0 9131m 6.2g 13m S 80 6.6 160593:01 mongod
27545 user 20 0 41880 6636 2896 S 61 0.0 0:01.86 Python/ target=_blank class=infotextkey>Python
27549 root 20 0 929m 882m 616 R 38 0.9 0:01.15 supervisord
...
在动手进行性能优化前,需要考虑以下几个问题:
对于第一个问题,我们解决性能问题的目的,自然有一个性能提升的效果,即要对性能指标进行量化,需要确认优化前指标、优化后指标。当优化后指标与优化前指标有不同时,才能判定我们的优化有没有效果。比如上述例子中的系统CPU使用率优化前后的变化。
对于第二个问题,有一个 “二八原则”,也就是 80% 的问题都是由 20% 的代码导致的。因此,并不是所有的性能问题都值得优化。在动手优化前,往往需要把所有性能问题分析一遍,找出最重要的、可以最大程度提升性能的问题。这个过程会花费较多的时间,下面有两个可以简化这一过程的方法:
对于第三个问题,一般来说,应该选择能最大程度提升性能的方法。但是需要注意的是,性能优化并非没有成本,很有可能你优化了一个指标,另一个指标的性能就变差了。