tcp_tw_reuse、tcp_tw_recycle
ddatsh
tcp 使用场景,一般区分为“长连接”和“短连接”
如何解决tcp短连接的TIME_WAIT问题
短连接最大缺点:占用资源(如:本地端口、socket句柄)
正常的TCP client关闭后,进入TIME_WAIT,持续的时间一般在1~4分钟
每秒建1000个短连接(Web请求访问memcached)
假TIME_WAIT时间是1分钟,1分钟内需要建立6W个短连接,这些短连接1分钟内都处于TIME_WAIT状态,都不会释放
Linux默认本地端口范围:net.ipv4.ip_local_port_range = 32768 61000
不到3W,由于没有本地端口就不能建立了
解决:
-
改长连接,通过proxy之类实现长连接
-
ipv4.ip_local_port_range,增大,只治标
-
client
设置socket SO_LINGER
打开tcp_tw_recycle和tcp_timestamps
打开tcp_tw_reuse和tcp_timestamps
设置tcp_max_tw_buckets为一个很小的值
SO_LINGER 数据结构
struct linger {
int l_onoff; /* 0 = off, nozero = on */
int l_linger; /* linger time */
};
- l_onoff=0,l_linger被忽略,内核缺省情况,close调用立即返回给调用者,如果可能将会传输任何未发送的数据
- l_onoff 非0,l_linger=0,socket关闭时TCP夭折连接,丢弃发送缓冲区中的任何数据并发送一个RST,而不是通常的四分组终止序列,避免了TIME_WAIT状态
- l_onoff 非0,l_linger 非0,当socket关闭时内核将拖延一段时间(由l_linger决定)
如缓冲区残留数据,进程睡眠,直到
a. 所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0) 或 b. 延迟时间到
应用检查close返回值非常重要,如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失 close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果socket设为非阻塞的,将不等待close完成
第 2 种 只有在丢弃数据的时候才发送RST”,如果没有丢弃数据,则走正常的关闭流程
if (data_was_unread) {
/* Unread data was tossed, zap the connection. */
NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
tcp_set_state(sk, TCP_CLOSE);
tcp_send_active_reset(sk, sk->sk_allocation);
}
第三种情况其实就是第一种和第二种的折中处理,且当socket为非阻塞的场景下是没有作用的
对于应对短连接导致的大量TIME_WAIT连接问题,第二种处理是最优的选择,libmemcached就是采用这种方式
linux TIME_WAIT :
-
net.ipv4.tcp_tw_reuse = 1
开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
-
net.ipv4.tcp_tw_recycle = 1
开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
-
net.ipv4.tcp_fin_timeout = 60
如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间(可改为30,一般来说FIN-WAIT-2的连接也极少)
注意:
-
不像Windows 可以修改注册表修改2MSL 的值,linux 是没有办法修改MSL的,tcp_fin_timeout 不是2MSL 而是Fin-WAIT-2状态
-
tcp_tw_reuse 和SO_REUSEADDR 是两个完全不同的东西
SO_REUSEADDR 允许同时绑定 127.0.0.1 和 0.0.0.0 同一个端口
SO_RESUSEPORT linux 3.7才支持,用于绑定相同ip:port,像nginx 那样 fork方式也能实现
-
tw_reuse,tw_recycle 必须在客户端和服务端timestamps(默认打开)开启时才管用
-
tw_reuse 只对客户端起作用,开启后客户端在1s内回收
-
tw_recycle 对客户端和服务器同时起作用,开启后在 3.5*RTO 内回收,RTO 200ms~ 120s 具体时间视网络状况。
内网状况比tw_reuse 稍快,公网尤其移动网络大多要比tw_reuse 慢,优点就是能够回收服务端的TIME_WAIT数量
对于客户端
-
作为客户端因为有端口65535问题,TIME_OUT过多直接影响处理能力,打开tw_reuse 即可解决,不建议同时打开tw_recycle,帮助不大。
-
tw_reuse 帮助客户端1s完成连接回收,基本可实现单机6w/s请求,需要再高就增加IP数量吧。
-
如果内网压测场景,且客户端不需要接收连接,同时tw_recycle 会有一点点好处。
-
业务上也可以设计由服务端主动关闭连接
对于服务端
-
打开tw_reuse无效
-
线上环境 tw_recycle 不要打开
服务器处于NAT 负载后,或者客户端处于NAT后(这是一定的事情,基本公司家庭网络都走NAT);
公网服务打开就可能造成部分连接失败,内网的话到时可以视情况打开;
一般公司对外服务都放在LB后面,LB会把timestamp 都给清空,就算你打开也不起作用
-
服务器TIME_WAIT 高怎么办
不像客户端有端口限制,处理大量TIME_WAIT Linux已经优化很好了,每个处于TIME_WAIT 状态下连接内存消耗很少,
而且也能通过tcp_max_tw_buckets = 262144 配置最大上限,现代机器一般也不缺这点内存。
一台每秒峰值1w请求的http 短连接服务,长期处于tw_buckets 溢出状态,很稳定
slabtop ss -s
唯一不爽的就是:
系统日志中overflow 错误一直再刷屏,也许该buckets 调大一下了
TCP: time wait bucket table overflow
- 业务上也可以设计由客户端主动关闭连接
原理分析
- MSL 由来
发起连接关闭方回复最后一个fin 的ack,为避免对方ack 收不到、重发的或还在中间路由上的fin 把新连接给干掉了,等个2MSL,4min。
也就是连接有谁关闭的那一方有time_wait问题,被关那方无此问题。
-
reuse、recycle
通过timestamp的递增性来区分是否新连接,新连接的timestamp更大,那么小的timestamp的fin 就不会fin掉新连接。
-
reuse
通过timestamp 递增性,客户端、服务器能够处理outofbind fin包
-
recycle
对于服务端,同一个src ip,可能会是NAT后很多机器,这些机器timestamp递增性无可保证,服务器会拒绝非递增请求连接。
细节之处还得好好阅读tcp 协议栈源码了