vlambda博客
学习文章列表

监控分析篇|解析操作系统级监控


监控分析篇|解析操作系统级监控



我相信有一些人看到这篇文章的标题肯定有种不想看的感觉,因为这样的内容实在被写得太多太多了。操作系统分析嘛,无非就是 CPU 使用率、I/O 使用率、内存使用率、网络使用率等各种使用率的描述。


然而因为视角的不同,在性能测试和分析中,这始终是我们绕不过去的分析点。我们得知道什么时候才需要去分析操作系统,以及要分析操作系统的什么内容。


首先,我们前面在性能分析方法中提到,性能分析要有起点,通常情况下,这个起点就是响应时间、TPS 等压力工具给出来的信息。


我们判断了有瓶颈之后,通过拆分响应时间就可以知道在哪个环节上出了问题,再去详细分析这个操作系统。这就需要用到我们的分析决策树了。今天我们单独把操作系统的这一环节给提出来,并加上前面说的细化过程,就可以得到下面的这个分析决策树


监控分析篇|解析操作系统级监控


在分段分层确定了这个系统所运行的应用有问题之后,还要记起另一件事情,就是“全局—定向”的监控思路。


既然说到了全局,我们得先知道操作系统中,都有哪些大的模块。这里就到了几乎所有性能测试人员看到就想吐的模块了,CPU、I/O、Memory、Network…没办法,谁让操作系统就这么点东西呢。我先画一个思维导图给你看一下。


监控分析篇|解析操作系统级监控


我很努力地把一些常见指标的相应关系都画到了图中,你是不是已经看晕了?看晕就对了,别着急。


我们先要知道的是,面对这些大的模块,到底要用什么的监控手段来实现对它们的监控呢?要知道,在一篇文章中不可能详尽地描述操作系统,我会尽量把我工作中经常使用到的一些和性能分析相关的、使用频度高的知识点整理给你。




监控命令



我们经常用到的 Linux 监控命令大概有这些:top、atop、vmstat、iostat、iotop、dstat、sar等……请你注意我这里列的监控命令是指可以监控到相应模块的计数器,而不是说只能监控这个模块,因为大部分命令都是综合的工具集。


监控分析篇|解析操作系统级监控


像这样的监控工具还能列上一堆,但这并不是关键,关键的是我们在什么时候能想起来用这些工具,以及知道这些工具的局限性。比如说 top,它能看 CPU、内存、Swap、线程列表等信息,也可以把 I/O 算进去,因为它有 CPU 的 wa 计数器,但是它看不了 Disk 和 Network,这就是明显的局限性。之后出现的atop对很多内容做了整理,有了 Disk 和 Net 信息,但是呢,在一些 Linux 发行版中又不是默认安装的。vmstat呢?它能看 CPU、内存、队列、Disk、System、Swap 等信息,但是它又看不了线程列表和网络信息。


当工具让你眼花缭乱的时候,不要忘记最初的目标,我们要监控的是这几大模块:CPU、I/O、Memory、Network、System、Swap。然后,我们再来对应前面提到的“全局—定向”监控的思路。如果你现在仅用命令来监控这个系统,你要执行哪几个呢?对应文章前面的思维导图,我们做一个细致的表格。


监控分析篇|解析操作系统级监控


你会发现,vmstat可以看 Swap,但它能看的是si和so,看不到其他的计数器,但是top可以看到这些计数器……像这样的细节还有很多。


因为计数器非常多,又不是每个都常用。但是万一某个时候就需要用了呢?这个时候如果你不知道的话,就无法继续分析下去。


这里我主要想告诉你什么呢?就是用命令的时候,你要知道这个命令能干什么,不能干什么。你可能会说,有这些么多的计数器,还有这么多的命令,光学个 OS 我得学到啥时候去?我要告诉你的是监控的思考逻辑。你要知道的是,正是因为你要监控 CPU 的某个计数器才执行了这个命令,而不是因为自己知道这个命令才去执行。这个关系我们一定要搞清楚。那么逻辑就是这样的:


监控分析篇|解析操作系统级监控


