跳转至

18丨CentOS:操作系统级监控及常用计数器解析(下)

在上一篇文章中,我们已经讲了监控系统层面的分析思路以及CPU分析,今天我们分析一下操作系统中其他的层面。

首先是I/O。

I/O

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

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

我们对I/O的判断逻辑关系是什么呢?

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

这个简化的图是思虑再三的结果。

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

在这张图中,我们取出一条数据来做详细看下:

Device:   rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s  avgrq-sz 
vda        0.00     0.67   18.33  114.33   540.00 54073.33   823.32
avgqu-sz   await r_await w_await  svctm  %util
127.01  776.75    1.76  901.01   7.54 100.00 

我解释一下其中几个关键计数器的含义。

svctm代表I/O平均响应时间。请注意,这个计数器,有很多人还把它当个宝一样,实际上在man手册中已经明确说了:“Warning! Do not trust this field any more. This field will be removed in a future sysstat version.” 也就是说,这个数据你爱看就爱,不一定准。

w_await表示写入的平均响应时间;r_await表示读取的平均响应时间;r/s表示每秒读取次数;w/s表示每秒写入次数。

而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++等,在分析过程中都直接看业务系统就好了。

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

[root@7dgroup ~]# free -m             
         total        used        free      shared  buff/cache   available
Mem:           3791        1873         421         174        1495        1512
Swap:             0           0           0
[root@7dgroup ~]#

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

因为Linux通常都会把用的内存给cache,但是不一定会用,所以free肯定会越来越少,但是available是计算了buffcache中不用的内存的,所以只要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内存立即就有了。你看上面这个图,就是一个机器上有两个节点,先被杀了一个,另一个接着泄露,又把内存耗光了,于是又被杀掉,最后内存全都空闲了。

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

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

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

NetWork

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

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

Recv_Q和Send_Q

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

数据发送过程是这样的。

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

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

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

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

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

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

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

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

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

三次握手和四次挥手

我们先看握手图:

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

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

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

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

[root@7dgroup ~]# netstat -s |grep -i listen    
8866 SYNs to LISTEN sockets dropped

那么半连接队列和什么参数有关呢?

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

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

[root@7dgroup2 ~]# netstat -s |grep overflow    
154864 times the listen queue of a socket overflowed

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

  1. net.core.somaxconn:系统中每一个端口最大的监听队列的长度。
  2. net.core.netdev_max_backlog:每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。
  3. 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左右,服务器有那么穷吗?

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的倾向性。官方说明如下:

swappiness
This control is used to define how aggressive the kernel will swap memory pages. Higher values will increase agressiveness, lower values decrease the amount of swap.
A value of 0 instructs the kernel not to initiate swap until the amount of free and file-backed pages is less than the high water mark in a zone.
The default value is 60.

  1. 值越高,则使用swap的倾向性越大。
  2. 值越低,则使用swap的倾向性越小。

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

 /*
  * With swappiness at 100, anonymous and file have the same priority.
  * This scanning priority is essentially the inverse of IO cost.
  */
 anon_prio = swappiness;
 file_prio = 200 - anon_prio;

也就是说如果swappiness设置为100时,则anon和file内存会同等的扫描;如果设置为0时,则file内存扫描的优先级会高。但是这并不是说设置为了0就没有swap了,在操作系统中还有其他的逻辑使用swap。

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

想关掉Swap就swapoff -a

和Swap相关的计数器有:top中的Totalfreeusedvmstat里的siso

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

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

总结

对操作系统的监控及常用计数器的分析会涉及到很多的内容,所以两篇文章可能也是覆盖不全的,我只把在性能测试分析工作中经常见到的计数器解析了一遍。总体来说,你需要记住以下三点:

  1. 监控平台再花哨,都只是提供数据来给你分析的。只要知道了数据的来源、原理、含义,用什么工具都不重要。
  2. 性能分析的时候,不会只看操作系统一个模块或哪几个固定计数器的。这些动态的数据,需要有分析链把它们串起来。
  3. 操作系统提供的监控数据是分析链路中不可缺少的一环,除非你能绕过操作系统,又能很确切地定位出根本原因。

思考题

我为什么说用什么监控平台并不重要呢?

欢迎你在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,一起交流进步。

