linux系统参数之ip_local_port_range、tcp_max_tw_buckets、tcp_tw_reuse

本文详细介绍了Linux系统中与TIME_WAIT状态相关的参数,如ip_local_port_range、tcp_max_tw_buckets和tcp_tw_reuse。TIME_WAIT状态的端口会占用系统资源,可能导致端口耗尽和客户端CPU高。建议客户端通过调整端口范围和开启tcp_tw_reuse来优化,服务端则需谨慎使用tcp_tw_recycle。同时,分析了源码中如何处理TIME_WAIT状态,并给出了相关参数的使用场景和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一,linux TIME_WAIT 相关参数

1,/proc/sys/net/ipv4/ip_local_port_range:端口范围,不要超过1024到65535,1024以下系统使用,65535以上会提示失败
2,/proc/sys/net/ipv4/tcp_max_tw_buckets:kernel中最多存在的TIME_WAIT数量
3,net.ipv4.tcp_tw_reuse = 0    表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
4,net.ipv4.tcp_tw_recycle = 0  表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
5,net.ipv4.tcp_fin_timeout = 60  表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间

6,net.ipv4.tcp_timestamps=0 Timestamps 用在其它一些东西中﹐可以防范那些伪造的sequence 号码。可用于计算往返时延RTT。

注意:

- 不像Windows 可以修改注册表修改2MSL 的值,linux 需要修改内核宏定义重新编译,tcp_fin_timeout 不是2MSL 而是Fin-WAIT-2状态超时时间.

- tcp_tw_reuse 和 SO_REUSEADDR 是两个完全不同的东西

   SO_REUSEADDR 允许同时绑定 127.0.0.1 和 0.0.0.0 同一个端口; SO_RESUSEPORT linux 3.7才支持,用于绑定相同ip:port,像nginx 那样 fork方式也能实现

(1). tw_reuse,tw_recycle 必须在客户端和服务端 timestamps 开启时才管用(默认打开)

(2). tw_reuse 只对客户端起作用,开启后客户端在1s内回收

(3). tw_recycle 对客户端和服务器同时起作用,开启后在 3.5*RTO 内回收,RTO 200ms~ 120s 具体时间视网络状况。

  内网状况比tw_reuse 稍快,公网尤其移动网络大多要比tw_reuse 慢,优点就是能够回收服务端的TIME_WAIT数量

 

二,可能的影响

当系统中socket出现大量timewait状态时,这些TIME_WAIT是占据端口号的,而且基本要1分钟左右才能被Kernel回收,因此会占用大量系统端口号。

1,导致客户端选不到可用端口,端口号耗尽,无法跟服务端建立连接。错误:

Cannot assign requested address

2,导致客户端CPU高。因为内核在选择可以用端口时,是用遍历查找端口。

三,建议

