[草稿]解决nginx大量time_wait连接

今天我司某系统报 Nginx 502 Bad Gateway 错误,登陆服务器看到大量的 TIME_WAIT 状态连接,如下效果:

[root@localhost ~]# netstat -an|awk '/^tcp/{++state[$NF]}END{for(a in state)print a,"\t",state[a]}'
LISTEN           17         #(服务器在等待进入呼叫)
LAST_ACK         5          #(正在等待处理的请求数)
CLOSE_WAIT       13         # 被动关闭
ESTABLISHED      1579       # 正在通信(正常数据传输状态)
FIN_WAIT         504
TIME_WAIT        131057     # 主动关闭(处理完毕,等待超时结束的请求数,有异常)

[TOC]

知识储备

为何出现大量 TIME_WAIT 连接

由上图可知:

  • TIME_WAIT 是主动断开连接的一方出现的,客户端,服务器都有可能出现;
  • 客户端服务端主动断开连接时,发出最后一个ACK后就会处于 TIME_WAIT状态

结论TIME_WAIT 是必然会出现的状态,是正常现象,且会定时回收

注释:TIME_WAIT 状态持续 2 MSL(Maximum Segment Lifetime,最大报文段的生命期)时间,这是一个IP数据包能在互联网上生存的最长时间,超过这个寿命的报文都会被丢弃,RFC 793建议是2分钟,但在不同的unix系统可能为30秒、1分、2分,比如Centos7则为60秒。在centos系统中,MSL可以通过修改参数 tcp_fin_timeout 来调整。

[root@localhost ~]# cat /proc/sys/net/ipv4/tcp_fin_timeout
60

知识点1:TIME_WAIT 和 CLOSE_WAIT 是两个完全不同的状态

  • TIME_WAIT 是主动关闭链接时形成的,主要是防止最后一个ACK丢失。由于TIME_WAIT等待2MSL的时间会非常长,因此server端应尽量减少主动关闭连接
  • CLOSE_WAIT 是被动关闭连接是形成的,当服务器端收到客户端发送的FIN,发送ACK后进入CLOSE_WAIT状态。若服务器端忙于处理读、写操作,而未将已收到FIN的连接进行close(),就不能由CLOSE_WAIT迁移到LAST_ACK,则系统中会存在很多CLOSE_WAIT状态的连接。

知识点2:为什么要有TIME_WAIT状态?

  • 防止上一次连接中的包,迷路后重新出现,影响新连接(经过2个MSL,上一次连接中所有的重复包都会消失)
  • 为了可靠地关闭TCP连接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。

知识点3:为什么 TIME_WAIT 状态需要保持 2MSL 这么长的时间?

  • 如果 TIME_WAIT 状态保持时间不足够长(比如小于2MSL),第一个连接就正常终止了。第二个拥有相同相关五元组的连接出现,而第一个连接的重复报文到达,干扰了第二个连接。TCP实现必须防止某个连接的重复报文在连接终止后出现,所以让TIME_WAIT状态保持时间足够长(2MSL),连接相应方向上的TCP报文要么完全响应完毕,要么被丢弃。建立第二个连接的时候,不会混淆。

知识点4:短时间内大量TIME_WAIT出现的根本原因:高并发且持续的短连接

1)导致 nginx 端出现大量TIME_WAIT的情况有两种:

  • keepalive_requests 设置比较小,高并发下超过此值后nginx会强制关闭和客户端保持的keepalive长连接;(主动关闭连接后导致nginx出现TIME_WAIT)
  • keepalive 设置的比较小(空闲数太小),导致高并发下nginx会频繁出现连接数震荡(超过该值会关闭连接),不停的关闭、开启和后端server保持的keepalive长连接;

2)导致后端server端出现大量TIME_WAIT的情况:

  • nginx没有打开和后端的长连接,即没有启用 proxy_http_version 1.1; 协议,导致1.0协议的http请求中Connection默认为close。因为服务器处理完http请求后会主动断开连接,然后这个连接就处于TIME_WAIT状态,持续长时间的高并发就会造成大量 TIME_WAIT。

3)服务器被攻击了,攻击方采用了大量的短连接

大量 TIME_WAIT 的危害

  • TCP协议规定,对于已经建立的连接,网络双方要进行四次握手才能成功断开连接,如果缺少了其中某个步骤,将会使连接处于假死状态,连接本身占用的资源不会被释放,但大量僵死的连接会浪费许多服务器资源。

  • 大量CLOSE_WAIT的连接堆积存在很大的隐患问题,如果CLOSE_WAIT状态连接的一直保持着,那么意味着对应数据的通道就一直被占用,典型的”占着茅坑不拉屎“,因为linux分配给每一个用户的文件句柄是有限的,一旦达到句柄数上限,新的连接请求就无法被处理,请求就会报大量的Too Many Open Files异常,从而导致服务异常。

