彻底理解并解决服务器出现大量TIME_WAIT - 第二篇

为了能彻底讲清楚TIME_WAIT的原理及解决办法,本系列一共有4篇
彻底理解并解决服务器出现大量TIME_WAIT - 第一篇_YZF_Kevin的博客-CSDN博客

彻底理解并解决服务器出现大量TIME_WAIT - 第二篇_YZF_Kevin的博客-CSDN博客

彻底理解并解决服务器出现大量TIME_WAIT - 第三篇_YZF_Kevin的博客-CSDN博客

彻底理解并解决服务器出现大量TIME_WAIT - 第四篇_YZF_Kevin的博客-CSDN博客

第一篇博客中我们讲了 TIME_WAIT 出现的原理,引发的问题,解决办法等,如下

解决办法

1. 代码层修改,把短连接改为长连接,但代价较大
2. 修改 ip_local_port_range,增大可用端口范围,比如1024 ~ 65535
3. 客户端程序中设置socket的 SO_LINGER 选项
4. 打开 tcp_tw_recycle 和tcp_timestamps 选项,有一定风险,且linux4.12之后被废弃
5. 打开 tcp_tw_reuse 和 tcp_timestamps 选项
6. 设置 tcp_max_tw_buckets 为一个较小的值

下面我们开始对各个办法进行详细讲解

办法1:代码层修改,把短连接改为长连接

由于TIME_WAIT出现的根本原因是高并发且持续的短连接,所以如果能把短连接改成长连接,就能彻底解决问题。比如http请求中的connection设置为keep-alive。只是代码层的修改往往会比较大,不再啰嗦

HTTP还要细分为3种情况,

1. 如果是HTTP一方或双方没有设置keep_alive导致的HTTP频繁断开,那么双方都在header上加上

Connection: Keep-Alive 即可

2. 如果是HTTP超时导致的断开,比如nginx的keepalive_timeout参数较小,那么设置一个大的值即可

3. 如果是HTTP的消息数达到上限,比如nginx的keepalive_requests参数较小,那么设置一个大的值即可

办法2. 修改 ip_local_port_range,增大可用端口范围

我这里linux系统是ubuntu,输入如下命令可查看可用的端口范围

默认差不多有3万个,我们可以修改这个值,但是注意最小不能小于1024,最大不能大于65535,也就是说改完之后最多有6万多个可用端口

只是一般线上遇到大量的TIME_WAIT,都是高并发且持续的短连接,单纯扩大端口范围并不能从根本上解决问题,只是能多撑一会儿

办法3. 客户端程序中设置socket的 SO_LINGER 选项

SO_LINGER 选项可以用来控制调用close函数关闭连接后的行为,linger的定义如下

struct linger {
     int l_onoff; /* 0 = off, nozero = on */
     int l_linger; /* linger time */
};

有三种情况

1. 设置 l_onoff 为0,l_linger的值会被忽略,也是内核缺省的情况,和不设置没区别。close调用会立即返回给调用者,TCP模块负责尝试发送残留的缓冲区数据,会经过通常四分组终止序列(FIN/ACK/FIN/ACK),不能解决任何问题

2. 设置 l_onoff 为1,l_linger为0,则连接立即终止,TCP将丢弃残留在发送缓冲区中的任何数据并发送一个RST报文给对方,而不是通常的四分组终止序列。对方收到RST报文后直接进入CLOSED状态,从根本上避免了TIME_WAIT状态

3. 设置 l_onoff 为1,l_linger > 0,有两种情况

    a. 如果socket为阻塞的,则close将阻塞等待l_linger 秒的时间。如果在l_linger秒时间内TCP模块成功发送完残留在缓冲区的数据,则close返回0,表示成功。如果l_linger时间内TCP模块没有成功发送残留的缓冲区数据,则close返回-1,表示失败,并将errno设置为EWOULDBLOCK

    b.  如果socket为非阻塞的,那么close立即返回,此时需要根据close返回值以及errno来判断TCP模块是否成功发送残留在缓冲区的数据

第3中情况,其实就是第1种和第2种的折中处理,且当socket为非阻塞的场景下是没有作用的

综上所述:第2种情况,也就是l_onoff为1,l_linger不为0,可以用于解决服务器大量TIME_WAIT的问题

只是Linux上测试的时候,并未发现发送了RST报文,而是正常进行了四步关闭流程,
初步推断是“只有在丢弃数据的时候才发送RST”,如果没有丢弃数据,则走正常的关闭流程

查看Linux源码,确实有这么一段注释和源码

/* As outlined in RFC 2525, section 2.17, we send a RST here because
* data was lost. To witness the awful effects of the old behavior of
* always doing a FIN, run an older 2.1.x kernel or 2.0.x, start a bulk
* GET in an FTP client, suspend the process, wait for the client to
* advertise a zero window, then kill -9 the FTP client, wheee...
* Note: timeout is always zero in such a case.
*/
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);
} 

从原理上来说,这个选项有一定的危险性,可能导致丢数据,使用的时候要小心一些

从实测情况来看,打开这个选项后,服务器TIME_WAIT连接数为0,且不受网络组网(例如是否虚拟机等)的影响

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值