记一次time_wait & close_wait的讨论总结

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: 记一次time_wait & close_wait的讨论总结

作者:牧原

image

TIME_WAIT是TCP连接关闭过程中的一个状态,具体是这么形成的:
1 主动关闭端A:发FIN,进入FIN-WAIT-1状态,并等待......
2 被动关闭端P:收到FIN后必须立即发ACK,进入CLOSE_WAIT状态,并等待......
3 主动关闭端A:收到ACK后进入FIN-WAIT-2状态,并等待......
4 被动关闭端P:发FIN,进入LAST_ACK状态,并等待......
5 主动关闭端A:收到FIN后必须立即发ACK,进入TIME_WAIT状态,等待2MSL后结束Socket
6 被动关闭端P:收到ACK后结束Socket


因此,TIME_WAIT状态是出现在主动发起连接关闭的一点,和是谁发起的连接无关,可以是client端,也可以是server端。
而从TIME_WAIT状态到CLOSED状态,有一个超时设置,这个超时设置是 2*MSL(RFC793定义了MSL为2分钟,Linux设置成了30s)


为什么需要TIME_WAIT?
主要有两个原因:
1)为了确保两端能完全关闭连接。
假设A服务器是主动关闭连接方,B服务器是被动方。如果没有TIME_WAIT状态,A服务器发出最后一个ACK就进入关闭状态,如果这个ACK对端没有收到,对端就不能完成关闭。对端没有收到ACK,会重发FIN,此时连接关闭,这个FIN也得不到ACK,而有TIME_WAIT,则会重发这个ACK,确保对端能正常关闭连接。
2)为了确保后续的连接不会收到“脏数据”
刚才提到主动端进入TIME_WAIT后,等待2MSL后CLOSE,这里的MSL是指(maximum segment lifetime,我们内核一般是30s,2MSL就是1分钟),网络上数据包最大的生命周期。这是为了使网络上由于重传出现的old duplicate segment都消失后,才能创建参数(四元组,源IP/PORT,目标IP/PORT)相同的连接,如果等待时间不够长,又创建好了一样的连接,再收到old duplicate segment,数据就错乱了。


TIME_WAIT 会导致什么问题
1) 新建连接失败
TIME_WAIT到CLOSED,需要2MSL=60s的时间。这个时间非常长。每个连接在业务结束之后,需要60s的时间才能完全释放。如果业务上采用的是短连接的方式,会导致非常多的TIME_WAIT状态的连接,会占用一些资源,主要是本地端口资源。
一台服务器的本地可用端口是有限的,也就几万个端口,由这个参数控制:
sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 61000
当服务器存在非常多的TIME_WAIT连接,将本地端口都占用了,就不能主动发起新的连接去连其他服务器了。
这里需要注意,是主动发起连接,又是主动发起关闭的一方才会遇到这个问题。
如果是server端主动关闭client端建立的连接产生了大量的TIME_WAIT连接,这是不会出现这个问题的。除非是其中涉及到的某个客户端的TIME_WAIT连接都有好几万个了。


2)TIME_WAIT条目超出限制
这个限制,是由一个内核参数控制的:
sysctl net.ipv4.tcp_max_tw_buckets
net.ipv4.tcp_max_tw_buckets = 5000
超出了这个限制会报一条INFO级别的内核日志,然后继续关闭掉连接。并没有什么特别大的影响,只是增加了刚才提到的收到脏数据的风险而已。
另外的风险就是,关闭掉TIME_WAIT连接后,刚刚发出的ACK如果对端没有收到,重发FIN包出来时,不能正确回复ACK,只是回复一个RST包,导致对端程序报错,说connection reset。
因此net.ipv4.tcp_max_tw_buckets这个参数是建议不要改小的,改小会带来风险,没有什么收益,只是表面上通过netstat看到的TIME_WAIT少了些而已,有啥用呢?
并且,建议是当遇到条目不够,增加这个值,仅仅是浪费一点点内存而已。


如何解决time_wait?
1)最佳方案是应用改造长连接,但是一般不太适用
2)修改系统回收参数
设置以下参数
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_tw_recycle = 1
设置该参数会带来什么问题?
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_tw_recycle = 1
如果这两个参数同时开启,会校验源ip过来的包携带的timestamp是否递增,如果不是递增的话,则会导致三次握手建联不成功,具体表现为抓包的时候看到syn发出,server端不响应syn ack
通俗一些来讲就是,一个局域网有多个客户端访问您,如果有客户端的时间比别的客户端时间慢,就会建联不成功
治标不治本的方式:
放大端口范围
sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 61000
放大time_wait的buckets
sysctl net.ipv4.tcp_max_tw_buckets
net.ipv4.tcp_max_tw_buckets = 180000


----------update 2020-02-22 关于tw_bucket之争------------
关于net.ipv4.tcp_max_tw_buckets到底要不要放大,目前云上ecs多数是设置了5000,我个人浅见觉得小了
image


简单来说 net.ipv4.tcp_max_tw_buckets的作用 是为了“优雅”的关闭连接
1,完整的关闭连接
2,避免有数据包重复
如果tw满了会怎样
TCP: time wait bucket table overflow
新内核
tw_bucket满了的话,会影响established状态的连接在finack的时候,直接进入closed状态
老内核
tw_bucket满了的话,会将tw_bucket里面的time_wait按照一定的规则(如LRU),将一批time_Wait直接进入closed状态 ,然后established状态发送finack后进入time_wait


