Write by lyc at 2020-12-1
参考博文:服务端 TCP 连接的 TIME_WAIT 问题分析与解决

1.大量的 TIME_WAIT 状态 TCP 连接,对业务上有什么影响?

Nginx 作为反向代理时,大量的短链接,可能导致 Nginx 上的 TCP 连接处于 TIME_WAIT 状态:

  1. 每一个 TIME_WAIT 状态,都会占用一个「本地端口」。TCP 本地端口数量,上限为 65535(6.5w),这是因为 TCP 头部使用 16 bit,存储「端口号」,因此约束上限为 65535
  2. 当大量的连接处于 TIME_WAIT 时,新建立 TCP 连接会出错,address already in use : connect 异常
  3. TCP 连接中,「主动发起关闭连接」的一端,会进入 TIME_WAIT 状态
  4. TIME_WAIT 状态,默认会持续 2 MSL(报文的最大生存时间),一般是 2x2 mins
  5. TIME_WAIT 状态下,TCP 连接占用的端口,无法被再次使用

2.TCP 连接的状态

统计 TCP 连接的状态

1
2
3
4
5
6
7
$ netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
LAST_ACK 1
CLOSE_WAIT 2
ESTABLISHED 281
FIN_WAIT1 7
FIN_WAIT2 63
TIME_WAIT 8403

连接状态说明

1
2
3
4
5
6
7
8
9
10
11
CLOSED      # 无连接是活动的或正在进行
LISTEN # 服务器在等待进入呼叫
SYN_RECV # 一个连接请求已经到达,等待确认
SYN_SENT # 应用已经开始,打开一个连接
ESTABLISHED # 正常数据传输状态,当前并发连接数
FIN_WAIT1 # 应用说它已经完成
FIN_WAIT2 # 另一边已同意释放
ITMED_WAIT # 等待所有分组死掉
CLOSING # 两边同时尝试关闭
TIME_WAIT # 另一边已初始化一个释放
LAST_ACK # 等待所有分组死掉

3.问题分析

大量的 TIME_WAIT 状态 TCP 连接存在,其本质原因是什么?

  1. 大量的短连接存在
  2. 特别是 HTTP 请求中,如果 connection 头部取值被设置为 close 时,基本都由「服务端」发起主动关闭连接
    • 所以 TIME_WAIT 过多一般多存在于服务器端,比如 Nginx反向代理服务器,我们可以将Nginx服务器与Web服务器拆分来减少 TIME_WAIT
  3. TCP 四次挥手关闭连接机制中,为了保证 ACK 重发和丢弃延迟数据,设置 TIME_WAIT 为 2 倍的 MSL(报文最大存活时间)

TIME_WAIT 状态:

  1. TCP 连接中,主动关闭连接的一方出现的状态;(收到 FIN 命令,进入 TIME_WAIT 状态,并返回 ACK 命令)
  2. 保持 2 个 MSL 时间,即,4 分钟;(MSL 为 2 分钟)

4.解决办法

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

对于客户端

  1. 作为客户端因为有端口65535问题,TIME_OUT 过多直接影响处理能力,打开 tw_reuse 即可解决,不建议同时打开 tw_recycle,帮助不大;

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

  3. 如果内网压测场景,且客户端不需要接收连接,同时 tw_recycle 会有一点点好处;

  4. 业务上也可以设计由服务端主动关闭连接。

    对于服务端(我们主要关注服务器端)

  5. 打开 tw_reuse 无效

  6. 线上环境 tw_recycle 不建议打开

    • 服务器处于NAT 负载后,或者客户端处于NAT后(基本公司家庭网络基本都走NAT);
    • 公网服务打开就可能造成部分连接失败,内网的话到时可以视情况打开;
    • 像我所在公司对外服务都放在负载后面,负载会把 timestamp 都给清空,就算你打开也不起作用。
  7. 服务器 TIME_WAIT 高怎么办?

    • 不像客户端有端口限制,处理大量 TIME_WAIT Linux已经优化很好了,每个处于 TIME_WAIT 状态下连接内存消耗很少
    • 而且也能通过 tcp_max_tw_buckets 配置最大上限,现代机器一般也不缺这点内存。
  8. 业务上也可以设计由客户端主动关闭连接,首先发起关闭的一方会进入TIME-WAIT状态,另一方可以快速回收连接。