对于客户端(为了解决TIME_WAIT问题的参数优化

1,端口号范围调大

比较无脑的方案当然是把端口号范围调大,把端口号范围调大,这样就能抗更多的TIME_WAIT;同时将tcp_max_tw_bucket调小。只要port范围 - tcp_max_tw_bucket大于一定的值,那么就始终有port端口可用,这样就可以避免再次到调大临界值得时候继续击穿临界点。

2,作为客户端因为有端口65535问题,开启tcp_tw_reuse,不建议同时打开tw_recycle,帮助不大。

tw_reuse 帮助客户端1s完成连接回收,基本可实现单机6w/s请求,需要再高就增加IP数量吧。

对于服务端(NAT负载后的服务端和客户端访问网站出现丢包现象

1. 打开tw_reuse无效

2,net.ipv4.tcp_timestamps和net.ipv4.tcp_tw_recycle一定不能同时打开。

3. 线上环境 tw_recycle 不要打开 因为一般的服务器、客户端都在NAT之后

 公网服务打开就可能造成部分连接失败,内网的话到时可以视情况打开;

   像我所在公司对外服务都放在负载后面,负载会把timestamp 都给清空,好吧,就算你打开也不起作用。

https://www.cnblogs.com/alchemystar/p/13444964.html

4. 服务器TIME_WAIT 高怎么办

   不像客户端有端口限制,处理大量TIME_WAIT Linux已经优化很好了,每个处于TIME_WAIT 状态下连接内存消耗很少,

而且也能通过tcp_max_tw_buckets =100000 解决(这个值根据TIME_WAIT数量定),现代机器一般也不缺这点内存。

 

四,源码分析

1,客户端建链(相关参数:ip_local_port_range、tcp_tw_reuse、tcp_timestamps)

(1) 耗CPU性能的循环查找可用端口(ip_local_port_range)

tcp_v4_connect--->inet_hash_connect--->__inet_hash_connect

int __inet_hash_connect(...)
{
		// 注意,这边是static变量
		static u32 hint;
		// hint有助于不从0开始搜索,而是从下一个待分配的端口号搜索
		u32 offset = hint + port_offset;
		.....
		inet_get_local_port_range(&low, &high);
		// 这边remaining就是61000 - 32768
		remaining = (high - low) + 1
		......
		for (i = 1; i <= remaining; i++) {
			port = low + (i + offset) % remaining;
			/* port是否占用check */
			....
			goto ok;
		}
		.......

        return -EADDRNOTAVAIL;
ok:
		hint += i;
		......
}

(2)开启tcp_tw_reuse,使得在大部分情况下1s之内的TIME_WAIT就可以重用(tcp_tw_reuse、tcp_timestamps)

tcp_v4_connect--->inet_hash_connect--->__inet_hash_connect--->__inet_check_established--->twsk_unique

static int __inet_check_established(......)
{
	......	
	sk_nulls_for_each(sk2, node, &head->chain) {
		if (sk2->sk_hash != hash)
			continue;

		if (likely(INET_MATCH(sk2, net, acookie,
					 saddr, daddr, ports, dif))) {
			if (sk2->sk_state == TCP_TIME_WAIT) {
				tw = inet_twsk(sk2);
				if (twsk_unique(sk, sk2, twp))
					break;
			}
			goto not_unique;
		}
	}
	......
not_unique:
	spin_unlock(lock);
	return -EADDRNOTAVAIL;
}

而其中的核心函数就是twsk_unique,它的判断逻辑如下:

int tcp_twsk_unique(......)
{
	......
	if (tcptw->tw_ts_recent_stamp &&
	    (twp == NULL || (sysctl_tcp_tw_reuse &&
			     get_seconds() - tcptw->tw_ts_recent_stamp > 1))) {
       // 对write_seq设置为snd_nxt+65536+2
       // 这样能够确保在数据传输速率<=80Mbit/s的情况下不会被回绕      
		tp->write_seq = tcptw->tw_snd_nxt + 65535 + 2
		......
		return 1;
	}
	return 0;	
}

在开启了tcp_timestamp以及tcp_tw_reuse的情况下,在Connect搜索port时只要比之前用这个port的TIME_WAIT状态的Socket记录的最近时间戳>1s,就可以重用此port,即将之前的1分钟缩短到1s。同时为了防止潜在的序列号冲突,直接将write_seq加上在65537,这样,在单Socket传输速率小于80Mbit/s的情况下,不会造成序列号重叠(冲突)。
同时这个tw_ts_recent_stamp设置的时机如下图所示:

同时这个tw_ts_recent_stamp设置的时机如下图所示:

所以如果Socket进入TIME_WAIT状态后,如果一直有对应的包发过来,那么会影响此TIME_WAIT对应的port是否可用的时间。开启了这个参数之后,由于从1min缩短到1s。

2,NAT负载后的服务端和客户端访问网站出现丢包现象(相关参数:tcp_tw_recycle、tcp_timestamps)

三次握手收到syn包:tcp_conn_request--->tcp_peer_is_proven

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 (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;
			}
		}
        ......
}