比如说,我想看下 OS 各模块的性能表现,所以执行 top 这个命令看了一些计数器,同时我又知道,网络的信息在top中是看不到的,所以我要把 OS 大模块看完,还要用netstat看网络,以此类推。如果你还是觉得这样不能直接刺激到你的神经,懵懂不知道看哪些命令。那么在这里,我用上面的工具给你做一个表格。命令模块对照表:


监控分析篇|解析操作系统级监控

我虽然给出了这张表,但要想融会贯通,还需要你亲手画几遍,每个命令都练习很多遍。好,这个时候,我们就已经把全局监控的第一层的计数器基本看完了。表格中加上你想看的计数器和相关的命令就可以了,你的这个表格就会越来越丰富,丰富的过程中,也就慢慢厘清了自己的思路。




监控平台 Grafana+Prometheus+node exporter




这是现在用得比较多的监控平台了。在微服务时代,再加上 Kubernetes+Docker 的盛行,这个监控套装几乎是干 IT 的都知道。我们来看一下常用的 Dashboard。为了理解上的通用性,我这里都用默认的信息,不用自己定制的。Grafana.com 官方 ID:8919 的模板内容如下:


监控分析篇|解析操作系统级监控

监控分析篇|解析操作系统级监控


还记得我们要看系统的模块是哪几个吗?CPU/Memory/IO/Network/System/Swap你可以自己对一下,是不是大模块都没有漏掉?确实没有。但是!上面的计数器你得理解。


我们先来看一下 CPU


它包括除了空闲 CPU 的其他所有 CPU 使用率,这其实就有 ni、hi、si、st、guest、gnice 的值。当我们在这个图中看到 System、User、I/O Wait 都不高时,如果 Total 很高,那就是 ni、hi、si、st、guest、gnice 计数器中的某个值大了。这时你要想找问题,就得自己执行命令查看了。


看完 CPU 之后,再看一下 Network


上图中有网络流量图。可以看到只有“上传下载”,这个值似乎容易理解,但是不够细致。node_exportor 还提供了一个“网络连接信息”图。可以看到 Sockets_used、CurrEstab、TCP_alloc、TCP_tw、UDP_inuse 这些值,它们所代表的含义如下:


Sockets_used:已使用的所有协议套接字总量

CurrEstab:当前状态为 ESTABLISHED 或 CLOSE-WAIT 的 TCP 连接数

TCP_alloc:已分配(已建立、已申请到 sk_buff)的 TCP 套接字数量

TCP_tw:等待关闭的 TCP 连接数

UDP_inuse:正在使用的 UDP 套接字数量




这些值也可以通过查看“cat /proc/net/sockstat”知道。这是监控工具套装给我们提供的便利。


然后我们再来看下 Memory


上图中有总内存、可用内存、已用内存这三个值。如果从应用的角度来看,我们现在对内存的分析,就要和语言相关了。像 Java 语言,一般会去分析 JVM。我们对操作系统的物理内存的使用并不关注,在大部分场景下物理内存并没有成为我们的瓶颈点,但这并不是说在内存上就没有调优的空间了。


关于内存这一块,我不想展开太多。因为展开之后内容太多了,如果你有兴趣的话,可以找内存管理的资料来看看。


其他几个模块我就不再一一列了,I/O、System、Swap 也都是有监控数据的。从全局监控的角度上看,这些计数器也基本够看。但是对于做性能分析、定位瓶颈来说,这些值显然是不够的。


还记得我在前面提到的“先全局监控再定向监控”找证据链的理念吧。像 node exporter 这样的监控套装给我们提供的就是全局监控的数据,就是大面上覆盖了,细节上仍然不够。那怎么办呢?下面我就来一一拆解一下。



CPU



关于 CPU 的计数器,已经有很多的信息了。这里我再啰嗦一下。CPU 常见的计数器是 top 中的 8 个值,也就是下面这些:


%Cpu(s):  0.7 us,  0.5 sy,  0.0 ni, 98.7 id,  0.0 wa,  0.0 hi,  0.2 si,  0.0 st


含义我就不写了,你搜一下就会知道。在 mpstat(Multi-Processor Statistics)中看到的是 10 个计数器:


[root@7dgroup3 ~]# mpstat -P ALL 3

Linux 3.10.0-957.21.3.el7.x86_64 (7dgroup3)   12/27/2019   _x86_64_  (2 CPU)

03:46:25 PM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle

03:46:28 PM  all    0.17    0.00    0.17    0.00    0.00    0.00    0.00    0.00    0.00   99.66

03:46:28 PM    0    0.33    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   99.67

03:46:28 PM    1    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00


监控分析篇|解析操作系统级监控


你可以看到计数器的名字稍有不同,像top中的wa在mpstat中是%iowait,si是 mpstat 中的%soft。


在 Linux 中,这就是我们经常查看的 CPU 计数器了。在我的性能生涯中,常见的问题大部分都是体现在这么几个计数器上(排名有先后):

  • us

  • wa

  • sy‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

  • si


首先,为了确定看到 CPU 高之后,接着往下分析的方向是绝对没有错的,建议你用Perf top -g先看一下 CPU 热点。perf默认用的是cpu-clock事件。这一步只是为了确定方向对不对。


那么如何从这几个计数器找到后续的证据链呢?下面就是我们定向监控分析的过程了。我要狂敲黑板了!!!


us cpu 是用户态进程消耗的 CPU 百分比。大家都知道怎么往下落。这个链就是下面这样的:


监控分析篇|解析操作系统级监控


wa cpu是 I/O 读写等待消耗的 CPU 百分比。这个证据链怎么往下落呢?来看一下。


监控分析篇|解析操作系统级监控


你看中间有一步跳跃,这就是 wa CPU 直接跳到线程了。为什么没有进程了呢?那是因为 iotop 有直接到线程的能力。如果你想先看进程也可以,记得执行 iotop -P。


sy cpu 是内核消耗的 CPU 百分比。这个问题就有点复杂了,因为它并没有一个固定的套路。但是它的分析链路仍然和 us CPU 高的分析链路差不多,只是这个进程可能不是应用的,而是系统自己的。但是,是什么导致内核进程占用 CPU 高呢。这可能和应用有关,当然也可能和配置有关。那么现在我们画一个它的分析链路。


监控分析篇|解析操作系统级监控


其实在实际的分析过程中,也是这样的。如果我们看到一个系统的进程消耗了更多的资源,那就要去查一下这个进程是干吗的,看它的运行逻辑和配置文件。不一定所有情况都是配置的问题,但绝大多数情况是这个原因,只能说,在系统级别,我们遇到的内核进程本身有性能问题的情况还是很少的。大部分时候都只是配置问题。


si CPU 是软中断消耗的 CPU 百分比。什么是软中断呢?


简单点来说,当出现异常或资源争用时,它是用来管理秩序的。CPU 正在吭哧吭哧着干活呢,突然来了一个优先级高的,needs immediate attention,这时就会发一个中断信号给 CPU。作为一个干活的,CPU 谁的话都得听,这时候就把手头的工作现场保存一下,干这个优先级高的活。除非这个中断是致命的,不然 CPU 会在干完这个活之后再回去干之前的活,这就是一次软中断。


这个值,越多就越有问题,关键是它有多少才是有问题呢?这一点你从来没有看过有人给建议值对不对?因为它根本没有可以参考的值,在不同的应用和硬件环境中,si CPU 都会有很大差别.


下面我来照例画个分析的图看一下。


监控分析篇|解析操作系统级监控


在这个判断的链路中,就是要把 si 的中断模块找出来,然后再分析这个模块的功能和配置。比如我们看网卡的中断,这是常见的一种性能问题。我们要知道网络是带宽不够?还是配置得不对?还是防火墙?还是啥啥啥别的原因?如果是其他的模块也是一样的逻辑。好,在知道了上面这几个常见的 CPU 计数器的分析证据链逻辑之后,我就不再详细画其他的 CPU 的计数器了。



I/O



I/O 其实是挺复杂的一个逻辑,但我们今天只说在做性能分析的时候,应该如何定位问题。


对性能优化比较有经验的人都会知道,当一个系统调到非常精致的程度时,基本上会卡在两个环节上,对计算密集型的应用来说,会卡在 CPU 上;对 I/O 密集型的应用来说,瓶颈会卡在 I/O 上。


