tcp_tw_reuse、tcp_tw_recycle

http://www.cnblogs.com/lulu/p/4149312.html
http://www.cnxct.com/coping-with-the-tcp-time_wait-state-on-busy-linux-servers-in-chinese-and-dont-enable-tcp_tw_recycle/?utm_source=tuicool&utm_medium=referral

net.ipv4.tcp_tw_reuse = 0 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭 net.ipv4.tcp_tw_recycle = 0 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭 net.ipv4.tcp_fin_timeout = 60 表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间(可改为30,一般来说FIN-WAIT-2的连接也极少

查看机器的log,发现某个后端服务异常,代码使用了短连接请求后端服务,并在失败时自动重试,创建socket时发生错误。查看机器的网络状态,发现有大量的TIME_WAIT状态。

统计机器的TIME_WAIT状态数量有几个命令,最简单的是 cat /proc/net/sockstat

sockets: used 2861
TCP: inuse 603 orphan 0 tw 19 alloc 795 mem 339
UDP: inuse 985 mem 557

如果要查看更详细的状态统计,可以使用netstat 或者ss 加 awk 来处理

netstat -ant |awk '{if(NR>1)++s[$NF]} END {for(k in s) print k,s[k]}'

ss -ant |awk '{if(NR>1)++s[$1]} END {for(k in s) print k,s[k]}'

推荐使用ss命令,当socket数量很大的时候,ss会快很多。

因此,原因很清楚了,是短连接在后端服务异常时大量产生的TIME_WAIT状态导致创建文件描述符失败,不能处理请求。

这种情况通常的处理建议是打开tcp_tw_recycle 或者tcp_tw_reuse 选项,那么是否有效,还会不会有什么坑?

TIME_WAIT是在连接断开时产生,先看下连接断开的过程:

上面就是常说的连接断开四次挥手的过程,TIME_WAIT出现在主动断开连接方,那它存在的意义是什么呢?

stevens在unix网络编程里边讲到有两点:

1、保证TCP连接关闭的可靠性。如果最终发送的ACK丢失,被动关闭的一端会重传最终的FIN包,如果执行主动关闭的一端没有维护这个连接的状态信息,会发送RST包响应,导致连接不正常关闭。

2、允许老的重复分组在网络中消逝。假设在一个连接关闭后,发起建立连接的一端(客户端)立即重用原来的端口、IP地址和服务端建立新的连接。老的连接上的分组可能在新的连接建立后到达服务端,TCP必须防止来自某个连接的老的重复分组在连接终止后再现,从而被误解为同一个连接的化身。要实现这种功能,TCP不能给处于TIME_WAIT状态的连接启动新的连接。

TIME_WAIT的时长通常定义成2*MSL,MSL表示报文在网络上存在的最长时间,如果超过这个时间,报文将被丢弃。linux下TIME_WAIT被定义在tcp.h中,时间是60s,除非重新编译内核,否则不能修改。

/* how long to wait to destroy TIME-WAIT state, about 60 seconds*/

#define TCP_TIMEWAIT_LEN (60*HZ) 

如果每秒有1000个请求,在60秒内产生的TIME_WAIT就有60000个,要控制或者减少TIME_WAIT的数量,协议栈提供了tcp_tw_recycle、tcp_tw_reuse、tcp_max_tw_buckets这几个选项,下面逐一分析。

tcp_tw_recycle

linux协议栈实现的时候提供了一种快速回收TIME_WAIT状态的机制,不用等待2MSL的时间,只要等待一个重传的时间即可回收,在idc内部,这个时间极短,可能不到1ms。但是新建立的连接可能存在风险:

1、如果之前的FIN延迟到达,新连接会被reset

2、如果之前发出的包延时后到达对端,会造成干扰

tcp协议栈设计的时候是如何处理这些风险呢,代码如下:

int tcp_conn_request(struct request_sock_ops *rsk_ops,
                     const struct tcp_request_sock_ops *af_ops,
                     struct sock *sk, struct sk_buff *skb)
{
	...
	if (!want_cookie && !isn) {
		/* VJ's idea. We save last timestamp seen
		 * from the destination in peer table, when entering
		 * state TIME-WAIT, and check against it before
		 * accepting new connection request.
		 *
		 * If "isn" is not zero, this request hit alive
		 * timewait bucket, so that all the necessary checks
		 * are made in the function processing timewait state.
		 */
		if (tcp_death_row.sysctl_tw_recycle) {
			bool strict;

			dst = af_ops->route_req(sk, &fl, req, &strict);

			if (dst && strict &&
				!tcp_peer_is_proven(req, dst, true,tmp_opt.saw_tstamp)) {
					NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
					goto drop_and_release;
			}
		}
	}	
	...
}

在tcp_tw_recycle模式下,判断是无效连接的条件是:

1、来自对端的tcp syn请求携带时间戳

2、本机在MSL时间内接收过来自同一台ip机器的tcp数据

3、新连接的时间戳小于上次tcp数据的时间戳

以上条件满足时,连接请求会被拒绝,使用netstat -s |grep timestamp 有如下记录

……packets rejects in established connections because of timestamp

因此,在启用了tcp_tw_recycle的情况下,TIME_WAIT时间内(60s),同一源ip主机syn请求中的timestamp必须是递增的,连接才能被接受。

这个看起来很完美,同一个主机的timestamp的一定是递增的,但是NAT环境就悲剧了,NAT下,多个主机映射到同一个或几个对外IP,NAT设备只修改源地址和端口,timestamp不做修改,不能保证来自NAT机器多个主机间连接请求的timestamp是递增的,时间戳小的请求都会被拒绝。

tcp_tw_reuse

TIME_WAIT的重用只满足一定的条件下,处于TIME_WAIT状态的socket连接可以被新请求的syn使用。条件如下:

1、新请求的sequence要大于TIME_WAIT连接的最后的sequence

2、如果启用了tcp的timestamp选项,syn请求的时间戳要大于TIME_WAIT连接最后接收数据的时间戳

这个选项没有太大的意义,满足这个条件的情形并不多,并不能减少TIME_WAIT的数量。

tcp_max_tw_buckets

这个选项其实没有什么可说的,就是设置系统允许的最大TIME_WAIT数量,如果超过这个量,就不再出现TIME_WAIT,直接close

struct inet_timewait_sock *inet_twsk_alloc(const struct sock *sk,
                                           struct inet_timewait_death_row *dr,
                                           const int state)
{
	struct inet_timewait_sock *tw;

	if (atomic_read(&dr->tw_count) >= dr->sysctl_max_tw_buckets)
			return NULL;
	...
}

 这种方式对TIME_WAIT数量控制简单粗暴,但是效果也比较明显。但是问题和tcp_tw_recycle类似,新连接也可能被对端重传的FIN reset。

总结:控制TIME_WAIT的选项都存在一些问题,最好慎用。最好的方式是维持正常的TIME_WAIT状态,通过连接池的方式复用连接,减少TIME_WAIT出现的数量。如果要使用tcp_tw_recycle,一定要确保没有NAT设备接入,如果是只有client场景的机器,可以使用tcp_tw_reuse或增大net.ipv4.ip_local_port_range范围来发起更多的连接。

posted @ 2016-01-14 13:29  jimshi  阅读(3119)  评论(0编辑  收藏  举报