# 生产环境 Nginx后端服务大量TIME-WAIT的解决步骤
[参考资料]() [https://mp.weixin.qq.com/s/degs3eNktJuk-BnJnfJwJQ](https://mp.weixin.qq.com/s/degs3eNktJuk-BnJnfJwJQ)
>[danger] **生产环境 Nginx后端服务大量TIME-WAIT的解决步骤**
>[danger] **TIME\_WAIT的4种查询方式**
>[success] **1、netstat -n | awk '/^tcp/ {++S\[$NF\]} END {for(a in S) print a, S\[a\]}'**

>[success] **2、ss -s**

>[success] **3、netstat -nat |awk '{print $6}'|sort|uniq -c|sort -rn**

>[success] **4、 统计TIME\_WAIT 连接的本地地址**
> **netstat -an | grep TIME\_WAIT | awk '{print $4}' | sort | uniq -c | sort -n -k1**

尝试抓取 tcp 包
~~~
tcpdump tcp -i any -nn port 8080 | grep "172.11.11.11"
~~~
##
## 系统参数优化
处理方法:需要修改内核参数开启重启:
~~~
net.ipv4.tcp_tw_reuse = 1,开启快速回收
net.ipv4.tcp_tw_recycle = 1 (在NAT网络中不建议开启,要设置为0),并且开启net.ipv4.tcp_timestamps = 1以上两个参数才生产。
~~~
具体操作方法如下:
~~~
echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf;
echo "net.ipv4.tcp_tw_recycle = 1" >> /etc/sysctl.conf;
echo "net.ipv4.tcp_timestamps = 1" >> /etc/sysctl.conf;
~~~
sysctl -p;
##
>[info] ## nginx 配置优化
当使用nginx作为反向代理时,为了支持长连接,需要做到两点:
1. 从client到nginx的连接是长连接
2. 从nginx到server的连接是长连接
保持和client的长连接:
~~~
http {
keepalive_timeout 120s 120s;
keepalive_requests 1000;
}
~~~
##
>[info] ## keepalive\_timeout设置
#语法
keepalive\_timeout timeout \[header\_timeout\];
第一个参数:设置keep-alive客户端(浏览器)连接在服务器端(nginx端)保持开启的超时值(默认75s);值为0会禁用keep-alive客户端连接;
第二个参数:可选、在响应的header域中设置一个值“Keep-Alive: timeout=time”;通常可以不用设置;
这个keepalive\_timeout针对的是浏览器和nginx建立的一个tcp通道,没有数据传输时最长等待该时候后就关闭.
nginx和upstream中的keepalive\_timeout则受到tomcat连接器的控制,tomcat中也有一个类似的keepalive\_timeout参数
>[info] keepalive\_requests理解设置
keepalive\_requests指令用于设置一个keep-alive连接上可以服务的请求的最大数量,当最大请求数量达到时,连接被关闭。默认是100。
这个参数的真实含义,是指一个keep alive建立之后,nginx就会为这个连接设置一个计数器,记录这个keep alive的长连接上已经接收并处理的客户端请求的数量。如果达到这个参数设置的最大值时,则nginx会强行关闭这个长连接,逼迫客户端不得不重新建立新的长连接。
### 保持和server的长连接
为了让nginx和后端server(nginx称为upstream)之间保持长连接,典型设置如下:(默认nginx访问后端都是用的短连接(HTTP1.0),一个请求来了,Nginx 新开一个端口和后端建立连接,后端执行完毕后主动关闭该链接)
~~~
location / {
proxy_http_version 1.1;
proxy_set_header Connection keep-alive;
proxy_pass ;
}
~~~
HTTP协议中对长连接的支持是从1.1版本之后才有的,因此最好通过proxy\_http\_version指令设置为”1.1”;
从client过来的http header,因为即使是client和nginx之间是短连接,nginx和upstream之间也是可以开启长连接的。
>[info] keepalive理解设置
此处keepalive的含义不是开启、关闭长连接的开关;也不是用来设置超时的timeout;更不是设置长连接池最大连接数。官方解释:
1.The connections parameter sets the maximum number of idle keepalive connections to upstream servers connections(设置到upstream服务器的空闲keepalive连接的最大数量)
2.When this number is exceeded, the least recently used connections are closed. (当这个数量被突破时,最近使用最少的连接将被关闭)
[3.It](http://3.It) should be particularly noted that the keepalive directive does not limit the total number of connections to upstream servers that an nginx worker process can open.(特别提醒:keepalive指令不会限制一个nginx worker进程到upstream服务器连接的总数量)
> ### 总结:
keepalive 这个参数一定要小心设置,尤其对于QPS比较高的场景,推荐先做一下估算,根据QPS和平均响应时间大体能计算出需要的长连接的数量。比如前面10000 QPS和100毫秒响应时间就可以推算出需要的长连接数量大概是1000. 然后将keepalive设置为这个长连接数量的10%到30%。比较懒的同学,可以直接设置为keepalive=1000之类的,一般都OK的了。
综上,出现大量TIME\_WAIT的情况
>[warning] 1)导致 nginx端出现大量TIME\_WAIT的情况有两种:
keepalive\_requests设置比较小,高并发下超过此值后nginx会强制关闭和客户端保持的keepalive长连接;(主动关闭连接后导致nginx出现TIME\_WAIT)
keepalive设置的比较小(空闲数太小),导致高并发下nginx会频繁出现连接数震荡(超过该值会关闭连接),不停的关闭、开启和后端server保持的keepalive长连接;
>[warning] 2)导致后端server端出现大量TIME\_WAIT的情况:
nginx没有打开和后端的长连接,即:没有设置proxy\_http\_version 1.1;和proxy\_set\_header Connection “”;从而导致后端server每次关闭连接,高并发下就会出现server端出现大量TIME\_WAIT
*****
## 扩展阅读:
**TIME\_WAIT是什么?**


