大家好,今天来介绍timewait过多的原因(服务端大量time_wait原因)的问题,以下是渲大师小编对此问题的归纳和整理,感兴趣的来一起看看吧!
TPS不稳定队列产生大量的TIME_WAIT
问题现象 :
在高并发的压力测试时候,有时候会TPS不稳定或者上不去的现象,同时观察到队列产生大量的TIME_WAIT。
基本的分析思路:
使用netstat -anfind “TCP”,发现大量的TIME_WAIT状态的队列。以至于部分客户端连接不上去,导致TPS不稳定或者上不去,成功率下降。
发现这种情况表示有较多的队列在等待,原因是服务器端或者客户端的连接数有限制。
可能的情况有:服务器系统端口数量不够。
我们先来看一张图:
TCP连接的建立可以简单的称为三次握手,而连接的中止则可以叫做四次握手。
建立连接
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据,也就是ESTABLISHED状态。
结束连接
TCP有一个特别的概念叫做half-close,这个概念是说,TCP的连接是全双工(可以同时发送和接收)连接,因此在关闭连接的时候,必须关闭传和送两个方向上的连接。客户机给服务器一个FIN为1 的TCP报文,然后服务器返回给客户端一个确认ACK报文,并且肢裤发送一个FIN报文,当客户机回复ACK报文后(四次握手),连接就结束了。
LISTEN: 表示监听状态。服务端调用了listen函数,可以开始accept连接了
SYN_SENT:表示客户端已经发送了SYN报文。当客户端调用connect函数发起连接时,首先发SYN给服务端,然后自己进入SYN_SENT状态,并等待服务端发送ACK+SYN。
SYN_RCVD:表示服务端收到客户端发送SYN报文。服务端收到这个报文后,进入SYN_RCVD状态,然后发送ACK+SYN给客户端。
ESTABLISHED:表示连接已经建立成功了。服务端发送完ACK+SYN后进余饥睁入该状态,客户端收到ACK后也进入该状态。
FIN_WAIT_1:表示主动关闭连接。无论哪方调用close函数发送FIN报文都会进入这个这个状态。
CLOSE_WAIT:表示被动关闭方等待关闭。当收到对方调用close函数发送的FIN报文时,回应对方ACK报文,此时进入CLOSE_WAIT状态。
FIN_WAIT_2:表示被动关闭方同意关闭连接。主动关闭连接方收到被动关闭方返回的ACK后,会进入该状态。
LAST_ACK:表示被动关闭方发送FIN报文后,等待对方的ACK报文状态,当收到ACK后进入CLOSED状态。
TIME_WAIT:表示收到对方的FIN报文并发送了ACK报文,就等2MSL后即可回到CLOSED状态了。如果FIN_WAIT_1状态下,收到对方同时带FIN标志和ACK标志的报文时,可以直接进入TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
CLOSED:结束
TCP要保证在所有可能的情况下使得所有的数据都能够被投递。当你关闭一个socket时,主动关闭一端的socket将进入TIME_WAIT状态,而被动关闭一方则转入CLOSED状态,这的确能够保证所有的数据都被传输。当一个socket关闭的时候,是通过两端互发信息的四次握手过程完成的,当一端调用close()时,就说明本端没有数据再要发送了。这好似看来在握手完成以后,socket就都应该处竖岁于关闭CLOSED状态了。
但这有两个问题:
首先,我们没有任何机制保证最后的一个ACK能够正常传输。
第二,网络上仍然有可能有残余的数据包(wandering duplicates),我们也必须能够正常处理。
我们再来看看TIME_WAIT:
TIME_WAIT是TCP连接断开时必定会出现的状态。是没有办法避免掉的。
TCP连接是全双工的,因此每个方向必须单独进行关闭。客户端与服务器端建立TCP/IP连接后关闭socket,服务端连接的端口状态未TIME_WAIT.主动关闭的一方在发送最后一个ACK后,就会进入TIME_WAIT状态。
总结主要的原因有两点:
1、防止上一次连接中的包,迷路后重新出现,影响新的连接
2、可靠的关闭TCP连接: 在主动关闭方发送最后一个ACK(FIN),有可能会丢失,这个时候被动方会重新发送FIN,如果这时主动方处于closed状态,就会响应RST而不是ACK。所以主动方要处于TIME_WAIT状态,而不是CLOSED。
修改的方法:
增加服务器端口数量或者增加服务器的数量
缩短超时时间
具体修改方法:
运行:regedit
系统注册表:\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters下,新建2个DWORD值:
MaxUserPort设置为:65534,缺省为5000;
TcpTimedWaitDelay设置为:30, 缺省为240。 —-缩短该时间 (单位:s)
服务产生大量TIME_WAIT如何解决
当TIME_WAIT超过linux系统tw数量的阀值(可用数量不会大于65535),系统会把多余的time-wait socket 删除掉,并且显示警告信息,如果是NAT网络环境又存在大量访问,会产生各种连接不稳定断开的情况,从而影响了服务的稳定性。
一、状态的产生
要解决TIME_WAIT状态过多的问题,先来研究下TIME_WAIT状态的产生,下面是TCP连接断开时的四次挥手状态转换图,说明一点,途中显示的是客户端主动断开连接,tcp连接也可以由服务器端主动断开连接。我们先来描述一下断开的状态:
1)客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务物旁迹器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2MSL(最长报文段寿命,RFC规定一个MSL为2min,linux中一般设置为30s)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
可以看到TIME_WAIT状态产生是在tcp连接主动关闭的一端产生的正常tcp状态,超过两个MSL之后,就会关闭,释放占用的端口。基于以上的分析我们可以推断,在我们的应用中产生大量TIME_WAIT状态的根本原因是频繁创建断开连接TCP连接。要解决TIME_WATIT状态过多的问题,就要分析我们的应用把频繁创建的短连接改为长连接。
二、常见的短连接产生的场景
1.服务连接服务
后台业务服务器,通常需要调用redis、mysql以及其他http服务和grpc服务,在服务相互调用中,如果使用的是短连接,高并发时就会产生大量TIME_WAIT,如何解决呢?一般情况下,redis等客户端会有连接池,我们要做的是设置好相关的连接服用参数,一般会有罩并连接数、连接重用时间、连接空闲启袜数等。所以在应用中通过设置合理的连接池参数可以避免TIME_WAIT状态过多的问题:
1.检查http连接池
2.检查grpc连接池
3.检查redis连接池
4.检查mysql连接池
…
我们来查看一个mysql连接池配置信息,最大连接数100,最大空闲连接数10,测试的并发数50,产生的效果如下:
可以看到TIME_WAIT状态快速上升,我们查看redis客户端的连接情况:
{MaxOpenConnections:100 OpenConnections:1 InUse:0 Idle:1 WaitCount:0 WaitDuration:0s MaxIdleClosed:0 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:17 InUse:15 Idle:2 WaitCount:0 WaitDuration:0s MaxIdleClosed:48 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:51 InUse:44 Idle:7 WaitCount:0 WaitDuration:0s MaxIdleClosed:82 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:51 InUse:50 Idle:1 WaitCount:0 WaitDuration:0s MaxIdleClosed:90 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:50 InUse:49 Idle:1 WaitCount:0 WaitDuration:0s MaxIdleClosed:126 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:51 InUse:49 Idle:2 WaitCount:0 WaitDuration:0s MaxIdleClosed:131 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:50 InUse:49 Idle:1 WaitCount:0 WaitDuration:0s MaxIdleClosed:181 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:51 InUse:51 Idle:0 WaitCount:0 WaitDuration:0s MaxIdleClosed:233 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:51 InUse:51 Idle:0 WaitCount:0 WaitDuration:0s MaxIdleClosed:240 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:46 InUse:38 Idle:8 WaitCount:0 WaitDuration:0s MaxIdleClosed:296 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:51 InUse:50 Idle:1 WaitCount:0 WaitDuration:0s MaxIdleClosed:313 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:51 InUse:50 Idle:1 WaitCount:0 WaitDuration:0s MaxIdleClosed:363 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:51 InUse:50 Idle:1 WaitCount:0 WaitDuration:0s MaxIdleClosed:409 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:50 InUse:48 Idle:2 WaitCount:0 WaitDuration:0s MaxIdleClosed:438 MaxLifetimeClosed:0}
{MaxOpenConnections:100 OpenConnections:49 InUse:49 Idle:0 WaitCount:0 WaitDuration:0s MaxIdleClosed:494 MaxLifetimeClosed:0}
分析发现MaxIdleClosed数据持续上升,此为mysql客户端连接池配置不合理产生大量TIME_WAIT状态的例子
2.网络抖动
网络情况不好时,如果主动方无TIME_WAIT等待,关闭前个连接后,主动方与被动方又建立起新的TCP连接,这时被动方重传或延时过来的FIN包过来后会直接影响新的TCP连接。同样网络情况不好并且无TIME_WAIT等待,关闭连接后无新连接,当接收到被动方重传或延迟的FIN包后,会给被动方回一个RST包,可能会影响被动方其它的服务连接。
网络抖动问题比较好排查,直接使用ping命令可以观察到。
什么情况下出现大量timewait
如果是做 server, 有可能会 socket耗尽
如果不是 server 那没影响.
timewait 状态表示对端桐高态已经发局源送 close, 而自念颂己还没有给对方响应, 在 4 分钟后会释放.
系统产生大量timewaite的处理
Timewait 和 TCP 连接的关系:
主动关闭的一端向服务端发送第 1 次挥手的请求,被动方返回了一个 ACK 的包,可以看到被动关闭端再一次发送完 FIN 和 ACK 包以后,主动关闭端就会会先发送一个 ACK 响应回去,然后进入 TIME_WAIT 状态。
我们看到在它进入 TIME_WAIT 状态之前,理派姿论上 4 次挥手已经完成了,为什么 TIME_WAIT 还需要保留一段时间?
这是因为 TCP的协议标准,需要保证4 次挥手尘告绝过程中最后一次连接发送的稳定性,如果ACK包发送不成功,就需要再次发送 ACK 包。
大量的 Timewait 产生会造成文件句柄、内存和端口的占用,由于系统会把过多的 time-wait socket 删除、回收,在网络条件不好的情况下,就可能会导致数据包重复的进行发送。
如何对 TimeWait 进行优化
了解了 Timewait 的影响(结论:通常不会直接造成服务连接的影响,但是会造成一些资源上及新建连接风险),为了避免过多的 Timewait 产生,我们需要考虑去进行一些优化。在单机系统上做性能优化的话,我们需要考虑两点:
第 1 点就是考虑把Timewait 队列加大。在操作系统资源、硬件资源能满足的情况下,我们可以把 tcp_max_tw_buckets 的值数调高,它的缓冲值也就越大。这个数字是我们可以进行操作系统内核优化的。
第 2 点即我们需要尝试修改操作系统内核优化的内容,也就是调整 TIME_WAIT 超出时间,如果它的操作时间能够更快地让操作系统进行回收,那么它就可以更快地释放资源。当然这个时间也不能调的太小,要不然 Timewait 的作用的意义就很友则小了。所以我建议操作系统内核的参数 tcp_fin_timeout 这个值调到 30,这就是一个比较合理的优化空间。
参考链接: https://kaiwu.lagou.com/course/courseInfo.htm?courseId=42#/detail/pc?id=1563
TIME_WAIT过高的处理
相信很多人都遇到过服务器出现大量TIME_WAIT的情况,大多数的解决办法是sysctl修改如下参数
net.ipv4.tcp_timestamps = 1 #上述两项生效的前提是TCP连接两端都要启用TCP时间戳
过一会发现TIME_WAIT数量直线下降后,服务貌似也没出扰裂问题,ok问题解决!其实不然。想真正理解所谓“大量的TIME_WAIT的问题”,我们要先理解TIME_WAIT。
TIME_WAIT是有友好的,不是多余的,是主动关闭TCP连接的一方在调用socker的close操作后最终会进入TIME_WAIT状态。服务器在处理客户端请求的时候,如果你的程序设计为服务器主动关闭,那么你才有可能需要关注这个TIMEWAIT状态过多的问题。如果你的服务器设计为被动关闭,那么你首先要关注的是CLOSE_WAIT。
换句话说:在一台负载均衡的服务器上(以Nginx为例),客户端向Nginx的请求,作为服务器来看属于被动连接;Nginx向Web服务器的请求属于主动连接。在讨论TIME_WAIT优化时,我们应该关注的是主动连接,即Nginx对Web服务器的连接。
1、防止前一个连接延迟的数据被后面复用的连接错误的接收;
2、可靠的关闭TCP连接;
关于TIME_WAIT作用的深入理解需要配合Socket连接五元组、RFC 793等概念,本文不重点讨论,感兴趣的童鞋请移步 老男孩博客 。
内核里有保存所有连接的一个hash table,这个hash table既包含TIME_WAIT状态的连接,也包含其它状态的连接。主要用于有新的数据到来的时候,从这个hash table里快速找到这条连接。还有一个hash table用来保存所有的bound ports,主要用于可以快速的找到一个可用的端口或者随机端口。
因此,内核保存这些数据必然会占用一定的内存,同理每次找到一个随机端口需要遍历一遍bound ports,必然需要一些CPU时间!
TIME_WAIT很多,既占内存又耗CPU,但其实进一步研究,1万条TIME_WAIT连接,也就多消耗1M左右的内存,对于现在的服务器来说已经不算什么。至于CPU,也不至于因为1万多的hash item就担忧。最起码在TIME_WAIT达到几千的量级上不必过多紧张,因为TIME_WAIT所占用的内存很少很少,同时记录和寻找可用的local port所消耗的CPU也基本可以忽略。
TIME_WAIT的存在是很重要的,如果强制忽略TIME_WAIT,还是有很高的机率,造成数据粗乱,或者短暂性的连接失败。比较直接的现象是,通过NAT后的IP大量访问服务的时候容易出现静置几分钟后连接失败或者多个客户端同时访问有的访问频繁失败的情况。
还有一种情况是:在 客户端-服务器 一对一的时候,没有任何问题,但是当源IP是经过NAT后的地址或服务器在负载均缓知闭衡器后面时,源地址为同一ip假如恰巧先后两次连接源端口相同,这台服务器先后收到两个包,第一个包的timestamp被服务器保存着,第二个包又来了,一对比,发现第二个包的timestamp比第一个还老——客户端时间不一致。服务器基于PAWS,判断第二个包是重复报文,丢弃之。反映出来的情况就是在服务器上抓包,发现有SYN包,但服务器就是不回ACK包,因为SYN包已经被丢弃了。可用如下命令验证,查看输出里面的 packets rejects in established connections because of timestamp 一项的数量变化。
写了这么多,那么猛镇tw_recycle参数到底该怎么用呢?在这里,引用 老男孩博客 里给出的两个典型场景的配置方案作为参考
在这种情况下,因为负载均衡服务器对Web服务器的连接(注意,负载均衡器向Web服务器发起的主动连接),TIME_WAIT大都出现在负载均衡服务器上,所以,在负载均衡服务器上的配置:
在这种情况下,Web服务器变成TIME_WAIT的重灾区。负载均衡对Web服务器的连接,由Web服务器首先关闭连接,TIME_WAIT出现在Web服务器上;Web服务器对DB服务器的连接,由Web服务器关闭连接,TIME_WAIT也出现在它身上,此时,负载均衡服务器上的配置:
http://blog.oldboyedu.com/tcp-wait/
http://blog.chinaunix.net/uid-24517549-id-4048652.html
http://blog.chinaunix.net/uid-28337979-id-4107112.html
写在最后,内核的优化不能想当然,需要配合相应的业务场景。当然,这是一个长期积累的过程,需要不断的研究摸索,也需要众位同道多多分享多多赐教!与君共勉!
转自: https://blog.51cto.com/cangzihu/1888622