4.1 向外连接的端口范围(可以配置)

/proc/sys/net/ipv4/ip_local_port_range

  • 表示用于向外连接的端口范围。
  • 缺省情况下很小:32768到61000,改为10240到65000
1
2
3
4
5
6
7
# 检查向外连接的端口范围
$ cat /proc/sys/net/ipv4/ip_local_port_range
32768 60999

# 修改向外连接的端口范围
$ vim /etc/sysctl.conf
net.ipv4.ip_local_port_range=10240 65000

4.2 TIME_WAIT 快速回收(线上环境不建议使用)

网络优化之net.ipv4.tcp_tw_recycle参数
【经验总结】tcp_tw_recycle参数引发的故障

  • /proc/sys/net/ipv4/tcp_timestamps 查看内核的时间戳支持,默认为1。如果是0,则修改为1。
  • /proc/sys/net/ipv4/tcp_tw_recycle 开启TCP连接中 TIME_WAIT sockets 的快速回收,默认为0,表示关闭。==在NAT(Network Address Translation)网络下,会导致大量的TCP连接建立错误。如果没有技术大神的指点的话,千万不要去改动他。==
  • /proc/sys/net/ipv4/tcp_tw_reuse 开启重用。允许将 TIME_WAIT sockets 重新用于新的TCP连接,默认为0,表示关闭。从协议设计上来看,对于TIME-WAIT状态的sockets重用到新的TCP连接上来说,是安全的。(用于客户端时的配置)

tcp_tw_recycle 这个开关,需要 net.ipv4.tcp_timestamps(默认开启的)这个开关开启才有效果。

1
2
3
4
5
6
7
8
9
10
# 查看 TIME_WAIT 快速回收
$ cat /proc/sys/net/ipv4/tcp_timestamps
$ cat /proc/sys/net/ipv4/tcp_tw_recycle
$ cat /proc/sys/net/ipv4/tcp_tw_reuse

# 配置 TIME_WAIT 快速回收
$ vim /etc/sysctl.conf
net.ipv4.tcp_timestamps=1
net.ipv4.tcp_tw_recycle=1 # 慎用
net.ipv4.tcp_tw_reuse=1

4.3 控制 TIME_WAIT 套接字的最大数量(可以配置)

/proc/sys/net/ipv4/tcp_max_tw_buckets

  • 表示系统同时保持 TIME_WAIT 套接字的最大数量
  • 如果超过这个数字,TIME_WAIT 套接字将立刻被清除并打印警告信息。
  • 系统日志中overflow 错误一直再刷屏 TCP: time wait bucket table overflow
  • 默认为180000,改为 10000,16384,32786 都可以,根据当前服务器的内存值进行调整。
1
2
3
4
5
6
7
# 查看
$ cat /proc/sys/net/ipv4/tcp_max_tw_buckets
32768

# 配置
$ vim /etc/sysctl.conf
net.ipv4.tcp_max_tw_buckets=16384

4.4 其他 TIME_WAIT 相关参数说明(保持默认,不要调整)

1
2
3
/proc/sys/net/ipv4/tcp_syncookies               # 1表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
/proc/sys/net/ipv4/tcp_fin_timeout # 减少TCP KeepAlive连接侦测的时间,使系统可以处理更多的连接。 默认值60
/proc/sys/net/ipv4/tcp_keepalive_time # 增加TCP SYN队列长度,使系统可以处理更多的并发连接。 默认值7200