searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

TCP的TIME_WAIT快速回收与重用

2023-07-17 06:52:38
163
0

相信熟悉Linux操作系统的各位对以下三个内核参数都不会陌生,其中还可能调整过对应的值,但实际上真的“改对”了吗,让我们一起深入探讨一下

一、net.ipv4.tcp_timestamps

在说快速回收与重用之前必须先说说时间戳这个参数,因为TIME_WAIT快速回收与重用都依赖于时间戳这个内核参数。首先我们看看时间戳选项的两个作用:
  • Round Trip Time Measurement
若非通过 Timestamp Option 来计算 RTT,大部分 TCP 实现只会以 "每个 Window 采样一次" 的频率来测算 RTT。因此通过开启时间戳选项可以实现更密集的 RTT 采样,使 RTT 的测算更精确。
  • PAWS (Protected Against Wrapped Squences,防止序列号回绕)
1. 假设 TCP Window Size 为 1GB (使用 Window Scale),发送者每发送一个 Window 的数据 Timestamp 值加 100,数据的发送情况如下所示:
时间点
发送数据量
Seq Num
Timestamp
接收
1 0G:1G 0G:1G 0~100 OK
2 1G:2G 1G:2G 100~200 其中某些Segment丢包后重传
3 2G:3G 2G:3G 200~300 OK
4 3G:4G 3G:4G 300~400 OK
5 4G:5G 0G:1G 400~500 OK
6 5G:6G 1G:2G 500~600 此前丢包的Segment出现了
 
2. 在时间点 2 的时候,发生了丢包;在时间点 4 和 5 之间,序列号发生了回绕;在时间点 6,已经被认为 "丢包" 的 Segment 延迟到达了
 
3. 由于延时到达的 Segment 的 timestamp 为 1xx,小于时间点 6 的有效 timestamp (5xx),因此这个 Segment 会根据 PAWS 机制丢弃,从而不会对 TCP 造成影响
 
TCP timestamp 的工作方式:连接双方各自维护自己的时间戳,时间戳的值随时间单调递增 (规定为 1ms-1s/次,常见值为 1ms、10ms)。本端发送 timesstamp 值,对方收到后在后续的 ACK 的 timestamp echo 回应本端的值,并在 timestamp 中发送自己的时间戳。TCP 记录发送时间戳和收到回应的时间, 从而获得 RTT。如图,设备 A 发出首个请求为 a2 时刻,然后设备 B 回复 ACK 将 a2 放入时间戳回显中,设备 A 在收到这个回显后结合当前自己的时间戳就可以计算出 RTT。
 
RTT (Round Trip Time) 即往返延时,由三部分组成:链路的传播时间 (propagation delay)、末端系统的处理时间、路由器缓存中的排队和处理时间 (queuing delay)。其中,前两个部分的值对于一个 TCP 连接相对固定,路由器缓存中的排队和处理时间会随着整个网络拥塞程度的变化而变化。所以 RTT 的变化在一定程度上反应了网络的拥塞程度。
 
RTO (Retransmission TimeOut) 即重传超时时间,TCP 超时与重传中一个很最重要的部分是对一个给定连接的往返时间 (RTT) 的测量。由于网络流量的变化,这个时间会相应地发生改变,TCP 需要跟踪这些变化并动态调整超时时间 RTO。RFC 2988 中是这样描述 RTO 的:
 
"The Transmission Control Protocol (TCP) uses a retransmission timer to ensure data delivery in the absence of any feedback from the remote data receiver. The duration of this timer is referred to as RTO (retransmission timeout)."
 
早期的 RTT 的测量是采用粗粒度的定时器 (Coarse grained timer),这会有比较大的误差。现在由于 TCP Timestamp 选项的使用,能够更精确的测量 RTT,从而计算出更加准确的 RTO。

二、net.ipv4.tcp_tw_recycle

当此选项设置为1时表示开启 TCP 连接中 TIME-WAIT sockets 的快速回收,默认为 0,表示关闭。必须在客户端和服务端同时开启 timestamps 时才管用,对客户端和服务器同时起作用,开启后在 3.5*RTO 内回收,RTO 的值在 200ms ~ 120s 之间,具体时间视网络状况而定。内网状况比 tw_reuse 稍快,公网尤其移动网络大多要比 tw_reuse 慢,优点就是能够回收服务端的 TIME_WAIT 数量。RTO 的值由下面的式子计算:
constintrto = (icsk->icsk_rto <<2) - (icsk->icsk_rto >>1);
 
