tcp time_wait连接数过多导致服务不可用
Jan 3, 2016
1 minute read

问题出现:

在元旦前夕,自己维护的一个服务突然在高峰时期收到大量报警,赶紧登上服务器看一下:

最开始的反应是memcache tcp read time out ,因为之前也出现过类似的警告所以开始尝试切换memcache,但是运维反馈已经切换了好几台还是不起作用。

又看到有mysql 连接不上报错,怀疑机房内网有问题,然后有开始切换机房加机器,当时问题还是没有得到解决,这下真晕了。。。。 完全排查不出什么问题(因为是高峰时期,tcp连接数自然很高,没有在意是这个问题,自己预估不够)

后来反应过来看到有好几个接口请求数增加,因为发版本的原因,是新版本才会请求这些接口,所以 我暂时屏蔽了这几个口接口问题得到遏制(幸好是用户感知不到的接口)。

排查:

这个问题围绕我好几天,我屏蔽的那几个接口,反复看了好几遍,而且又将其中的sql查询分析,并没有慢请求。。。 这下又彻底懵了,没有头绪。

说来也巧,高峰时期我用netstat -an 命令查看了一些,看到大量tcp time_wait 的连接,赶紧Google了下,tcp time_wait状态是在主动关闭连接的一方保持的一个状态(一般指客户端),

总共的连接数在近3w(ss -s可查),其中有2w多都是这种连接,而且都是mysql 连接(这里边服务器程序就相当于客户端去连接mysql服务器),到这里心里大致有底了,把数据库连接池加上就好了,Golang提供了两个函数可进行配置:

SetMaxOpenConns用于设置最大打开的连接数,默认值为0表示不限制。
SetMaxIdleConns用于设置闲置的连接数。

为什么会有TIME_WAIT状态?

源于tcp链接关闭中的四次挥手,是主动关闭链接的一方产生的状态: TCP状态转化图三次握手/四次挥手:

TCP状态转化图

四次挥手的过程如下:

  • 主动关闭连接的一方,调用close();协议层发送FIN包

  • 被动关闭的一方收到FIN包后,协议层回复ACK;然后被动关闭的一方,进入CLOSE_WAIT状态,主动关闭的一方等待对方关闭,则进入FIN_WAIT_2状态;此时,主动关闭的一方 等待 被动关闭一方的应用程序,调用close操作

  • 被动关闭的一方在完成所有数据发送后,调用close()操作;此时,协议层发送FIN包给主动关闭的一方,等待对方的ACK,被动关闭的一方进入LAST_ACK状态;

  • 主动关闭的一方收到FIN包,协议层回复ACK;此时,主动关闭连接的一方,进入TIME_WAIT状态;而被动关闭的一方,进入CLOSED状态

  • 等待2MSL时间,主动关闭的一方,结束TIME_WAIT,进入CLOSED状态

所以time_wait属于tcp正常的一个状态,是为了解决网络的丢包和网络不稳定锁存在的一个状态。

因为当前服务器并发相对较大,所以存在了大量的链接为关闭,如果只是几百的话,也不会影响服务器性能。

使用连接池保存长链接,可使得链接复用,不会出现大量的这种状态。

反思:

问题终于找到解决,但是回过头来想了一下,其实一开始就可以定位到的,只是很多问题集中在一起出现反而让自己不知所措,这个时候如果是敏感的人的话应该马上能够从tcp状态上看出问题,之后线上遇到问题,重要的是先解决(不管通过什么方式),之后再一步步排查。


Back to posts