我们对 I/O 的判断逻辑关系是什么呢?我们先画一个 I/O 基本的逻辑过程。我们很多人嘴上说 I/O,其实脑子里想的都是 Disk I/O,但实际上一个数据要想写到磁盘当中,没那么容易,步骤并不简单。


监控分析篇|解析操作系统级监控


这是简化版的图。I/O 有很多原理细节,那我们如何能快速地做出相应的判断呢?首先要祭出的一个工具就是iostat。


监控分析篇|解析操作系统级监控



svctm代表 I/O 平均响应时间

w_await表示写入的平均响应时间;

r_await表示读取的平均响应时间;

r/s表示每秒读取次数;

w/s表示每秒写入次数。



svctm(官方手册提示“Warning! Do not trust this field any more. This field will be removed in a future sysstat version.” 也就是说,这个数据你爱看就爱,不一定准)


而 IO/s 的关键计算是这样的:

IO/s = r/s + w/s = 18.33+114.33 = 132.66

%util = ( (IO/s * svctm) /1000) * 100% = 100.02564%


这个%util是用svctm算来的,既然svctm都不一定准了,那这个值也只能参考了。还好我们还有其他工具可以接着往深了去定位,那就是iotop。


Total DISK READ :       2.27 M/s | Total DISK WRITE :    574.86 M/s

Actual DISK READ:       3.86 M/s | Actual DISK WRITE:      34.13 M/s

  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND

  394 be/3 root        0.00 B/s  441.15 M/s  0.00 % 85.47 % [jbd2/vda1-8]

32616 be/4 root     1984.69 K/s    3.40 K/s  0.00 % 42.89 % kube-controllers

13787 be/4 root        0.00 B/s    0.00 B/s  0.00 % 35.41 % [kworker/u4:1]

...............................


从上面的Total DISK WRITE/READ就可以知道当前的读写到底有多少了,默认是按照I/O列来排序的,这里有Total,也有Actual,并且这两个并不相等,为什么呢?


因为 Total 的值显示的是用户态进程与内核态进程之间的速度,而 Actual 显示的是内核块设备子系统与硬件之间的速度。


而在I/O交互中,由于存在cache和在内核中会做I/O排序,因此这两个值并不会相同。那如果你要说磁盘的读写能力怎么样,我们应该看的是Actual。这个没啥好说的,因为Total再大,不能真实写到硬盘上也是没用的。


在下面的线程列表中,通过排序,就可以知道是哪个线程(注意在第一列是 TID 哦)占的I/O高了。



Memory




关于内存,要说操作系统的内存管理,那大概开一个新专栏也不为过。但是在性能测试的项目中,如果不做底层的测试,基本上在上层语言开发的系统中,比如说 Java、Go、C++ 等,在分析过程中都直接看业务系统就好了。


在操作系统中,分析业务应用的时候,我们会关注的内存内容如下面的命令所示:


监控分析篇|解析操作系统级监控


total肯定是要优先看的,其次是available,这个值才是系统真正可用的内存,而不是free。


因为 Linux 通常都会把用的内存给cache,但是不一定会用,所以free肯定会越来越少,但是available是计算了buff和cache中不用的内存的,所以只要available多,就表示内存够用。


当出现内存泄露或因其他原因导致物理内存不够用的时候,操作系统就会调用OOM Killer,这个进程会强制杀死消耗内存大的应用。这个过程是不商量的,然后你在“dmesg”中就会看到如下信息。


[12766211.187745] Out of memory: Kill process 32188 (java) score 177 or sacrifice child

[12766211.190964] Killed process 32188 (java) total-vm:5861784kB, anon-rss:1416044kB, file-rss:0kB, shmem-rss:0kB


这种情况只要出现,TPS 肯定会掉下来,如果你有负载均衡的话,压力工具中的事务还是可能有成功的。但如果你只有一个应用节点,或者所有应用节点都被OOM Killer给干掉了,那 TPS 就会是这样的结果。


监控分析篇|解析操作系统级监控


对内存监控,可以看到这样的趋势:


监控分析篇|解析操作系统级监控


内存慢慢被耗光,但是杀掉应用进程之后,free内存立即就有了。你看上面这个图,就是一个机器上有两个节点,先被杀了一个,另一个接着泄露,又把内存耗光了,于是又被杀掉,最后内存全都空闲了。


在我参与的性能项目中,这样的例子还挺常见。当然对这种情况的分析定位,只看物理内存已经没有意义了,更重要的是看应用的内存是如何被消耗光的。


对于内存的分析,你还可以用nmon和cat/proc/meminfo看到更多信息。如果你的应用是需要大页处理的,特别是大数据类的应用,需要关注下HugePages相关的计数器。


内存我们就说到这里,总之,要关注available内存的值。



NetWork




这里我们就来到了网络分析的部分了,在说握手之前,我们先看网络的分析决策链。


监控分析篇|解析操作系统级监控


请看上图中,在判断了瓶颈在网络上之后,如果知道某个进程的网络流量大,首先肯定是要考虑减少流量,当然要在保证业务正常运行,TPS 也不降低的情况下。



Recv_Q 和 Send_Q


当然我们还要干一件事,就是可能你并不知道是在哪个具体的环节上出了问题,那就要学会判断了。网络I/O栈也并不简单,看下图:


监控分析篇|解析操作系统级监控


数据发送过程是这样的。


应用把数据给到tcp_wmem就结束它的工作了,由内核接过来之后,经过传输层,再经过队列、环形缓冲区,最后通过网卡发出去。


数据接收过程则是这样的。


网卡把数据接过来,经过队列、环形缓冲区,再经过传输层,最后通过tcp_rmem给到应用。


你似乎懂了对不对?那么在这个过程中,我们有什么需要关注的呢?


首先肯定是看队列,通过netstat或其他命令可以看到Recv_Q和Send_Q,这两项至少可以告诉你瓶颈会在哪一端。如下图所示:


监控分析篇|解析操作系统级监控


我画个表清晰地判断一下瓶颈点。


监控分析篇|解析操作系统级监控


其实这个过程中,我还没有把防火墙加进去,甚至我都没说NAT的逻辑,这些基础知识你需要自己先做足功课。


在我们做性能分析的过程中,基本上,基于上面这个表格就够通过接收和发送判断瓶颈点发生在谁身上了。


但是,要是这些队列都没有值,是不是网络就算好了呢?还不是。



三次握手和四次挥手



我们先看握手图:

监控分析篇|解析操作系统级监控


我发现一点,很多人以为三次握手是非常容易理解的,但是没几个人能判断出和它相关的问题。


握手的过程,我就不说了,主要看这里面的两个队列:半连接队列和全连接队列。


在 B 只接到第一个syn包的时候,把这个连接放到半连接队列中,当接到ack的时候才放到全连接队列中。这两个队列如果有问题,都到不了发送接收数据的时候,你就看到报错了。


查看半连接全连接溢出的手段也很简单,像下面这种情况就是半连接没建立起来,半连接队列满了,syn包都被扔掉了。


[root@7dgroup ~]# netstat -s |grep -i listen    

8866 SYNs to LISTEN sockets dropped


那么半连接队列和什么参数有关呢?代码中的backlog:你是不是想起来了ServerSocket(int port, int backlog)中的backlog?是的,它就是半连接的队列长度,如果它不够了,就会丢掉syn包了。还有操作系统的内核参数net.ipv4.tcp_max_syn_backlog。


而像下面这样的情况呢,就是全连接队列已经满了,但是还有连接要进来,已经超过负荷了。


[root@7dgroup2 ~]# netstat -s |grep overflow    

154864 times the listen queue of a socket overflowed


这是在性能分析过程中经常遇到的连接出各种错的原因之一,它和哪些参数有关呢?我列在这里。


net.core.somaxconn:系统中每一个端口最大的监听队列的长度。

net.core.netdev_max_backlog:每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。

open_file:文件句柄数。



我们再来看下四次挥手。我遇到性能测试过程中的挥手问题,有很多都是做性能分析的人在不了解的情况下就去做各种优化动作而产生的。先看一下 TCP 挥手图:


监控分析篇|解析操作系统级监控


在挥手的逻辑中,和性能相关的问题真的非常少。


但有一个点是经常会问到的,那就是TIME_WAIT。不知道为什么,很多人看到TIME_WAIT就紧张,就想去处理掉,于是搜索一圈,哦,要改recycle/reuse的 TCP 参数,要改fin_time_out值。


至于为什么要处理TIME_WAIT,却没几个人能回答得上来。


在我的性能工作经验中,只有一种情况要处理TIME_WAIT,那就是端口不够用的时候。


TCP/IPv4的标准中,端口最大是 65535,还有一些被用了的,所以当我们做压力测试的时候,有些应用由于响应时间非常快,端口就会不够用,这时我们去处理TIME_WAIT的端口,让它复用或尽快释放掉,以支持更多的压力。


所以处理TIME_WAIT的端口要先判断清楚,如果是其他原因导致的,即使你处理了TIME_WAIT,也没有提升性能的希望。


如果还有人说,还有一种情况,就是内存不够用。我必须得说,那是我没见过世面了,我至今没见过因为TIME_WAIT的连接数把内存耗光了的。


一个 TCP 连接大概占 3KB,创建 10 万个连接,才100000x3KB≈300M左右,何况最多才 65535 呢?服务器有那么穷吗?



System




确切地说,在性能测试分析的领域里,System 似乎实在是没有什么可写的地方。


我们最常见的 System 的计数器是in(interrupts:中断)和cs(context switch:上下文切换)。


监控分析篇|解析操作系统级监控


因为这是我能找得到的最疯狂的 System 计数器了。


中断的逻辑在前面跟你说过了。


cs也比较容易理解,就是 CPU 不得不转到另一件事情上,听这一句你就会知道,中断时肯定会有cs。但是不止中断会引起 cs,还有多任务处理也会导致cs。


因为cs是被动的,这个值的高和低都不会是问题的原因,只会是一种表现,所以它只能用来做性能分析中的证据数据。


在我们的这个图中,显然是由于in引起的cs,CPU 队列那么高也是由in导致的。像这样的问题,你可以去看我们在之前提到的si CPU高的那个分析链了。



Swap




Swap 的逻辑是什么呢?它是在磁盘上创建的一个空间,当物理内存不够的时候,可以保存物理内存里的数据。如下图所示:


监控分析篇|解析操作系统级监控


先看和它相关的几个参数。



在操作系统中,vm.swappiness 是用来定义使用 swap 的倾向性。官方说明如下:

  • 值越高,则使用 swap 的倾向性越大。

  • 值越低,则使用 swap 的倾向性越小。


但这个倾向性是谁跟谁比呢?简单地说,在内存中有 anon 内存 (匿名而链表,分为:inactive/active) 和 file 内存 (映射页链表,也分为:inactive/active),而 swappiness 是定义了对 anon 页链表扫描的倾向性。参考内容:

https://cloud.tencent.com/developer/article/1576347?from=information.detail.linux%20%E5%8C%BF%E5%90%8D%E5%86%85%E5%AD%98%20anon


swapiness默认是 60%。注意,下面还有一个参数叫vm.min_free_kbytes。即使把vm.swappiness改为 0,当内存用到小于vm.min_free_kbytes时照样会发生 Swap。


想关掉 Swap 就swapoff -a。


和 Swap 相关的计数器有:top中的Total、free、used和vmstat里的si、so。


说到 Swap,在性能测试和分析中,我的建议是直接把它关了。


为什么呢?因为当物理内存不足的时候,不管怎么交换性能都是会下降的,不管是 Swap 还是磁盘上的其他空间,都是从磁盘上取数据,性能肯定会刷刷往下掉。






总结

监控平台再花哨,都只是提供数据来给你分析的。只要知道了数据的来源、原理、含义,用什么工具都不重要。

性能分析的时候,不会只看操作系统一个模块或哪几个固定计数器的。这些动态的数据,需要有分析链把它们串起来。

操作系统提供的监控数据是分析链路中不可缺少的一环,除非你能绕过操作系统,又能很确切地定位出根本原因。