我们增加一个-v参数来取反,这样就获取了本地地址中不是80和443端口的TIME-WAIT状态数量,那么这个数量就是Nginx作为客户端进行内连后端服务器所产生的。
很明显对内侧的TIME_WAIT明显比对外侧要高,这就是因为Nginx反向代理到后端使用随机端口来主动连接后端服务的固定端口,在短连接的情况下(通常是短连接),Nginx作为主动发起连接的一方会主动断开,所以在业务繁忙的Nginx代理服务器上会看到大量的对内侧的TIME_WAIT。
基于这种情况可以采用net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle优化方式,因为高位随机端口具备复用的可能。当然至于旧IP分组影响新连接的情况在前面已经说过了其依靠时间戳来做丢弃。具体机制请看后面,现在你只需要知道是依靠时间戳来规避这个问题。
另外net.ipv4.ip_local_port_range参数可以设置一个更大的范围,比如net.ipv4.ip_local_port_range = 2048 65000这就意味着你的可用随机端口多了,端口少我们更多关注与端口复用,端口多其实是不是复用的意义就不是那么大,当然这还得取决于并发量,当然这里也不要死磕,如果你的并发量是100万,你怎么可能指望1台Nginx来抗住流量呢,显然需要构建Nginx集群。
再有net.ipv4.tcp_max_tw_buckets这个参数当主机对外的时候需要调整,如果完全是内网提供服务那么这个值无需关心,它根据系统内存动态生成的,当然你可以修改。在对外的时候主要是简单防止DoS攻击。
net.ipv4.tcp_fin_timeout这个值保持默认60秒或者调整成30秒都可以,主要避免对端上层应用死掉了无法进行正常发送fin,进而长期处在CLOSE_WAIT阶段,这样你自己这段的服务器就被拖住了。
总结对于TIME_WAIT不要死磕,存在即合理,明明是一个很正常的且保证可靠通信的机制你非要抑制它的产生或者让它快速消失。任何的调整都是双刃剑,就像2台Nginx组成的集群去抗100万并发的流量,你非要去优化TIME_WAIT,你为什么不想想会不会是你Nginx集群规模太小了呢?
作为不会主动进行外连的服务器来说对于TIME_WAIT除了消耗一点内存和CPU资源之外你不必过多关心这个状态。
针对Nginx做反代的场景使用reuse优化一下,另外调大一下高位端口范围,fin_timeout可以设置小一点,至于net.ipv4.tcp_max_tw_buckets保存默认就可以,另外对于net.ipv4.tcp_tw_recycle则放弃使用吧,比较从Linux 4.10以后这个参数也被弃用了参见kernel.org。
2MSL和resue或者recycle会不会有冲突这个问题在TCP上有一个术语缩写是PAWS,全名为PROTECT AGAINST WRAPPED SEQUENCE NUMBERS,也就是防止TCP的Seq序列号反转的机制。
我们上面介绍了2MSL的作用以及减少TIME_WAIT常用措施,但是你想过没有重用TIME_WAIT状态的端口以及快速回收会不会引发收到该相同4元组之前的重复IP报文呢?很显然是有可能的,那么这里就谈谈如何规避。通常2种办法:
TCP序列号,也就是Seq位置的数字
时间戳,所以这也是为什么在开启resue和recycle的时候要求开启时间戳功能。
TCP头中的序列号位有长度限制(32位),其最大值为2的32次方个,这就意味着它是循环使用的,也很容易在短时间内完成一个循环(序列号反转),在1Gbps的网络里17秒就可以完成一个循环,所以单纯的通过检查序列号不能完全实现阻挡老IP分组的数据,因为高速网络中这个循环完成的太快,而一个IP分组的最长TTL是2MSL,通常是1分钟,所以最主要还是靠时间戳。
前面我们也几次提到时间戳,比如在reuse和recycle的时候提到会对比时间戳,如果收到的报文时间戳小于最近连接的时间戳就会被丢弃,那么我们如何获取这个时间戳呢?我们先看看它长什么样子:
TSval:发送端时间戳
TSecr:对端回显时间戳
我们看第三行,如下图:
这一行是客户端回复ACK给服务器完成三次握手的最后一个阶段,TSval就是客户端的时间戳这个和第一行一样这是因为速度快还没有走完一个时间周期,这一行的TSecr是434971890,这个就是第二行服务器回复SYN时候给客户端发来的服务器的时间戳,这个就叫做回显时间戳。