精选留言(15)
  • 罗辑思维 👍(21) 💬(2)

    当操作系统中配置了vm.swappiness是 30%,那么当内存用到1-30%=70%的时候,就会发生 Swap。 高老师,文中对swappiness参数设置值描述跟倪鹏飞老师在专栏讲解有不一样的地方。个人还是认同swappiness不是内存的百分比。下面这段是摘自是倪鹏飞老师《Linux性能分析实战》第19讲。 --------------------------- /proc/sys/vm/swappiness 选项,用来调整使用 Swap 的积极程度。 swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页。 虽然 swappiness 的范围是 0-100,不过要注意,这并不是内存的百分比,而是调整 Swap 积极程度的权重,即使你把它设置成 0,当剩余内存 + 文件页小于页高阈值时,还是会发生 Swap。

    2020-04-05

  • 木剑温华 👍(9) 💬(1)

    一个 TCP 连接大概占 3KB,创建 10 万个连接,才100000x3KB≈300M左右,何况最多才 65535 呢?服务器有那么穷吗? 这里感觉老师说的有一点问题,一个tcp链接由四元组组成:sip:sport---cip:cport,单机最多65535个端口,但是可以根据公式可以看到只影响了sport的数量,但是cip和cport的组合是无穷尽的,所以单机理论最大连接数远大于65535,我之前在iot项目和消息推送项目做过相关的压测,成功地把单机最大长连接数提高到100w以上。

    2021-08-31

  • 月亮和六便士 👍(4) 💬(1)

    高老师,看完您的课程,有一些思路了,但是我发现思路和实战之间有一道鸿沟,我已经掉到沟里了,比如 分析的起点拆分响应时间,但是不知道怎么拆分,开发更是一头雾水。应用又是部署在docker里面,好不容易配置了个Tomcat监控,结果重启了一下配置又没了,运维说docker里没法暴露监控端口,真是寸步难行啊

    2020-03-27

  • SeaYang 👍(3) 💬(1)

    使用Linux服务器作为压力机,TPS达到比较高的时候压力机会大量报无法分配请求地址的错误,从而导致TPS直接降为0,命令看了下TIME_WAIT的数量很多,调整了一下几个内核参数,就解决了

    2020-10-27

  • aoe 👍(3) 💬(1)

    老师硬核调优!测试、开发、运维后期在操作系统、网络上都这么强了!

    2020-10-14

  • 黑脸龙猫酱 👍(3) 💬(1)

    老师可否说下对于云服务器,io有些时间段不稳定的情况应该如何处理?

    2020-03-16

  • 小老鼠 👍(3) 💬(1)

    由于上下文切换过多引起性能降低的情形多吗?

    2020-02-18

  • 悦霖 👍(2) 💬(1)

    高老师问一下,每次性能测试看io较高基本都是jbd2这个进程占用大量的IO,怎么进一步分析,而且这个jdb2是个啥?

    2020-02-18

  • Geek_6add55 👍(1) 💬(1)

    老师你好,netstat命令Recv_Q和Send_Q的值显示的是环形缓冲区的队列还是套接字缓冲区的队列

    2023-06-27

  • 闲鱼超人 👍(1) 💬(1)

    监控平台为什么不重要呢? 监控平台的主要用途是为了提供运行时状态数据给我们的,利用这些数据,我们分析性能情况。所以关键是数据、是证据链,是这些数据反馈出来的问题,这是核心。所以从这个角度来说,监控平台是不重要的,因为只要能提供这些你需要的数据,哪个平台都可以。

    2021-02-24

  • David.cui 👍(1) 💬(1)

    高老师讲的还是很透彻的,能分析到非常细微的差别。 高手!

    2020-12-03

  • jy 👍(0) 💬(1)

    老师,请教一个问题, 文中:"在我们做性能分析的过程中,基本上,基于上面这个表格就够通过接收和发送判断瓶颈点发生在谁身上了" 感觉这里没说完整呢 比如发送端Send_Q有值,接收端Recv_Q也有值,说明瓶颈点在接收端,那下一步如何分析接收端呢? 比如发送端Send_Q有值,接收端Recv_Q没有值,说明瓶颈点在发送端或者网络设备,这种情况,那下一步又该如何分析发送端呢? 谢谢老师指导。

    2022-10-21

  • Fzz 👍(0) 💬(2)

    老师你好,想问下系统tps到顶,系统CPU只有20%,系统相响应时间1秒以下,感觉压力发起端也没有瓶颈,想问下这个瓶颈在哪?看了资源的使用也不高,不知道是不是jvm或者应用的的问题?

    2022-06-13

  • 学员141 👍(0) 💬(2)

    在测试环境压测,发现服务器端口占满了,服务器的端口配置是否需要修改?网上说过大又容易被攻击(我们是docker,一个节点上部署了18个应用,节点主机16C16G,有些应用CPU和内存都分配很小,导致都到一个节点上来)

    2021-07-10

  • 0909 👍(0) 💬(1)

    因为数据来源都是一样的,重要的还是要根据数据去找到瓶颈和优化方案

    2021-06-21