其中 icsk->icsk_rto 的值是超时重传的时间,这个值是根据网络情况动态计算的。rto 的值为 icsk->icsk_rto 的 3.5 倍。在网络比较好的情况下,rto 的值会小于 TCP_TIMEWAIT_LEN,从而达到加速的目的;但是如果在网络情况比较差,也就是说客户端和服务器端往返的时间比较长的情况下,rto 的值有可能会大于 TCP_TIMEWAIT_LEN,这种情况下反而适得其反,这种情况通常是由客户端引起的。所以在设置 tcp_tw_recycle 的时候要考虑到客户端的情况。
 
下面在来看看为什么 rto 的值要选择为 icsk->icsk_rto 的 3.5 倍,也就是 RTO*3.5,而不是 2 倍、4 倍呢?我们知道,在 FIN_WAIT_2 状态下接收到 FIN 包后,会给对端发送 ACK 包,完成 TCP 连接的关闭。但是最后的这个 ACK 包可能对端没有收到,在过了 RTO (超时重传时间) 时间后,对端会重新发送 FIN 包,这时需要再次给对端发送 ACK 包,所以 TIME_WAIT 状态的持续时间要保证对端可以重传两次FIN包。
 
如果重传两次的话,TIME_WAIT 的时间应该为 RTO*(0.5+0.5+0.5)=RTO*1.5,但是这里却是 RTO*3.5。这是因为在重传情况下,重传超时时间采用一种称为 "指数退避" 的方式计算。例如:当重传超时时间为 1s 的情况下发生了数据重传,我们就用重传超时时间为 2s 的定时器来重传数据,下一次用 4s,一直增加到 64s 为止 (参见tcp_retransmit_timer())。所以这里的 RTO*3.5=RTO*0.5+RTO*1+RTO*2,其中 RTO*0.5 是第一次发送 ACK 的时间到对端的超时时间 (系数就是乘以 RTO 的值),RTO*1 是对端第一次重传 FIN 包到 ACK 包到达对端的超时时间,RTO*2 是对端第二次重传 FIN 包到 ACK 包到达对端的超时时间。注意,重传超时时间的指数退避操作 (就是乘以 2) 是在重传之后执行的,所以第一次重传的超时时间和第一次发送的超时时间相同。整个过程及时间分布如下图所示 (注意:箭头虽然指向对端,只是用于描述过程,数据包并未被接收到):
 
TCP 有一种行为,当 tcp_timestamps 选项开启后,会缓存每个连接最新的时间戳,如果同时开启了 tcp_tw_recycle 选项,那么最近的 60s 内同一源 ip 主机的 socket connect 请求中的 timestamp 必须是递增的,也就是必须大于缓存的时间戳,否则视为无效,相应的数据包会被丢弃。可知,Linux 是否启用这种行为取决于 tcp_timestamps 和 tcp_tw_recycle,因为 tcp_timestamps 缺省就是开启的,所以当 tcp_tw_recycle 被开启后,实际上这种行为就被激活了。
 
例如有的公司都用 LVS 做负载均衡,如果实现的方案为 VS/NAT,那么当请求到达 LVS 后,它修改地址数据后便转发给后端服务器,但不会修改时间戳数据,对于后端服务器来说,请求的源地址就是 LVS 的地址,加上端口会复用,所以从后端服务器的角度看,原本不同客户端的请求经过 LVS 的转发,就可能会被后端服务器认为是同一个连接,加之不同客户端的时间可能不一致,便可能出现时间戳错乱的现象,进而直接导致时间戳小的数据包被丢弃。再比如如果网站用户处于一个 NAT 的网络中 (比如:一个公司只有一个或有限的几个 IP 做公网出口、一个家庭宽带一般只有一个公网 IP 出口等) ,在这种情况下,建立连接的 SYN 可能就被直接丢掉 (可能会看到 connection timeout 的错误)
 
具体的表现通常是是客户端明明发送的 SYN,但服务端就是不响应 ACK,可以通过下面命令来确认数据包不断被丢弃的现象:
 
