• 彻底理解并解决服务器出现大量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的定义如下

    1. struct linger {
    2. int l_onoff; /* 0 = off, nozero = on */
    3. int l_linger; /* linger time */
    4. };

    有三种情况

    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源码,确实有这么一段注释和源码

    1. /* As outlined in RFC 2525, section 2.17, we send a RST here because
    2. * data was lost. To witness the awful effects of the old behavior of
    3. * always doing a FIN, run an older 2.1.x kernel or 2.0.x, start a bulk
    4. * GET in an FTP client, suspend the process, wait for the client to
    5. * advertise a zero window, then kill -9 the FTP client, wheee...
    6. * Note: timeout is always zero in such a case.
    7. */
    8. if (data_was_unread) {
    9. /* Unread data was tossed, zap the connection. */
    10. NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
    11. tcp_set_state(sk, TCP_CLOSE);
    12. tcp_send_active_reset(sk, sk->sk_allocation);
    13. }

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

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

  • 相关阅读:
    Vue3新增加的css语法糖
    [NPUCTF2020]ezinclude
    C++函数
    【面经&八股】搜广推方向:面试记录(八)
    视觉里程计(1):什么是视觉里程计
    用excel计算矩阵的乘积
    视频压缩文件太大了怎么缩小?别怕,视频压缩我有利器
    【C++】:模板进阶
    C++,没有与这些操作数匹配的“[]“运算符
    [附源码]java毕业设计高校贫困生认定系统
  • 原文地址:https://blog.csdn.net/yzf279533105/article/details/126924472