介绍:压测场景TIME_WAIT处理流程=====某私有云项目有压测场景。LoadRunner用于在Windows压测机上进行业务压测。测试运行一段时间后,大量端口无法分配错误。其实通过问题描述和Windows的报错信息,基本可以确定是压力测试仪的问题。不过,可能原因很多,一直没有达成一致。因此,我借此机会分析了客户压测机成为压测瓶颈的可能性。除了CPU、网络、I/O等机器性能参数外,还需要考虑网络协议引入的资源短缺问题。注:以下内容的目的是澄清TCP协议中比较模糊的内容,熟悉协议的可以忽略。TIME_WAIT依据:RFC793TCP协议=============================================================================================================================================================================其具体设计的目的,简而言之就是保证数据在一个可靠的传输不稳定的物理网络环境;因此,TCP在具体实现上增加了很多异常处理,整体协议变得更加复杂。了解TCP协议,推荐阅读RFC793,详细内容可参考文末链接[1]。同时,还需要了解“TCP状态转换”状态机,如下图所示,具体可以参考文末资料[2]。图1.TCP状态转换图本文只讨论TW在TCP协议中的作用,不涉及协议整体的分析。挥手四次后的TIME_WAIT状态后面会被TW缩写代替。2.1TW的作用首先,主要作用是保证TCP连接关闭的可靠性。考虑在四路挥手过程中,如果主动关闭方发送的LAST_ACK丢失,被动关闭方将重传FIN。此时如果主动关闭方对应的TCPEndpoint没有进入TW状态而是直接在内核中清理,根据协议,主动关闭方会认为自己没有打开这个端口,并响应与RST到由被动关闭方重传的FIN。这种行为最终导致被动关闭方认为连接异常关闭,业务上可能会收到异常报错。其次,TW状态还可以防止同一TCP端口从网络上的先前连接接收重复的数据包。理论上,数据包在网络上的过期时间对应MSL(MaximalSegmentLifetime)。随着操作系统的不断发展,也有例外。这部分搜索PAWS应该可以找到很多类似的文章。同样,端口进入TW状态,也避免了被操作系统快速重用的可能性。2.2TW形成的原因当主机操作系统主动关闭TCPEndpoint(socket)时,TCPEndpoint进入TW状态。以Windows为例,Windows内核会相应清理TCPEndpoint数据结构,然后放入额外的TW队列中,设置一个2MSL定时器,定时器超时后调用相应的释放代码。Linux上的实现类似。目前有很多“TCP连接”进入TW的说法,但我们可能需要了解“连接”其实是一个抽象的概念。实际上,“连接”在逻辑上是存在的,因为客户端和服务器,以及中间可能涉及的四层设备,同时为一次传输创建关联的TCP资源(Endpoint,或Session)。准确理解TW状态,即TCPEndpointTW进入TW状态。2.3总结TW是为了保证TCP连接的正常终止(避免端口被快速重用),同时也是保证网络丢失的数据包正常到期(防止之前连接的数据包被错误接收).TW暗杀技巧详见文末资料[3]。概念澄清========几个可能比较模糊的地方欢迎讨论,如下:作为连接的双方,客户端和服务端的TCPEndpoint都有可能进入TW状态(极端情况下,双方可能同时进入TW状态)。这种情况是顺理成章的,具体可以参考文末的资料[4]。TW是标准的一部分,并不代表TCP端口或连接状态异常。(这个概念很重要,避免陷入一些不必要的陷阱。)虽然CLOSE_WAIT也是标准的一部分,但它的出现表明本地TCPEndpoint处于半关闭状态,往往是因为应用程序没有调用socket相关关闭或关机。可能的原因是应用程序还有未发送的数据,这种情况下CLOSE_WAIT最终会消失。详细描述这部分。处于CLOSE_WAIT状态的端口会长时间缓慢积累。这种情况需要注意。积累到一定程度,端口资源就不够用了。对于TCPEndpoint这个名词,很多人可能不太了解。这里简单解释一下:在Windows2008R2之前,socket是用户态(usermode)的概念。大多数Windows套接字应用程序基本上都是基于Winsock开发的,由中间层AFD.sys驱动翻译成内核tcpip.sys协议栈驱动可接受的TCPEndpoint数据结构。2008R2之后,为了方便内核的网络编程,微软在WindowsKernel中提供了WSK,即Winsock在内核中的实现。文中提到的TCPEndpoint是Windows内核中TCPIP.sys驱动文件实现的一种TCP数据结构,在Linux上也对应socket。本文简单使用Endpoint来指代内核的“套接字”。TW优化方法===========对于linux,优化方法已经讨论的很多了。以Centos为例,开启时间戳时,配置tcp_tw_reuse和tcp_tw_recycle。对于客户端,连接请求的发起者。net.ipv4.tcp_timestamps=1net.ipv4.tcp_tw_reuse=1对于服务器端,连接请求接收方net.ipv4.tcp_timestamps=1net.ipv4.tcp_tw_recycle=1注意:启用tcp_tw_recycle会带来一些副作用,具体在NAT地址转换场景下,容易出现连接异常。详情可参考文末资料[4]。配置max_tw_buckets,连接请求的发送方和接收方通用,但需要注意的是这个选项本身就违背了TW设计的初衷。net.ipv4.tcp_max_tw_buckets=5000配置ip_local_port_range,连接请求发起者。net.ipv4.ip_local_port_range=500065535针对Windows,资料较少,这边借之前的工作经验,总结如下:WindowsVista/WindowsServer2008之前的操作系统,注册表端口范围:HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesTcpipParametersMaxUserPort=0n65534TW超时时间:HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesTcpipParametersTcpTimedWaitDelay=0n30Windows7/WindowsServer2008R2andlaterversionsPortrange:netshintipv4setdynamicporttcpstart=1025num=64511TWTimeout:HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesTcpipParametersTcpTimedWaitDelay=0n30WindowsServer2012andearlier30:0n30WindowsServer2012andearlier30:0n30de(3ci-mallier:0n30de)-300(decimal)WindowsServer2012R2andlater:2-300(decimal)Windows8.1andlater:2-300(decimal)Note:Anymodificationinvolvingtheregistrywillonlytakeeffectafterrestartingthemachine.UnlikeLinux,Windowsdoesnothaveafastrecoverymechanism,andthereisnopossibilityoffastrecoveryofTW.Itcanonlywaitfor2MSLtoexpire(ieTcpTimedWaitDelay).TheonlysituationwhereWindowscanquicklyreclaimtheEndpointintheTWstate:theSEQsequencenumberofthenewconnectionrequest>theSEQsequencenumberrecordedbytheEndpointintheTWstate.Atthispoint,thekernelconsiderstheSYNrequestlegitimate.Here,theTCPEndpointofthisTWstatemustbeontheserverside(theserviceportopenedthroughsocketaccept).(此能力必须开启Windows的RFC1323选项,内容可自行搜索)压测客户端无法分配端口原因分析=========================无法分配有两种可能:完全随机的动态端口请求,错误的端口分配异常,基本上操作系统没有可用的端口。指定端口的绑定应用报错端口分配异常,可能存在端口使用冲突。对于第一种情况,首先需要通过netstat-ano进行快速检查,分析是否有端口满,以及满端口的TCPEndpoint状态。考虑针对不同状态的不同方案。比如极端情况下,如果服务器没有异常,端口分配失败,详情请参考文末信息[5]。以Windows操作系统的TW状态全是可用端口的场景为例(在Linux上出现的可能性较小)。在分析问题之前,需要对Windows上的端口分配原理有一个大概的了解。Windows和Linux具有非常不同的动态分配端口机制。Linux应该是基于五元组分布的粗略理解,即可能有同一个动态端口访问不同服务器的服务端口。Windows的动态端口分配基于位图查找。无论从哪里访问,动态端口池最大为1025-65536,即64511。考虑最短TW超时30秒,如果以64511/29=2225端口/s的速度创建端口,则很有可能30秒后无法分配端口。这还是在连接处理比较快的情况下。如果连接建立后没有关闭,或者关闭时间过长,创建端口的速度还是需要继续降低,避免端口出现问题。了解了TW形成的原因之后,相应的解决办法就会更加清晰。降低应用程序创建端口的速率。考虑连接时长和TW超时时间,计算出一个比较合理的连接建立速度。但物理机操作系统、CPU/内存、网络IO等可能影响连接状态,准确计算困难;同时,就应用程序而言,需要额外的逻辑来降低端口创建速度,这是不太可能的。在这个压测场景中,通过增加机器变相的降低了端口需求。压力测试一般考虑整个系统在某个固定阈值下的响应。在压力一定的情况下,通过分配压力可以减少对端口资源的占用。改变连接的行为,使用持久连接(注意:非HTTP长连接),比如500并发只建立500个连接。好处很明显,但是坏处也很明显。长连接不符合用户真实行为,压测结果可能失真。同时,该方法需要应用程序的支持。防止本机TCPEndpoint进入TW状态,可以参考以下两种解决方案。a)不允许本机主动关闭连接,而让对方主动关闭。这样宿主机就进入被动关闭过程,应用关闭TCPEndpoint后,可以直接释放端口资源。有些协议本身有行为或参数控制是保持连接还是请求对方关闭连接。在考虑此类问题时,可以适当使用它们。比如HTTP长短连接,具体可以参考文末的资料[4]。b)通过TCPReset强制释放端口。任何一方都可以发送TCPReset,无论是发送方还是接收方,相应的TCPEndpoint在看到TCPReset后将立即被删除。这里可以设置socket的SO_LINGER选项,比如配置Nginx,具体可以参考文末官方文档[6]。图2:NginxLingeringConfiguration参考说明对于压力测试工具本身,官网也有类似的ABRUPT选项,具体可以参考文末官方文档[7]。图3:LoadRunnerABRUPT配置选项说明作者:陈戈原文链接
