linux上TCP connection timeout的原因查找

来源:互联网 发布:前台ajax获取json数据 编辑:程序博客网 时间:2024/06/05 19:30

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

JAVA中的timeout

[java] view plain copy print?
  1. java.net.SocketTimeoutException: connect timed out
  2. 客户端异常:connect timed out
  3. at java.net.PlainSocketImpl.socketConnect(Native Method)
  4. at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:345)
  5. at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
  6. at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
  7. at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
  8. at java.net.Socket.connect(Socket.java:589)

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

plainsocketimpl.c 中

[cpp] view plain copy print?
  1. while (1) {
  2. jlong newTime;
  3. #ifndef USE_SELECT
  4. {
  5. struct pollfd pfd;
  6. pfd.fd = fd;
  7. pfd.events = POLLOUT;
  8. errno = 0;
  9. connect_rv = NET_Poll(&pfd, 1, timeout);
  10. }
  11. #else
  12. {
  13. fd_set wr, ex;
  14. struct timeval t;
  15. t.tv_sec = timeout / 1000;
  16. t.tv_usec = (timeout % 1000) * 1000;
  17. FD_ZERO(&wr);
  18. FD_SET(fd, &wr);
  19. FD_ZERO(&ex);
  20. FD_SET(fd, &ex);
  21. errno = 0;
  22. connect_rv = NET_Select(fd+1, 0, &wr, &ex, &t);
  23. }
  24. #endif
  25. if (connect_rv >= 0) {
  26. break;
  27. }
  28. if (errno != EINTR) {
  29. break;
  30. }
  31. /*
  32. * The poll was interrupted so adjust timeout and
  33. * restart
  34. */
  35. newTime = JVM_CurrentTimeMillis(env, 0);
  36. timeout -= (newTime - prevTime);
  37. if (timeout <= 0) {
  38. connect_rv = 0;
  39. break;
  40. }
  41. prevTime = newTime;
  42. } /* while */
  43. if (connect_rv == 0) {
  44. JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
  45. "connect timed out");
  46. /*
  47. * Timeout out but connection may still be established.
  48. * At the high level it should be closed immediately but
  49. * just in case we make the socket blocking again and
  50. * shutdown input & output.
  51. */
  52. SET_BLOCKING(fd);
  53. JVM_SocketShutdown(fd, 2);
  54. return;
  55. }

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

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

[cpp] view plain copy print?
  1. newTime = JVM_CurrentTimeMillis(env, 0);
  2. timeout -= (newTime - prevTime);
  3. if (timeout <= 0) {
  4. connect_rv = 0;
  5. break;
  6. }

同时设置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的大小

[plain] view plain copy print?
  1. cat /proc/sys/net/ipv4/tcp_max_syn_backlog

有8192 在产线上没有这么多的客户端的连接,不可能backlog queue会满

虽然syn_backlog 的设置是8192 但并不代表服务器启动的时候设置成了8192,所以必须查这个端口所设置的backlog大小

[plain] view plain copy print?
  1. ss -lt

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

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

[html] view plain copy print?
  1. <Connectorexecutor="tomcatThreadPool"
  2. port="8080"
  3. protocol="HTTP/1.1"
  4. acceptCount="5000"
  5. connectionTimeout="25000"
  6. maxHttpHeaderSize="8192"
  7. useBodyEncodingForURI="true"
  8. enableLookups="false"
  9. redirectPort="8443"
  10. URIEncoding="UTF-8"
  11. maxThreads="500"
  12. maxKeepAliveRequests="1000"
  13. keepAliveTimeout="30000"
  14. />

产线上已经设置了acceptCount, 默认是100 但是这里设置了是5000 ,这与通过ss看到的send-q的结果严重不符合

通过内核代码分析,发现原来内核参数不仅仅是通过tcp_max_syn_backlog控制,同时也受somaxconn控制

查看

[html] view plain copy print?
  1. cat /proc/sys/net/core/somaxconn
发现值是128, OK 原因找到了,修改/etc/sysctl.conf 添加

[html] view plain copy print?
  1. 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.

[java] view plain copy print?
  1. <strong>countUpOrAwaitConnection</strong>();
  2. Socket socket = null;
  3. try {
  4. // Accept the next incoming connection from the server
  5. // socket
  6. socket = serverSocketFactory.acceptSocket(serverSocket);
  7. } catch (IOException ioe) {
  8. countDownConnection();
  9. // Introduce delay if necessary
  10. errorDelay = handleExceptionWithDelay(errorDelay);
  11. // re-throw
  12. throw ioe;
  13. }

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


0 0
原创粉丝点击