服务器短时间内大量的TIME_WAIT出现,会引发以下问题

  • 由于处于TIME_WAIT状态,连接并未关闭,占据了大量的CPU,内存,文件描述符等,造成新的连接无法建立,客户端表现就是连接失败
  • 如果服务器上同时有nginx,且nginx由于反向代理,那么还会占用很多端口(S端处于TIME_WAIT,该连接的另一方即C端需独占一个端口,C端是由nginx代理建立的),要知道端口是有限的,最多65535,一旦端口占用完,无论服务器配置如何高,新连接都无法建立了,客户端表现仍然是连接失败

如何缓解 TIME_WAIT 连接

调整代码

  • 代码层修改,把短连接改为长连接

    注:由于TIME_WAIT出现的根本原因是高并发且持续的短连接,所以如果能把短连接改成长连接,就能彻底解决问题。比如http请求中的connection设置为keep-alive

  • 客户端程序中设置socket的 SO_LINGER 选项(用来控制调用close函数关闭连接后的行为),linger的定义如下:
struct linger {
    int l_onoff;        /* 0 = off, nozero = on */
    int l_linger;       /* linger time */

优化linux内核

  • TIME_WAIT 过多,常见的解决办法就是 快速回收链接复用
  • 修改 /etc/sysctl.conf 配置如下参数,然后 /sbin/sysctl -p /etc/sysctl.conf 使配置立即生效
net.ipv4.tcp_syncookies = 1                     # 开启SYN Cookies,当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认0
net.ipv4.tcp_tw_reuse = 1                       # 开启重用,允许将TIME-WAIT sockets重新用于新的TCP连接,默认0
net.ipv4.tcp_tw_recycle = 1                     # 启用timewait快速回收,NAT环境建议设0禁用,linux4.12(centos7.9默认内核3.10.0)之后已废弃,默认0
net.ipv4.tcp_timestamps = 1                     # 只有该参数启用,tcp_tw_reuse和tcp_tw_recycle开启才能生效,默认1
net.ipv4.tcp_fin_timeout = 1                    # 当服务器主动关闭连接时,socket保持在FIN-WAIT-2状态的最大时间,默认60秒
net.ipv4.tcp_keepalive_time = 300               # 当keepalive启用时,TCP发送keepalive消息的频度,默认2小时
net.ipv4.tcp_max_tw_buckets = 5000              # 内核持有的状态为TIME_WAIT的最大数量,超出的TIME_WAIT连接会被立即销毁并打印警告,默认180000
net.ipv4.tcp_max_syn_backlog = 8192             # 调整SYN队列的长度,以容纳更多等待连接的网络连接数,默认1024
net.ipv4.ip_local_port_range = 1024 65535       # 允许系统打开的端口范围,默认32768到60999
net.core.somaxconn = 8192                       # web应用中listen函数的backlog,默认128
net.core.netdev_max_backlog = 8192              # 网卡接收数据包的速率比内核处理的速率快时,允许送到队列的数据包的最大数目

需要注意如下几点

  • 关于 net.ipv4.tcp_tw_recycle,NAT环境建议设0禁用,且linux4.12(centos7.9默认内核3.10.0)之后已废弃

优化nginx配置

当使用nginx作为反向代理时,为了支持长连接,需要做到两点:

  • 保持client到nginx的长连接
http {
    keepalive_timeout 60;           # TCP长连接存活时间,默认75秒
    keepalive_requests 1000;        # 一个keep-alive连接上可以服务的请求的最大数量,超出的连接将被关闭,默认100
}
#
#
#
  • upstream 保持nginx到server的长连接

    Nginx从 1.1.4 开始,实现了对后端机器的长连接支持功能。在Upstream中这样配置可以开启长连接的功能


http {
    upstream myservers {
        ip_hash;
        server 192.168.0.21:8001 max_fails=5 fail_timeout=300s weight=1;
        server 192.168.0.22:8001 max_fails=5 fail_timeout=300s weight=5;
        server 192.168.0.23:8001 max_fails=5 fail_timeout=300s weight=5;
    }

    server {
        listen 80;
        location / {
            proxy_http_version 1.1;                     # http 1.1协议才开始支持长连接
            proxy_set_header Connection keep-alive;     # http 1.0中Connection默认close,但在http1.1中默认keep-alive
            proxy_pass http://myservers;
        }
    }
}
Copyright © www.sqlfans.cn 2023 All Right Reserved更新时间: 2023-06-25 17:13:16

results matching ""

    No results matching ""