/* 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.
* 注释大意为:
* 我们在进入TIME_WAIT状态的时候将最后的时间戳记录到peer tables中,
* 然后在新的连接请求进来的时候检查这个时间戳
*/
// 在tcp_timestamps和tcp_tw_recycle开启的情况下
bool tcp_peer_is_proven(struct request_sock *req, struct dst_entry *dst,
			bool paws_check, bool timestamps)
{
    ......

		if (tm &&
            //	/** TCP_PAWS_MSL== 60 */
		    /** TCP_PAWS_WINDOW ==1 */
		    // 以下都是针对同一个对端ip
		    // tcp_ts_stamp 对端ip的连接进入time_wait状态后记录的本机时间戳	
		    // 当前时间在上一次进入time_wait记录的实际戳后的一分钟之内
		    (u32)get_seconds() - tm->tcpm_ts_stamp < TCP_PAWS_MSL &&  // 60s时间内
            // tcp_ts 最近接收的那个数据包的时间戳(对端带过来的)
		    // 对端当前请求带过来的时间戳小于上次记录的进入time_wait状态后记录的对端时间戳
		    ((s32)(tm->tcpm_ts - req->ts_recent) > TCP_PAWS_WINDOW ||
		     !timestamps))
			ret = false;
		else
			ret = true;
    ......
	return ret;
}

上述代码的核心意思即是在tcp_timestamps和tcp_tw_recycle开启的情况下,同样ip的连接,在上个连接进入time_wait状态的一分钟内,如果有新的连接进来,而且新的连接的时间戳小于上个进入time_wait状态的最后一个包的时间戳,则将这个syn丢弃,进入drop_and_release。

参考:

解Bug之路-Nginx 502 Bad Gateway

https://www.cnblogs.com/alchemystar/archive/2020/07/31/13409534.html

解Bug之路-记一次调用外网服务概率性失败问题的排查

https://www.cnblogs.com/alchemystar/p/13444964.html

tcp_tw_reuse、tcp_tw_recycle 使用场景及注意事项

https://www.cnblogs.com/lulu/p/4149312.html

一个NAT问题引起的思考

http://perthcharles.github.io/2015/08/27/timestamp-NAT/