# netstat -s | grep rejects
3077875 passive connections rejected because oftimestamp
 
因此,安全起见,通常要禁止 tcp_tw_recycle,而不是 tcp_timestamps,因为 tcp_timestamps 并不是导致问题的原因,只不过是 tcp_tw_recycle 要依赖于开启 tcp_timestamps 才能生效,而开启 tcp_timestamps 对于计算 RTT 是有用的。至于 TIME_WAIT 连接过多的问题,可以通过激活 tcp_tw_reuse 来缓解。

三、net.ipv4.tcp_tw_reuse

当此选项设置为1时表示开启重用。允许将 TIME-WAIT sockets 重新用于新的 TCP 连接,默认为 0,表示关闭。需要注意的是,tcp_tw_reuse 这个选项只有在 Client 执行 connect 操作的场景下才会生效,而且必须在客户端和服务端 timestamps 开启时才管用,开启后客户端在 1s 内回收。作为客户端,因为端口有 65535 限制的问题,TIME_WAIT 过多会直接影响处理能力,打开 tw_reuse 即可帮助客户端在 1s 内完成连接的回收 (不建议同时打开 tw_recycle,帮助不大),基本可实现单机 6w/s 的请求,需要更高的并发可以通过增加 IP 地址来实现,此外,当作为客户端需要连本机的一个服务的时候,应该首选 Unix 域套接字而不是 TCP。如果客户端的端口不够用,调用 connect() 时将返回 EADDRNOTAVAIL 错误。
 
通常认为 tcp_tw_reuse 比 tcp_tw_recycle 安全一些,这是因为一来 TIME_WAIT 创建时间必须超过一秒才可能会被复用;二来只有连接的时间戳是递增的时候才会被复用。官方文档里是这样说的:如果从协议视角看它是安全的,那么就可以使用。这是比较官方的说法,其实如果网络比较稳定,比如都是内网连接,那么就可以尝试使用 tcp_tw_reuse。
0条评论
0 / 1000
尹****进
2文章数
0粉丝数
尹****进
2 文章 | 0 粉丝
尹****进
2文章数
0粉丝数
尹****进
2 文章 | 0 粉丝
原创

TCP的TIME_WAIT快速回收与重用

2023-07-17 06:52:38
163
0

相信熟悉Linux操作系统的各位对以下三个内核参数都不会陌生,其中还可能调整过对应的值,但实际上真的“改对”了吗,让我们一起深入探讨一下

一、net.ipv4.tcp_timestamps

在说快速回收与重用之前必须先说说时间戳这个参数,因为TIME_WAIT快速回收与重用都依赖于时间戳这个内核参数。首先我们看看时间戳选项的两个作用:
  • Round Trip Time Measurement
若非通过 Timestamp Option 来计算 RTT,大部分 TCP 实现只会以 "每个 Window 采样一次" 的频率来测算 RTT。因此通过开启时间戳选项可以实现更密集的 RTT 采样,使 RTT 的测算更精确。
  • PAWS (Protected Against Wrapped Squences,防止序列号回绕)
1. 假设 TCP Window Size 为 1GB (使用 Window Scale),发送者每发送一个 Window 的数据 Timestamp 值加 100,数据的发送情况如下所示:
时间点
发送数据量
Seq Num
Timestamp
接收
1 0G:1G 0G:1G 0~100 OK
2 1G:2G 1G:2G 100~200 其中某些Segment丢包后重传
3 2G:3G 2G:3G 200~300 OK
4 3G:4G 3G:4G 300~400 OK
5 4G:5G 0G:1G 400~500 OK
6 5G:6G 1G:2G 500~600 此前丢包的Segment出现了
 
2. 在时间点 2 的时候,发生了丢包;在时间点 4 和 5 之间,序列号发生了回绕;在时间点 6,已经被认为 "丢包" 的 Segment 延迟到达了
 
3. 由于延时到达的 Segment 的 timestamp 为 1xx,小于时间点 6 的有效 timestamp (5xx),因此这个 Segment 会根据 PAWS 机制丢弃,从而不会对 TCP 造成影响
 
