TCP TIME_WAIT 状态
当需要断开一个 TCP 连接时,需要四次挥手,每一次需要发送一报文,同时 TCP 连接会进行状态的变化。在挥手时的最后一个 ACK 发出后,TCP 连接会进入 TIME_WAIT 状态。
TIME_WAIT 状态通常需要等待 2MSL 的时间再变成 CLOSED 状态。而因为这个状态,经常会产生一些奇怪的问题。本文整理了 TIME_WAIT 状态的作用和如何应对大量 TIME_WAIT 的建议。
TIME_WAIT 的作用
TIME_WAIT 主要有两个作用:
- 防止延迟的数据段被其他使用相同源地址、源端口、目的地址以及目的端口的 TCP 连接收到
- 保证远程 TCP 连接被正确关闭
旧报文干扰新连接
因为数据段的网络传输时间不确定,所以可能会收到上一次 TCP 连接中未被收到的数据段。
在经过 2MSL
时间,就可以使本连接持续的时间内所产生的所有报文都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文。
保证远程 TCP 连接被正确关闭
1. 让 TCP 连接等待被动关闭连接的一方收到 FIN 对应的 ACK 消息。
因为客户端发出的 ACK 可能还没有被服务端接收,服务端可能还处于 LAST_ACK 状态。服务端因为没有收到 ACK 消息,所以仍然认为当前连接是合法的。
新连接的建立时会回复 RST 消息终止连接,客户端重新发送 SYN 消息请求握手时会收到服务端的 RST 消息,连接建立的过程就会被终止。
2. 等待足够长的时间以确定远程的 TCP 连接接收到了其发出的终止连接消息 FIN 对应的 ACK
2MSL = ACK 到达服务器 + 服务器发送 FIN 重传包,一来一回。
- 服务端正常收到了 ACK 消息并关闭当前 TCP 连接,
- 服务端没有收到 ACK 消息,重新发送 FIN 关闭连接并等待新的 ACK 消息
大量 TIME_WAIT 如何处理
加机器
TCP 的 TIME_WAIT 状态有着非常重要的作用,它是保证 TCP 协议可靠性不可缺失的设计。
如果能通过加机器解决的话就尽量加机器。如果不能解决的话,我们就需要理解其背后的设计原理并尽可能避免修改默认的配置。
调整短连接为长连接
TIME_WAIT 是在关闭连接时出现,通过使用长连接减少连接关闭,进而减少 TIME_WAIT 状态的出现。
调整内核参数
net.ipv4.tcp_max_tw_buckets
:表示系统同时保持 TIME_WAIT 套接字的最大数量。超过此数量时,系统会立即清理出多余的 TIME_WAIT 连接。意味着有些连接并没有成功等待 2MSL,就会造成通讯异常。一般不建议调整。net.ipv4.tcp_tw_recycle
:开启 TCP 连接中 TIME-WAIT sockets 的快速回收,默认为 0,表示关闭。- 已经在 Linux 4.12 中移除
- 如果 NAT 或 LVS 做负载均衡出现的连接失败率高
- 从后端服务器的角度看,原本不同客户端的请求经过 LVS 的转发,就可能会被认为是同一个连接。
- 具体的表现通常是是客户端明明发送的 SYN,但服务端就是不响应 ACK。
net.ipv4.tcp_fin_timeout
:修改系统默认的 TIMEOUT 时间net.ipv4.tcp_tw_reuse
:(推荐)开启重用。允许将 TIME-WAIT sockets 重新用于新的 TCP 连接,默认为 0,表示关闭。- 开启重用机制需要依赖 tcp_timestamps 的功能
- 开启重用相比开启快速回收要更安全一点
net.ipv4.ip_local_port_range
:(推荐)表示用于向外连接的端口范围。调整可用端口范围,增加可同时存在的 TCP 连接数上限。
调整连接参数(推荐)
使用 SO_LINGER
选项并设置暂存时间 l_linger
为 0。当关闭 TCP 连接时,内核就会直接丢弃缓冲区中的全部数据并向服务端发送 RST
消息直接终止当前的连接。
引用
TCP TIME_WAIT 状态