<think>嗯,用户问到了TCP的TIME_WAIT状态过多的原因。首先,我需要回忆一下TCP连接关闭的过程。记得TIME_WAIT出现在主动关闭连接的一方,也就是发送最后一个ACK的那一端。这个状态会持续2MSL(Maximum Segment Lifetime)的时间,通常是2分钟,不过实际可能更短,比如在Linux中默认是60秒。 那为什么会有大量的TIME_WAIT呢?可能的原因有几个。首先,高并发短连接的情况,比如HTTP服务器通常处理大量短连接,每次处理完请求后主动关闭连接,这时候就会积累很多TIME_WAIT。例如,像一些Web服务使用完毕后立即关闭连接,导致主动关闭的一方(服务器)产生大量TIME_WAIT。 其次,是不是客户端也可能出现这个问题?比如,当客户端频繁地连接服务器然后关闭,比如爬虫或者某些客户端应用,这时候如果客户端主动关闭连接,就会产生TIME_WAIT。不过通常服务器端更容易出现,因为处理更多连接。 另外,配置问题也可能导致TIME_WAIT过多。例如,系统参数设置不当,像net.ipv4.tcp_max_tw_buckets限制了TIME_WAIT的数量,如果设置太低,虽然不会增加数量,但可能导致问题。或者有没有开启tcp_tw_reusetcp_tw_recycle之类的选项,这些可能会影响TIME_WAIT的处理方式,不过需要注意这些参数在不同系统版本中的效果和潜在问题。 还有,应用设计的问题,比如没有使用连接池或者长连接,导致频繁创建和关闭连接,这样自然会产生大量TIME_WAIT。这种情况下,优化应用设计,复用连接可能有效。 另外,需要考虑的是TIME_WAIT本身是TCP协议的正常机制,它的存在是为了确保最后一个ACK被正确接收,防止旧连接的报文干扰新连接。所以少量的TIME_WAIT是正常的,但过多的话会影响新连接的建立,比如耗尽端口号或占用太多内存。 用户可能是在实际运维中遇到了这个问题,想知道原因和解决办法。需要分情况讨论,是服务器端还是客户端的问题,是否应用设计有优化空间,或者系统参数是否需要调整。同时,需要提醒用户谨慎修改系统参数,因为不当的设置可能引起其他问题,比如NAT环境下的tcp_tw_recycle可能导致连接失败。 总结下来,主要原因可能包括高频率短连接、主动关闭的一方设计、系统配置限制等。解决方案可能涉及优化连接管理、调整内核参数、使用SO_REUSEADDR等选项。不过需要根据具体情况来分析,不能一概而论。</think>TCP 的 **TIME_WAIT** 状态过多通常是由高频短连接场景触发的,以下是常见原因和背后的逻辑: --- ### 1. **短连接高频使用** - **现象**:应用频繁建立 TCP 连接并快速关闭(例如 HTTP 短连接)。 - **原因**:每个连接关闭时,**主动关闭方**(如服务器)会进入 `TIME_WAIT` 状态,持续 `2*MSL`(默认 60 秒,MSL 是报文最大生存时间)。 - **典型场景**: - Web 服务器(如 Nginx)处理大量短请求时主动关闭连接。 - 客户端频繁调用后关闭连接(如爬虫、微服务间调用)。 --- ### 2. **主动关闭方设计不合理** - **问题**:若服务端被设计为主动关闭连接(例如某些 HTTP 服务器配置),会积累 `TIME_WAIT`。 - **优化思路**: - 调整关闭策略(如让客户端主动关闭)。 - 使用 HTTP `Keep-Alive` 保持长连接。 --- ### 3. **系统资源限制** - **端口耗尽**:`TIME_WAIT` 会占用端口资源(每个连接由五元组唯一标识)。 - 客户端高频连接时,端口可能被耗尽(尤其 IPv4 端口范围有限)。 - **内存消耗**:每个 `TIME_WAIT` 连接占用内核内存(通常约 4KB)。 --- ### 4. **内核参数配置** - **相关参数**: - `net.ipv4.tcp_max_tw_buckets`:限制 `TIME_WAIT` 数量上限。 - `net.ipv4.tcp_tw_reuse`:允许复用 `TIME_WAIT` 连接(需协议支持)。 - `net.ipv4.tcp_tw_recycle`:快速回收 `TIME_WAIT`(已废弃,NAT 环境下有风险)。 --- ### 解决方案 1. **优化应用设计** - 使用连接池或长连接(如 HTTP/1.1 Keep-Alive、HTTP/2 多路复用)。 - 避免服务端主动关闭连接(根据场景调整)。 2. **调整内核参数**(需谨慎评估) ```bash # 允许复用 TIME_WAIT 连接(需内核 3.2+,且协议支持时间戳) echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse # 增大端口范围 echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range # 调整 TIME_WAIT 最大数量 echo 5000 > /proc/sys/net/ipv4/tcp_max_tw_buckets ``` 3. **设置 `SO_REUSEADDR`** - 允许绑定处于 `TIME_WAIT` 状态的端口(需应用层支持)。 --- ### 为什么需要 TIME_WAIT? - **确保可靠关闭**:防止最后一个 `ACK` 丢失,导致对端重传 `FIN`。 - **避免旧连接干扰**:确保网络中属于旧连接的报文过期,避免与新连接冲突。 --- ### 总结 `TIME_WAIT` 是 TCP 协议的正常机制,但需在高频短连接场景中合理优化。通过长连接、参数调优或调整关闭策略,可有效减少其影响。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值