上篇笔记主要介绍了与TIME_WAIT相关的基础知识,本文则从实践出发,说明如何解决文章标题提出的问题。
1. 查看系统网络配置和当前TCP状态
在定位并处理应用程序出现的网络问题时,了解系统默认网络配置是非常必要的。以x86_64平台Linux kernelversion 2.6.9的机器为例,ipv4网络协议的默认配置可以在/proc/sys/net/ipv4/下查看,其中与TCP协议栈相关的配置项均以tcp_xxx命名,关于这些配置项的含义,请参考这里的文档,此外,还可以查看linux源码树中提供的官方文档(src/linux/Documentation/ip-sysctl.txt)。下面列出我机器上几个需重点关注的配置项及其默认值:
cat /proc/sys/net/ipv4/ip_local_port_range 32768 61000 cat /proc/sys/net/ipv4/tcp_max_syn_backlog 1024 cat /proc/sys/net/ipv4/tcp_syn_retries 5 cat /proc/sys/net/ipv4/tcp_max_tw_buckets 180000 cat /proc/sys/net/ipv4/tcp_tw_recycle 0 cat /proc/sys/net/ipv4/tcp_tw_reuse 0其中,前3项分别说明了local port的分配范围(默认的可用端口数不到3w)、incomplete connection queue的最大长度以及3次握手时SYN的最大重试次数,这3项配置的含义,有个概念即可。后3项配置的含义则需要理解,因为它们在定位、解决问题过程中要用到,下面进行重点说明。
2. 网络问题定位思路
参考前篇笔记开始处描述的线上实际问题,收到某台机器无法对外建立新连接的报警时,排查定位问题过程如下:
用netstat –at | grep “TIME_WAIT”统计发现,当时出问题的那台机器上共有10w+处于TIME_WAIT状态的TCP连接,进一步分析发现,由报警模块引起的TIME_WAIT连接有2w+。将netstat输出的统计结果重定位到文件中继续分析,一般会看到本机的port被大量占用。
由本文前面介绍的系统配置项可知,tcp_max_tw_buckets默认值为18w,而ip_local_port_range范围不到3w,大量的TIME_WAIT状态使得local port在TIME_WAIT持续期间不能被再次分配,即没有可用的local port,这将是导致新建连接失败的最大原因。
在这里提醒大家:上面的结论只是我们的初步判断,具体原因还需要根据代码的异常返回值(如socket api的返回值及errno等)和模块日志做进一步确认。无法建立新连接的原因还可能是被其它模块列入黑名单了,本人就有过这方面的教训:程序中用libcurl api请求下游模块失败,初步定位发现机器TIME_WAIT状态很多,于是没仔细分析curl输出日志就认为是TIME_WAIT引起的问题,导致浪费了很多时间,折腾了半天发现不对劲后才想起,下游模块有防攻击机制,而发起请求的机器ip被不在下游模块的访问白名单内,高峰期上游模块通过curl请求下游的次数太过频繁被列入黑名单,新建连接时被下游模块的TCP层直接以RST包断开连接,导致curl api返回”Recv failure: Connection reset by peer”的错误。惨痛的教训呀 =_=
另外,关于何时发送RST包,《Unix Network Programming Volume 1》第4.3节做了说明,作为笔记,摘出如下:
An RST is a type of TCP segment that is sent by TCP when somethingis wrong.Three conditions that generatean RST are:
1) when a SYN arrives for a port that has no listening server;
2) when TCP wants to abort an existing connection;
3) when TCP receives a segment for a connection that does not exist. (TCPv1 [pp.246–250] contains additional information.)
3. 解决方法
可以用两种思路来解决机器TIME_WAIT过多导致无法对外建立新TCP连接的问题。
3.1 修改系统配置
具体来说,需要修改本文前面介绍的tcp_max_tw_buckets、tcp_tw_recycle、tcp_tw_reuse这三个配置项。
1)将tcp_max_tw_buckets调大,从本文第一部分可知,其默认值为18w(不同内核可能有所不同,需以机器实际配置为准),根据文档,我们可以适当调大,至于上限是多少,文档没有给出说明,我也不清楚。个人认为这种方法只能对TIME_WAIT过多的问题起到缓解作用,随着访问压力的持续,该出现的问题迟早还是会出现,治标不治本。
2)开启tcp_tw_recycle选项:在shell终端输入命令”echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle”可以开启该配置。
需要明确的是:其实TIME_WAIT状态的socket是否被快速回收是由tcp_tw_recycle和tcp_timestamps两个配置项共同决定的,只不过由于tcp_timestamps默认就是开启的,故大多数文章只提到设置tcp_tw_recycle为1。更详细的说明(分析kernel源码)可参见这篇文章。
还需要特别注意的是:当client与server之间有如NAT这类网络转换设备时,开启tcp_tw_recycle选项可能会导致server端drop(直接发送RST)来自client的SYN包。具体的案例及原因分析,可以参考这里、这里或这里以及这里的分析,本文不再赘述。
3)开启tcp_tw_reuse选项:echo1 > /proc/sys/net/ipv4/tcp_tw_reuse。该选项也是与tcp_timestamps共同起作用的,另外socket reuse也是有条件的,具体说明请参见这篇文章。查了很多资料,与在用到NAT或FireWall的网络环境下开启tcp_tw_recycle后可能带来副作用相比,貌似没有发现tcp_tw_reuse引起的网络问题。
3.2 修改应用程序
具体来说,可以细分为两种方式:
1)将TCP短连接改造为长连接。通常情况下,如果发起连接的目标也是自己可控制的服务器时,它们自己的TCP通信最好采用长连接,避免大量TCP短连接每次建立/释放产生的各种开销;如果建立连接的目标是不受自己控制的机器时,能否使用长连接就需要考虑对方机器是否支持长连接方式了。
2)通过getsockopt/setsockoptapi设置socket的SO_LINGER选项,关于SO_LINGER选项的设置方法,《UNP Volume1》一书7.5节给出了详细说明,想深入理解的同学可以去查阅该教材,也可以参考这篇文章,讲的还算清楚。
4. 需要补充说明的问题
我们说TIME_WAIT过多可能引起无法对外建立新连接,其实有一个例外但比较常见的情况:S模块作为WebServer部署在服务器上,绑定本地某个端口;客户端与S间为短连接,每次交互完成后由S主动断开连接。这样,当客户端并发访问次数很高时,S模块所在的机器可能会有大量处于TIME_WAIT状态的TCP连接。但由于服务器模块绑定了端口,故在这种情况下,并不会引起“由于TIME_WAIT过多导致无法建立新连接”的问题。也就是说,本文讨论的情况,通常只会在每次由操作系统分配随机端口的程序运行的机器上出现(每次分配随机端口,导致后面无端口可用)。
【参考资料】
1. ipsysctl-tutorial之tcpvariables
2. proc_sys_net_ipv4
3. linux+v3.2.8/Documentation/networking/ip-sysctl.txt
4. 系列文章—tcp短连接TIME_WAIT问题解决方法大全1-5
5. 打开tcp_tw_recycle引起的一个问题
6. dropping of connections with tcp_tw_recycle = 1
7. tcp_tw_recycle和nat造成syn_ack问题
================= EOF ==================