TCP timestamp 的工作方式:连接双方各自维护自己的时间戳,时间戳的值随时间单调递增 (规定为 1ms-1s/次,常见值为 1ms、10ms)。本端发送 timesstamp 值,对方收到后在后续的 ACK 的 timestamp echo 回应本端的值,并在 timestamp 中发送自己的时间戳。TCP 记录发送时间戳和收到回应的时间, 从而获得 RTT。如图,设备 A 发出首个请求为 a2 时刻,然后设备 B 回复 ACK 将 a2 放入时间戳回显中,设备 A 在收到这个回显后结合当前自己的时间戳就可以计算出 RTT。
 
RTT (Round Trip Time) 即往返延时,由三部分组成:链路的传播时间 (propagation delay)、末端系统的处理时间、路由器缓存中的排队和处理时间 (queuing delay)。其中,前两个部分的值对于一个 TCP 连接相对固定,路由器缓存中的排队和处理时间会随着整个网络拥塞程度的变化而变化。所以 RTT 的变化在一定程度上反应了网络的拥塞程度。
 
RTO (Retransmission TimeOut) 即重传超时时间,TCP 超时与重传中一个很最重要的部分是对一个给定连接的往返时间 (RTT) 的测量。由于网络流量的变化,这个时间会相应地发生改变,TCP 需要跟踪这些变化并动态调整超时时间 RTO。RFC 2988 中是这样描述 RTO 的:
 
"The Transmission Control Protocol (TCP) uses a retransmission timer to ensure data delivery in the absence of any feedback from the remote data receiver. The duration of this timer is referred to as RTO (retransmission timeout)."
 
早期的 RTT 的测量是采用粗粒度的定时器 (Coarse grained timer),这会有比较大的误差。现在由于 TCP Timestamp 选项的使用,能够更精确的测量 RTT,从而计算出更加准确的 RTO。

二、net.ipv4.tcp_tw_recycle

当此选项设置为1时表示开启 TCP 连接中 TIME-WAIT sockets 的快速回收,默认为 0,表示关闭。必须在客户端和服务端同时开启 timestamps 时才管用,对客户端和服务器同时起作用,开启后在 3.5*RTO 内回收,RTO 的值在 200ms ~ 120s 之间,具体时间视网络状况而定。内网状况比 tw_reuse 稍快,公网尤其移动网络大多要比 tw_reuse 慢,优点就是能够回收服务端的 TIME_WAIT 数量。RTO 的值由下面的式子计算:
constintrto = (icsk->icsk_rto <<2) - (icsk->icsk_rto >>1);
 
其中 icsk->icsk_rto 的值是超时重传的时间,这个值是根据网络情况动态计算的。rto 的值为 icsk->icsk_rto 的 3.5 倍。在网络比较好的情况下,rto 的值会小于 TCP_TIMEWAIT_LEN,从而达到加速的目的;但是如果在网络情况比较差,也就是说客户端和服务器端往返的时间比较长的情况下,rto 的值有可能会大于 TCP_TIMEWAIT_LEN,这种情况下反而适得其反,这种情况通常是由客户端引起的。所以在设置 tcp_tw_recycle 的时候要考虑到客户端的情况。
 
下面在来看看为什么 rto 的值要选择为 icsk->icsk_rto 的 3.5 倍,也就是 RTO*3.5,而不是 2 倍、4 倍呢?我们知道,在 FIN_WAIT_2 状态下接收到 FIN 包后,会给对端发送 ACK 包,完成 TCP 连接的关闭。但是最后的这个 ACK 包可能对端没有收到,在过了 RTO (超时重传时间) 时间后,对端会重新发送 FIN 包,这时需要再次给对端发送 ACK 包,所以 TIME_WAIT 状态的持续时间要保证对端可以重传两次FIN包。
 