在构建TCP客户端服务器系统时,很容易犯简单的错误,这些错误会严重限制可伸缩性。其中一个错误是没有考虑到状态。在这篇博文中,我将解释为什么存在,它可能导致的问题,如何解决它,以及什么时候不应该。`TIME_WAIT``TIME_WAIT`
\`\`
\`\`
`TIME_WAIT`是 TCP 状态转换图中经常被误解的状态。这是某些套接字可以进入并保持相对较长时间的状态,如果您有足够的套接字,那么您创建新套接字连接的能力可能会受到影响,这可能会影响客户端服务器系统的可伸缩性。关于插座如何以及为什么首先进入,经常存在一些误解,不应该有,这并不神奇。从下面的TCP状态转换图中可以看出,是TCP客户端通常最终处于的最终状态。`TIME_WAIT``TIME_WAIT``TIME_WAIT`

尽管状态转换图显示为客户端的最终状态,但它不一定是最终进入的客户端。事实上,这是启动“活动关闭”的对等方最终进入的最终状态,这可以是客户端或服务器。那么,发布“主动平仓”是什么意思?`TIME_WAIT``TIME_WAIT`
如果 TCP 对等方是第一个调用连接的对等方,则会启动“活动关闭”。在许多协议和客户端/服务器设计中,这就是客户端。在HTTP和FTP服务器中,这通常是服务器。导致对等体最终 inis 的实际事件顺序如下。`Close()``TIME_WAIT`

现在我们知道套接字是如何结束的,了解为什么这种状态存在以及为什么它可能是一个潜在的问题很有用。`TIME_WAIT`
\`\`
`TIME_WAIT`通常也称为 2MSL 等待状态。这是因为转换到的套接字会停留在持续时间内是*最大段生存期的 2 倍*。MSL 是构成 TCP 协议一部分的数据报的任何段在被丢弃之前在网络上保持有效的最长时间。此时间限制最终受用于传输 TCP 段的 IP 数据报中的 TTL 字段的限制。不同的实现为 MSL 选择不同的值,常见值为 30 秒、1 分钟或 2 分钟。RFC 793 将 MSL 指定为 2 分钟,Windows 系统默认使用此值,但可以使用**TcpTimedWaitDelay**注册表设置进行调整。`TIME_WAIT`
可能影响系统可伸缩性的原因是,TCP 连接中一个完全关闭的套接字将保持该状态约 4 分钟。如果许多连接被快速打开和关闭,则套接字的输入可能会开始在系统上累积;您可以使用**NetStat** 查看套接字。一次可以建立有限数量的套接字连接,限制此数量的因素之一是可用本地端口的数量。如果套接字过多,您会发现很难建立新的出站连接,因为缺少可用于新连接的本地端口。但为什么存在呢?`TIME_WAIT``TIME_WAIT``TIME_WAIT``TIME_WAIT``TIME_WAIT``TIME_WAIT`
状态有两个原因。第一种是防止一个连接的延迟段被误解为后续连接的一部分。连接处于 2MSL 等待状态时到达的任何分段都将被丢弃。`TIME_WAIT`

在上图中,我们有两个从端点 1 到终点 2 的连接。每个端点的地址和端口在每个连接中都是相同的。第一个连接终止,活动关闭由端点 2 启动。如果端点 2 的保留时间不够长,无法确保上一个连接中的所有段都已失效,则延迟的段(具有适当的序列号)可能会被误认为是第二个连接的一部分......`TIME_WAIT`
请注意,延迟的段不太可能导致这样的问题。首先,每个端点的地址和端口需要相同;这通常不太可能,因为客户端的端口通常是由操作系统从临时端口范围中为您选择的,因此在连接之间会发生变化。其次,延迟段的序列号需要在新连接中有效,这也不太可能。但是,如果这两件事都发生,那么将防止新连接的数据被损坏。`TIME_WAIT`
状态的第二个原因是可靠地实现TCP的全双工连接终止。如果终点 2 的终点被丢弃,则终点 1 将重新发送决赛。如果连接已转换到卡通端点 2,则唯一可能的响应是发送重新传输的 anas 将是意外的。这将导致端点 1 收到错误,即使所有数据都已正确传输。`TIME_WAIT``ACK``FIN``CLOSED``RST``FIN`
不幸的是,某些操作系统的实现方式似乎有点幼稚。只有与不需要的套接字完全匹配的连接才能被阻止以提供保护。这意味着由客户端地址、客户端端口、服务器地址和服务器端口标识的连接。但是,某些操作系统施加了更严格的限制,并阻止在端口号包含在连接中的连接中时重复使用本地端口号。如果最终有足够多的套接字,则无法建立新的出站连接,因为没有本地端口可以分配给新连接。`TIME_WAIT``TIME_WAIT``TIME_WAIT``TIME_WAIT``TIME_WAIT`
Windows 不这样做,只会阻止建立与*连接完全匹配*的出站连接。`TIME_WAIT`
入站连接受其影响较小。虽然服务器主动关闭的连接完全像客户端连接一样进入,但服务器正在侦听的本地端口不会阻止成为新入站连接的一部分。在 Windows 上,服务器正在侦听的已知端口可以构成后续接受连接的一部分,如果从远程地址和端口建立新连接,而远程地址和端口当前构成此本地地址和端口的连接的一部分,则只要新序列号大于当前连接的最终序列号,就允许该连接。但是,服务器上的累积可能会影响性能和资源使用,因为不需要的连接最终会超时,这样做需要一些工作,并且在状态结束之前,连接仍在占用服务器上的(少量)资源。`TIME_WAIT``TIME_WAIT``TIME_WAIT``TIME_WAIT``TIME_WAIT``TIME_WAIT``TIME_WAIT`
鉴于由于本地端口号耗尽而影响出站连接建立,并且这些连接通常使用操作系统从临时端口范围自动分配的本地端口,因此要改善这种情况,您可以做的第一件事是确保您使用的是大小合适的临时端口范围。在 Windows 上,您可以通过调整注册表设置来执行此操作;详情请看这里。请注意,默认情况下,许多 Windows 系统的临时端口范围约为 4000,这对于许多客户端服务器系统来说可能太低。`TIME_WAIT``MaxUserPort`
虽然可以减少套接字花费的时间长度,但这通常实际上并没有帮助。鉴于这只是建立并主动关闭许多连接的问题,调整 2MSL 等待期通常只会导致在给定时间内可以建立和关闭更多连接的情况,因此您必须不断向下调整 2MSL,直到它太低以至于您可能开始出现问题由于延迟段似乎是以后连接的一部分;仅当您连接到同一远程地址和端口并且非常快速地使用所有本地端口范围时,或者当您连接到同一远程地址和端口并将本地端口绑定到固定值时,才会发生这种情况。`TIME_WAIT``TIME_WAIT`
更改 2MSL 延迟通常是计算机范围的配置更改。您可以尝试使用套接字选项在套接字级别变通。这允许在已存在具有相同地址和端口的现有套接字时创建套接字。新套接字实质上劫持了旧套接字。您可以使用允许在具有相同端口的套接字已存在时创建套接字,但这也可能导致拒绝服务攻击或数据盗窃等问题。在Windows平台上,另一个套接字选项可以帮助防止一些缺点,请参阅此处,但在我看来,最好避免这些变通方法的尝试,而是设计您的系统,以免出现问题。`TIME_WAIT``SO_REUSEADDR``SO_REUSEADDR``TIME_WAIT``SO_EXCLUSIVEADDRUSE``SO_REUSEADDR``TIME_WAIT``TIME_WAIT`
上面的 TCP 状态转换图都显示了有序的连接终止。还有另一种方法可以终止TCP连接,那就是中止连接并发送一个而不是一个。这通常是通过将套接字选项设置为 0 来实现的。这会导致丢弃挂起的数据,并使用 an 中止连接,而不是传输挂起的数据,并使用 a 干净地关闭连接。重要的是要意识到,当连接中止时,任何可能在对等方之间流动的数据都会被丢弃,并立即交付;通常为表示“连接已被对等方重置”这一事实的错误。远程对等方知道连接已中止,并且两个对等方都不会进入。`RST``FIN``SO_LINGER``RST``FIN``RST``TIME_WAIT`
当然,已中止使用的连接的新化身可能会成为延迟段问题的受害者,但无论如何,这成为问题所需的条件极不可能,有关更多详细信息,请参阅上文。为了防止已中止的连接导致延迟段问题,两个对等方都必须转换为连接关闭可能是由中介(例如路由器)引起的。但是,这不会发生,并且连接的两端都只是关闭。`RST``TIME_WAIT``TIME_WAIT`
您可以采取几件事来避免给您带来麻烦。其中一些假设您能够更改客户端和服务器之间使用的协议,但对于自定义服务器设计,您通常会这样做。`TIME_WAIT`
对于从不建立自己的出站连接的服务器,除了维护连接的资源和性能影响之外,您不必过度担心。`TIME_WAIT`
对于确实建立出站连接并接受入站连接的服务器,黄金法则是始终确保如果需要发生,它最终会出现在另一个对等体而不是服务器上。最好的方法是*永远不要*从服务器启动活动关闭,无论出于何种原因。如果对等方超时,请中止连接,而不是关闭它。如果您的对等方发送无效数据,中止连接等。这个想法是,如果您的服务器从未启动活动关闭,它永远不会累积套接字,因此永远不会受到它们引起的可扩展性问题的困扰。虽然很容易看出在发生错误情况时如何中止连接,但正常连接终止呢?理想情况下,您应该在协议中设计一种让服务器告诉客户端它应该断开连接的方法,而不是简单地让服务器发起主动关闭。因此,如果服务器需要终止连接,服务器会发送应用程序级别的“我们完成了”消息,客户端会将其作为关闭连接的理由。如果客户端未能在合理的时间内关闭连接,则服务器将中止连接。`TIME_WAIT``RST``TIME_WAIT`
在客户端上,事情稍微复杂一些,毕竟,必须有人发起活动关闭才能干净地终止TCP连接,如果是客户端,那么这就是最终的结果。但是,在客户端上结束有几个优点。首先,如果由于某种原因,由于套接字的积累,客户端最终会遇到连接问题,因为它只是一个客户端。其他客户端不会受到影响。其次,快速打开和关闭与同一服务器的TCP连接效率低下,因此除了尝试保持更长的时间而不是更短的时间之外,它更有意义。不要设计一种协议,即客户端每分钟连接到服务器,并通过打开新连接来实现。相反,使用持久连接设计,仅在连接失败时重新连接,如果中间路由器拒绝在没有数据流的情况下保持连接打开,那么您可以实现应用程序级 ping,使用 TCP 保持活动状态或只是接受路由器正在重置您的连接;好消息是你没有积累套接字。如果您在连接上所做的工作自然是短暂的,请考虑某种形式的“连接池”设计,从而保持连接打开并重用。最后,如果绝对必须快速打开和关闭从客户端到同一服务器的连接,那么也许可以设计一个可以使用的应用程序级关闭序列,然后进行中止关闭。您的客户端可以发送“我已完成”消息,然后您的服务器可以发送“再见”消息,然后客户端可以中止连接。`TIME_WAIT``TIME_WAIT``TIME_WAIT``TIME_WAIT``TIME_WAIT`
\`\`
`TIME_WAIT`存在是有原因的,通过缩短 2MSL 周期或允许地址重用来解决这个问题并不总是一个好主意。如果你能够在设计协议时考虑到避免,那么你通常可以完全避免这个问题。`SO_REUSEADDR``TIME_WAIT`
- 基础
- 文档帮助
- 计算机基础知识
- Centos文件系统
- Linux基础知识入门
- Linux命令帮助文档
- 常见命令
- 其他
- vim编辑器
- 编辑命令
- 末行模式
- 常见命令
- 配置文件
- 用户useradd
- 用户login.defs
- 环境变量设置
- 常用命令
- 帮助命令
- 管道符
- 关机和重启
- 目录操作
- 目录结构
- ls
- cd
- pwd
- mkdir
- rmdir
- cp
- rm
- mv
- 文件操作
- touch
- ln 重点
- stat 重点
- file
- cat
- tac
- nl
- more
- less
- head
- tail
- 文件处理工具
- wc
- cut
- sort
- uniq
- 文件属性(权限)
- chmod
- chown
- chgrp
- umask
- sudo权限
- chattr权限
- chattr
- lsattr
- 文件特殊权限
- SetUID
- SetGID
- StickyBIT粘着位
- ACL权限
- 搜索命令
- 命令搜索whereis
- grep
- 拓展
- 元字符
- egrep
- locate
- find
- type
- 压缩和解压
- zip和unzip
- gzip和gunzip
- bzip2和bunzip2
- tar
- 网络管理
- netstat网络状态
- 消息邮件
- 软件包安装
- 源码包
- 二进制包
- rpm手工安装
- yum在线安装
- 系统管理
- 常用命令
- shell内建命令
- pstree
- bash 特性
- history 历史命令
- 补全、快捷键
- 命令行展开
- 命令执行结果
- alias命令别名
- 通配符
- 重定向及管道
- 计算机运算
- bash 中的变量的种类
- bash 的配置文件
- bash中的算术运算
- bash条件测试
- 用户和组管理
- 用户管理
- useradd
- usermod
- userdel
- passwd
- pwck
- chage
- chsh
- chfn
- id
- finger
- su
- 用户组管理
- groupadd
- groupmod
- groupdel
- gpasswd
- groups
- 用户查看
- whoami
- w
- who
- last
- lastlog
- lastb
- 网络管理
- 相关命令
- 防火墙
- 文件拷贝
- 软件工具
- 软件包管理
- Gcc软件安装
- 其他
- redis
- 相关资料
- swoole
- IDE工具
- vscode + Xdebug 断点调试
- vscode 远程链接
- git
- 不想输入账户密码
- window电脑问题
- 浏览器https访问不了
- php
- 常用函数
- rabbitMQ
- mysql
- 触发器
- 常用更新语句
- 忘记密码处理方法
- 查询
- 数据迁移
- 加索引不加锁
- 查询优化
- 基础知识
- 进程线程协程
- tp6
- 注册自定义命令空间
- Ajax异常错误
- nginx
- 域名变成ip问题
- 概念知识点
- QPS,TPS,RT概念
- Nginx服务大量TIME-WAIT
