当前位置: 首页 > 科技观察

系统调用导致网络收包卡顿的问题分析_0

时间:2023-03-18 18:31:22 科技观察

系统调用导致网络卡顿的问题分析,为了保证系统的快速响应,系统设置的超时时间为30毫秒。检查中发现,系统多台服务器的超时交易量在逐渐增加。为规避系统运行风险,协调网络、操作系统等专家共同分析,分析过程中补充了大量Linux操作系统内核。知识,现记录分析过程和其中用到的知识点,为解决类似问题提供一个通用的解决方案。1、申请系统超时。外部系统将请求发送到前端服务。前端服务执行业务逻辑后,重新组装消息并将请求发送到后端服务。事务处理超时体现在前端服务对后端服务的请求上,但是根据网络设备的镜像网络包分析,后端服务的处理时间并没有异常,并且前端发送给后端的请求可以被后端快速处理。所以问题还是出现在前端服务上。图1系统逻辑架构由于系统交易量大,响应时间短,处理过程的日志记录很少,无法根据前端服务的应用日志定位根因。对于前端服务向后端服务的请求,选择一个超时事务较多的时间段内的网络镜像包进行分析。发现超时事务集中在两个固定时间点,即:11:29:4.200,9个事务超时,平均TCPack_rtt为5毫秒;11:29:54.100,2个事务超时,平均TCPack_rtt为6毫秒。每个时间段的超时交易的rtts都大于30毫秒,一些没有超时的交易的ack的rtts比较长。其他正常交易时间的ack_rtt在0.06毫秒左右。图2网络镜像包异常现象根据该现象,初步怀疑是网络收发包出现拥塞问题,操作系统的网络协议栈没有及时处理接收到的网络包。具体处理过程是前端向后端服务发送请求,后端服务立即响应并返回响应包。因为协议栈没有及时处理响应包,导致相应的ack包没有及时返回,前端服务也没有及时收到。后台服务返回,导致超时现象。为了验证怀疑方向,物理重启了一台服务器,重启后超时现象消失;同时在另一台服务器上只重启了应用系统,超时现象并没有缓解。因此,可以确定事务超时现象是由操作系统引起的,与应用系统的处理性能无关。2、排查由于怀疑与操作系统的协议栈处理有关,为了方便排查,避免影响生产的稳定运行,我们使用perf和hping3进行异常复现和问题分析。perf是Linux的性能分析工具,可以在系统内核函数级和指令级搜索热点,可以用来分析程序中热点函数的CPU占用率,从而定位性能瓶颈。hping3是一个开源的网络工具,通过rawSocket直接拼装icmp、udp、tcp报文,并记录对方服务器的ack响应时间。图3故障处理方案分析解决方案是在同一子网内找另一台服务器部署hping3工具,不断向异常服务器发送TCP包,记录ACK返回延迟的时间点。在异常服务器上部署perf工具,跟踪操作系统的内核调用,发现异常点。通过hping3每1毫秒发送一个网络包,记录收到ack包的时间。可以发现在1650140557这个时间点有大量的rtt时间比较长,分析perf工具收集到的内核调用数据可以发现,同时有大量的rtt发起的请求SS进程。读取此时正在执行的系统调用函数。根据调用栈proc_reg_read可以定位到程序正在读取/proc目录下的文件内容。图4内核系统异常调用栈由于生产服务器上部署了各种代理和大量的监控脚本,因此怀疑是这些代理和监控脚本发起的ss命令调用。在系统中使用ss命令搜索脚本,可以找到callss的命令行如图5所示。图5触发异常的shell调用通过strace跟踪ss的调用过程,如图6.图6trace跟踪ss调用过程,关注读处理慢的系统调用。可以发现读取处理时间超过10ms的地方有5处,最长处理时间为189ms;根据3号文件句柄,可以确认正在读取的文件是“/proc/slabinfo”。图7ss命令系统调用异常至此可以确认是网络包接收卡住,是监控周期性调用ss命令导致的。ss命令会读取/proc/slabinfo,读取时,读取函数的部分系统调用处理较慢,影响网络接收包的处理。为了进一步验证上述结论,我们在异常服务器上使用ss命令停止监控脚本,并启动前端服务将其加入生产队列。经过长时间的观察,没有发现超时现象。然后在服务器上连续多次手动执行cat/proc/slabinfo,可以发现超时现象又出现了。三、深入分析原因1、第一个问题:为什么读取/proc/slabinfo会导致网络收包卡顿?网络协议栈接收到tcp包,自动返回ack,在中断中被操作系统处理,优先级更高。但是读取/proc/slabinfo对于用户来说是一个通用的系统调用。为什么会影响内核的中断处理呢?首先,我们需要了解Linux操作系统代码执行的几种上下文场景。硬件中断上下文:优先级最高,只要有硬件中断就会立即执行,包括时钟中断和网卡中断;软件中断上下文:当硬件中断执行完成或系统调用完成时执行,这是操作系统内核核心代码的主要执行环境,包括进程调度、网络协议栈处理等;内核进程上下文:运行在内核态,但受进程调度管理,按照进程调度分配的时间片执行,如果超过时间片,可能被其他人执行用户进程上下文:这是它所在的环境大多数应用程序都已执行。它由进程调度管理,根据进程调度分配的时间片执行。如果超过执行时间片,将强制进行进程切换。那么它们的执行优先级不是:硬件中断>软件中断>内核进程>用户进程。事实上,情况并非完全如此。由于硬件中断、软件中断和内核进程都运行在内核态,运行在同一个地址空间,所以所有的数据都是共享的。如果硬件中断强制执行软件中断,软件中断强制中断内核进程的执行,可能会导致内核数据混乱或各种死锁。为了保护关键数据的安全和完整性,软件中断可以关闭硬件中断,内核进程可以关闭硬件中断和软件中断。在执行cat/proc/slabinfo时,程序首先运行在用户进程的上下文中,然后执行系统调用read切换到内核进程的上下文中。由于内核的内存分配信息保存在slabinfo中,在读取内存分配信息时,为了保证数据的一致性,在内核进程的上下文中关闭了硬件中断和软件中断。由于在硬件中断中进程调度也是由时钟中断触发的,如果硬件中断和软件中断长时间关闭,则不会执行进程调度。如果read的执行时间长,那么禁止中断的时间会比较长,进程会长时间占用CPU不释放,导致内核协议栈无法处理网络接收包。查看linux内核代码/linux-3.0.101/mm/slab.c中的s_show函数,可以看到如下禁止中断代码图8读取slabinfo时禁止中断的内核代码2.第二个问题:服务器有40CPU,为什么一个ss命令会阻塞网络包的处理?理论上应该还有39个CPU要处理?这里涉及到RPS(ReceivePacketSteering)和RFS(ReceiveFlowSteering)技术,其主要作用是将大量高速网卡的网络包分配给多核CPU单独处理,但这种分配是预先根据socket四元组(源IP、源端口、目的IP、目的端口)分配分配给不同的CPU处理。ss命令在哪个CPU上执行,分配给这个CPU的网络包处理就会卡住。所以在生产环境的表现上,只是部分网络包被卡住,并不是所有的网络包都会被卡住。3、第三个问题:为什么读/proc/slabinfo慢?Slab算法以字节为单位管理内存,是内核的一种小型内存管理算法。它的特点是基于对象的管理。slab分配算法使用cache来存储内核对象,将相同类型的对象归为一类。每当请求这样一个对象时,slab分配器从一个slab列表中分配一个这个大小的单元,当它要被释放时,通过将它重新保存在该列表中而不是直接返回给伙伴系统来避免这些内部碎片.slab分配器不会丢弃分配的对象,而是释放它们并将它们保存在内存中。以后再请求新的对象时,可以直接从内存中获取,不需要重复初始化。可以通过cat/proc/slabinfo查看slab分配的内存。其中包含的信息主要是统计信息,包括每个对象内存总数、分配对象数、每页内存对象数、占用内存页数等。这些统计数据从何而来?内核中的统计信息是开箱即用的吗?通过看代码可以发现,都是一点一点积累和总结的。如果内存对象的数量很大,那将是一个非常耗时的循环。其内核代码如下:通过cat/proc/slabinfo|输出sort-n-k3,查看哪些内存对象数量大。图9slab内存对象个数,如图9所示,分别是dentry、buffer_head、proc_inode_cache。dentry是文件目录到inode映射的缓存;buffer_head是硬盘的块缓存;proc_inode_cache是??/proc目录下inode节点信息的缓存。dentry和proc_inode_cache分别达到了797万和388万。这个数据量很大,导致输出/proc/slabinfo时耗时很长。4、第四个问题:为什么重启服务器后网络卡顿就消失了,为什么之后又变慢了?重启后freeze现象消失的问题很容易回答,因为重启后slab中缓存的信息全部释放,对象数量少,slabinfo统计处理速度更快,网络包的接收不会受到影响。重启运行一段时间后,又会出现卡顿现象,因为slab中缓存的信息又变多了,那么到底是谁导致slab缓存了大量信息呢?slab中大量的内存对象是dentry和proc_inode_cache。查看/proc目录下的文件时,操作系统会自动将目录信息和inode信息缓存到这两个对象中。因此,有程序会周期性地读取/proc目录下的文件,导致这两个内存对象缓存的数量增加。再次跟踪ss命令,可以发现ss命令会扫描/proc下所有进程打开的文件句柄,包括socket链接。我们的服务器经常会维护上万个链接,这些链接的存活时间最短只有几秒,最长只有几个小时。所以,执行一次ss-lntp命令,就会缓存上万个proc_inode_cache和dentry。如果周期性地执行这条命令,opensockethandle会不断变化,从而导致slabcache的数量不断增加。4、问题解决通过以上分析,可以确定网络封包卡顿的原因是:周期性的调用ss-lntp命令,同时这台服务器上的网络链接不断变化,造成很大的要缓存在slab中的内存对象的数量。而ss会读取/proc/slabinfo中的汇总信息,而slabinfo中的汇总信息每次都是通过一个一个的累加得到的,效率较低,并且在累加的时候关闭了操作系统的中断处理,导致接收到网络报文无法及时处理。因此,可能的解决办法:(1)执行echo"2">/proc/sys/vm/drop_caches,强制操作系统回收inode和dentry中的缓存信息。该命令执行过程中,系统会卡顿,卡顿时间为数秒至数分钟。关键业务操作时慎用;(2)定时重启服务器,释放slab中的缓存信息;(3)停止周期调用ss-lntp;(4)对于ss命令,可以去掉-p参数。去掉这个参数后,ss将不再扫描/proc目录下的进程信息,也不会增加slab中的内存对象数量。另外,也可以升级到更高版本的ss命令。在高版本的ss命令中,不再读取/proc/slabinfo,因为没有可以获取到的有效信息。以下是github中的commit信息。图10GitHubcommit信息汇总这个问题的分析持续了大概一两个月。期间,一度迷茫,没有进步。有了初步的分析思路后,又补充了操作系统内核的各种知识,主要包括进程调度、调度优先级、中断处理、网络协议栈处理、虚拟文件系统等,终于可以把内核的来龙去脉说清楚了整个问题很清楚。参考资料:《如何调试Kubernetes集群中的网络延迟问题?》TheoJulienne分布式实验室《网卡多队列:RPS、RFS、RSS、FlowDirector(DPDK支持)》rtoaxCSDNhttps://rtoax.blog.csdn.net/article/details/108987658https://github.com/shemminger/iproute2/commit/10f687736b8dd538fb5e2bdacf6bef2c690ee99d