如果重传两次的话,TIME_WAIT 的时间应该为 RTO*(0.5+0.5+0.5)=RTO*1.5,但是这里却是 RTO*3.5。这是因为在重传情况下,重传超时时间采用一种称为 "指数退避" 的方式计算。例如:当重传超时时间为 1s 的情况下发生了数据重传,我们就用重传超时时间为 2s 的定时器来重传数据,下一次用 4s,一直增加到 64s 为止 (参见tcp_retransmit_timer())。所以这里的 RTO*3.5=RTO*0.5+RTO*1+RTO*2,其中 RTO*0.5 是第一次发送 ACK 的时间到对端的超时时间 (系数就是乘以 RTO 的值),RTO*1 是对端第一次重传 FIN 包到 ACK 包到达对端的超时时间,RTO*2 是对端第二次重传 FIN 包到 ACK 包到达对端的超时时间。注意,重传超时时间的指数退避操作 (就是乘以 2) 是在重传之后执行的,所以第一次重传的超时时间和第一次发送的超时时间相同。整个过程及时间分布如下图所示 (注意:箭头虽然指向对端,只是用于描述过程,数据包并未被接收到):
 
TCP 有一种行为,当 tcp_timestamps 选项开启后,会缓存每个连接最新的时间戳,如果同时开启了 tcp_tw_recycle 选项,那么最近的 60s 内同一源 ip 主机的 socket connect 请求中的 timestamp 必须是递增的,也就是必须大于缓存的时间戳,否则视为无效,相应的数据包会被丢弃。可知,Linux 是否启用这种行为取决于 tcp_timestamps 和 tcp_tw_recycle,因为 tcp_timestamps 缺省就是开启的,所以当 tcp_tw_recycle 被开启后,实际上这种行为就被激活了。
 
例如有的公司都用 LVS 做负载均衡,如果实现的方案为 VS/NAT,那么当请求到达 LVS 后,它修改地址数据后便转发给后端服务器,但不会修改时间戳数据,对于后端服务器来说,请求的源地址就是 LVS 的地址,加上端口会复用,所以从后端服务器的角度看,原本不同客户端的请求经过 LVS 的转发,就可能会被后端服务器认为是同一个连接,加之不同客户端的时间可能不一致,便可能出现时间戳错乱的现象,进而直接导致时间戳小的数据包被丢弃。再比如如果网站用户处于一个 NAT 的网络中 (比如:一个公司只有一个或有限的几个 IP 做公网出口、一个家庭宽带一般只有一个公网 IP 出口等) ,在这种情况下,建立连接的 SYN 可能就被直接丢掉 (可能会看到 connection timeout 的错误)
 
具体的表现通常是是客户端明明发送的 SYN,但服务端就是不响应 ACK,可以通过下面命令来确认数据包不断被丢弃的现象:
 
# netstat -s | grep rejects
3077875 passive connections rejected because oftimestamp
 
因此,安全起见,通常要禁止 tcp_tw_recycle,而不是 tcp_timestamps,因为 tcp_timestamps 并不是导致问题的原因,只不过是 tcp_tw_recycle 要依赖于开启 tcp_timestamps 才能生效,而开启 tcp_timestamps 对于计算 RTT 是有用的。至于 TIME_WAIT 连接过多的问题,可以通过激活 tcp_tw_reuse 来缓解。

三、net.ipv4.tcp_tw_reuse

当此选项设置为1时表示开启重用。允许将 TIME-WAIT sockets 重新用于新的 TCP 连接,默认为 0,表示关闭。需要注意的是,tcp_tw_reuse 这个选项只有在 Client 执行 connect 操作的场景下才会生效,而且必须在客户端和服务端 timestamps 开启时才管用,开启后客户端在 1s 内回收。作为客户端,因为端口有 65535 限制的问题,TIME_WAIT 过多会直接影响处理能力,打开 tw_reuse 即可帮助客户端在 1s 内完成连接的回收 (不建议同时打开 tw_recycle,帮助不大),基本可实现单机 6w/s 的请求,需要更高的并发可以通过增加 IP 地址来实现,此外,当作为客户端需要连本机的一个服务的时候,应该首选 Unix 域套接字而不是 TCP。如果客户端的端口不够用,调用 connect() 时将返回 EADDRNOTAVAIL 错误。
 
通常认为 tcp_tw_reuse 比 tcp_tw_recycle 安全一些,这是因为一来 TIME_WAIT 创建时间必须超过一秒才可能会被复用;二来只有连接的时间戳是递增的时候才会被复用。官方文档里是这样说的:如果从协议视角看它是安全的,那么就可以使用。这是比较官方的说法,其实如果网络比较稳定,比如都是内网连接,那么就可以尝试使用 tcp_tw_reuse。
文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0