欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

linux上TCP connection timeout问题解决办法

程序员文章站 2022-06-14 12:47:51
 linux上tcp connection timeout问题解决办法 最近在产线上经常出现connection timeout的问题,先看看java 中关于c...

 linux上tcp connection timeout问题解决办法

最近在产线上经常出现connection timeout的问题,先看看java 中关于connection timeout 的异常如何产生

java中的timeout

java.net.sockettimeoutexception: connect timed out 
客户端异常:connect timed out 
  at java.net.plainsocketimpl.socketconnect(native method) 
  at java.net.abstractplainsocketimpl.doconnect(abstractplainsocketimpl.java:345) 
  at java.net.abstractplainsocketimpl.connecttoaddress(abstractplainsocketimpl.java:206) 
  at java.net.abstractplainsocketimpl.connect(abstractplainsocketimpl.java:188) 
  at java.net.sockssocketimpl.connect(sockssocketimpl.java:392) 
  at java.net.socket.connect(socket.java:589) 

我们能经常看到的connect timed out异常产生,看一下java 是如何生成这个异常

plainsocketimpl.c 中

while (1) { 
        jlong newtime; 
#ifndef use_select 
        { 
          struct pollfd pfd; 
          pfd.fd = fd; 
          pfd.events = pollout; 
 
          errno = 0; 
          connect_rv = net_poll(&pfd, 1, timeout); 
        } 
#else 
        { 
          fd_set wr, ex; 
          struct timeval t; 
 
          t.tv_sec = timeout / 1000; 
          t.tv_usec = (timeout % 1000) * 1000; 
 
          fd_zero(&wr); 
          fd_set(fd, &wr); 
          fd_zero(&ex); 
          fd_set(fd, &ex); 
 
          errno = 0; 
          connect_rv = net_select(fd+1, 0, &wr, &ex, &t); 
        } 
#endif 
 
        if (connect_rv >= 0) { 
          break; 
        } 
        if (errno != eintr) { 
          break; 
        } 
 
        /* 
         * the poll was interrupted so adjust timeout and 
         * restart 
         */ 
        newtime = jvm_currenttimemillis(env, 0); 
        timeout -= (newtime - prevtime); 
        if (timeout <= 0) { 
          connect_rv = 0; 
          break; 
        } 
        prevtime = newtime; 
 
      } /* while */ 
 
      if (connect_rv == 0) { 
        jnu_throwbyname(env, jnu_javanetpkg "sockettimeoutexception", 
              "connect timed out"); 
 
        /* 
         * timeout out but connection may still be established. 
         * at the high level it should be closed immediately but 
         * just in case we make the socket blocking again and 
         * shutdown input & output. 
         */ 
        set_blocking(fd); 
        jvm_socketshutdown(fd, 2); 
        return; 
      } 

这里可以看到在做connect的时候,是调用 net_poll 或者 net_select, 在linux 上就是使用 poll/select

当发生timeout的时候connect_rv=0  ,这里有个注意点虽然在poll/select 是传入timeout的时间,但是这是会被打断的,connect_rv返回的值为-1 ,所以jvm里面重新计算了timeout , 确保timeout 的时间片已经运行完了,才推出循环。

newtime = jvm_currenttimemillis(env, 0); 
        timeout -= (newtime - prevtime); 
        if (timeout <= 0) { 
          connect_rv = 0; 
          break; 
        } 

同时设置connect_rv 为0, 也是下面只有当connect_rv为0的时候才抛出connect timeout

什么是connect timeout ?

也就是client 发出 syn 包,server端在你指定的时间内没有回复ack,poll/select 返回0

server 端为什么没有回复ack, 因为syn包的回复是内核层的,要么网络层丢包,要么就是内核层back_log的queue满了,关于backlog在本片中就不详细描述了。

当时查看产线上的连接最高能到1000多,同时查看了backlog 的queue的大小

cat /proc/sys/net/ipv4/tcp_max_syn_backlog 

有8192 在产线上没有这么多的客户端的连接,不可能backlog queue会满,虽然syn_backlog 的设置是8192 但并不代表服务器启动的时候设置成了8192,所以必须查这个端口所设置的backlog大小

ss -lt 

看到send-q在8080端口是128 ,原来在服务器端启动listen 的时候设置了128的backlog

查看tomcat 的配置,默认bio的设置

<connector executor="tomcatthreadpool" 
      port="8080"  
        protocol="http/1.1" 
          acceptcount="5000" 
          connectiontimeout="25000" 
          maxhttpheadersize="8192" 
          usebodyencodingforuri="true" 
          enablelookups="false"  
          redirectport="8443"  
          uriencoding="utf-8"  
          maxthreads="500" 
          maxkeepaliverequests="1000" 
          keepalivetimeout="30000" 
        /> 

产线上已经设置了acceptcount, 默认是100 但是这里设置了是5000 ,这与通过ss看到的send-q的结果严重不符合
通过内核代码分析,发现原来内核参数不仅仅是通过tcp_max_syn_backlog控制,同时也受somaxconn控制
查看

cat /proc/sys/net/core/somaxconn 

发现值是128, ok 原因找到了,修改/etc/sysctl.conf 添加

net.core.somaxconn = 8192 

sysctl -f /etc/sysctl.conf 重新加载一下,这样就能改变全局了

问题:是1000多个连接,500个工作线程,因为backlog的大小是受socket.accept控制的,我们通常境况下会单独起一个线程去serversocket.accept(),而当前server的load并不高,不因该会出现back_log queue出现满的情况,更何况只有1000多个连接,代码就是真相,查看tomcat的源码。

原来accptor 线程在accept 之前,会去countuporwaitconnection 发现接受到的的socket数目大于设置的work线程数目的时候,会停止accept.

<strong>countuporawaitconnection</strong>(); 
 
         socket socket = null; 
         try { 
           // accept the next incoming connection from the server 
           // socket 
           socket = serversocketfactory.acceptsocket(serversocket); 
         } catch (ioexception ioe) { 
           countdownconnection(); 
           // introduce delay if necessary 
           errordelay = handleexceptionwithdelay(errordelay); 
           // re-throw 
           throw ioe; 
         } 

也就是说当并发超过628个连接以上,就有可能出现backlog queue满的情况,而出现connect timeout的情况,一切皆清楚了。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!