tw的开销是什么?
1,特别少量的内存
2,占用本地端口
tw放大的好与坏?
1,放大的话需要更多的内存开销,但是几乎可以忽略不计
2,占用更多的本地端口,需要适当的放大本地端口范围,端口范围经过简单的测试,建议设置为tw的1.5倍
net.ipv4.ip_local_port_range
3,netstat 大量的扫描socket的时候(ss不会扫描,但是ss在slab内存特别高的时候,也有可能会引起抖动),极端情况下可能会引起性能抖动
4,tw放大,local_port_range放大,还可以配置复用以及快速回收等参数
5,使用快速回收可能会导致snat时间戳递增校验问题,不递增的话syn不响应


特殊场景的时候(本机会发起大量短链接的时候)
1, nginx结合php-fpm需要本地起端口,
2,nginx反代如(java ,容器等)
如下图所示,
tcp_tw_reuse参数需要结合net.ipv4.tcp_timestamps = 1 一起来用
即 服务器即做客户端,也做server端的时候
tcp_tw_reuse参数用来设置是否可以在新的连接中重用TIME_WAIT状态的套接字。注意,重用的是TIME_WAIT套接字占用的端口号,而不是TIME_WAIT套接字的内存等。这个参数对客户端有意义,在主动发起连接的时候会在调用的inet_hash_connect()中会检查是否可以重用TIME_WAIT状态的套接字。如果你在服务器段设置这个参数的话,则没有什么作用,因为服务器端ESTABLISHED状态的套接字和监听套接字的本地IP、端口号是相同的,没有重用的概念。但并不是说服务器端就没有TIME_WAIT状态套接字。
因此 该类场景最终建议是

net.ipv4.tcp_tw_recycle = 0 关掉快速回收
net.ipv4.tcp_tw_reuse = 1   开启tw状态的端口复用(客户端角色)
net.ipv4.tcp_timestamps = 1 复用需要timestamp校验为1 
net.ipv4.tcp_max_tw_buckets = 30000 放大bucket
net.ipv4.ip_local_port_range = 15000 65000 放大本地端口范围

内存开销测试

# ss -s
Total: 15254 (kernel 15288)
TCP:   15169 (estab 5, closed 15158, orphaned 0, synrecv 0, timewait 3/0), ports 0
Transport Total     IP        IPv6
*         15288     -         -        
RAW       0         0         0        
UDP       5         4         1        
TCP       11        11        0        
INET      16        15        1        
FRAG      0         0         0        
15000个socket消耗30多m内存

image

----------万恶的分割线-----------
关于 close_wait
image


如上所示,CLOSE_WAIT的状态是 服务器端/客户端程序收到外部过来的FIN之后,响应了ACK包,之后就进入了 CLOSE_WAIT 状态。一般来说,如果一切正常,稍后服务器端/客户端程序 需要发出 FIN 包,进而迁移到 LAST_ACK 状态,收到对端过来的ACK后,完成TCP连接关闭的整个过程。
注:不管是服务器还是客户端,只要是被动接收第一个FIN的那一方才会进入CLOSE_WAIT状态

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
11月前
|
网络协议 Cloud Native
为什么需要 TIME_WAIT 状态
为什么需要 TIME_WAIT 状态
为什么需要 TIME_WAIT 状态
sleep () 和 wait () 的区别
sleep () 和 wait () 的区别
86 0
|
监控
Sleep()和wait()方法的区别
Sleep()和wait()方法的区别
136 0
|
Java 程序员
sleep 和 wait 的区别
Java 中,线程的 "sleep" 和 "wait" 方法区别
129 0
|
监控
实践解读CLOSE_WAIT和TIME_WAIT
实践解读CLOSE_WAIT和TIME_WAIT
389 0
实践解读CLOSE_WAIT和TIME_WAIT
|
Java
sleep与wait区别
第一个区别是在对系统资源的占用上。 wait是Object类的一个函数(也就意味着所有对象都有这个函数),指线程处于进入等待状态,此时线程不占用任何资源,不增加时间限制。wait可以被notify和notifyAll函数唤醒(当然这两个同时也是Object的函数)。 而sleep则是Thread类的一个函数,指线程被调用时,占着CPU不工作。此时,系统的CPU部分资源被占用,其他线程无法进入,会增加时间限制。
141 0
|
监控
sleep 与 wait 区别
sleep 与 wait 区别
114 0
|
弹性计算 网络协议 Java
记一次time_wait & close_wait的讨论总结
TIME_WAIT是TCP连接关闭过程中的一个状态,具体是这么形成的:1 主动关闭端A:发FIN,进入FIN-WAIT-1状态,并等待......2 被动关闭端P:收到FIN后必须立即发ACK,进入CLOSE_WAIT状态,并等待.
7382 2
记一次time_wait & close_wait的讨论总结
sleep( ) 和 wait( ) 的这 5 个区别,你知道几个?
sleep(休眠) 和 wait(等待) 方法是 Java 多线程中常用的两个方法,它们有什么区别及一些该注意的地方有哪些呢?下面给大家一一分解。
334 0