linux上TCP connection timeout問題解決辦法
最近在產(chǎn)線上經(jīng)常出現(xiàn)connection timeout的問題,先看看Java 中關(guān)于connection timeout 的異常如何產(chǎn)生
JAVA中的timeout
1
2
3
4
5
6
7
8
|
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 ) |
我們能經(jīng)常看到的connect timed out異常產(chǎn)生,看一下java 是如何生成這個異常
plainsocketimpl.c 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
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的時候,是調(diào)用 NET_Poll 或者 NET_Select, 在linux 上就是使用 poll/select
當(dāng)發(fā)生timeout的時候connect_rv=0 ,這里有個注意點雖然在poll/select 是傳入timeout的時間,但是這是會被打斷的,connect_rv返回的值為-1 ,所以jvm里面重新計算了timeout , 確保timeout 的時間片已經(jīng)運行完了,才推出循環(huán)。
1
2
3
4
5
6
|
newTime = JVM_CurrentTimeMillis(env, 0); timeout -= (newTime - prevTime); if (timeout <= 0) { connect_rv = 0; break ; } |
同時設(shè)置connect_rv 為0, 也是下面只有當(dāng)connect_rv為0的時候才拋出connect timeout
什么是connect timeout ?
也就是client 發(fā)出 syn 包,server端在你指定的時間內(nèi)沒有回復(fù)ack,poll/select 返回0
server 端為什么沒有回復(fù)ack, 因為syn包的回復(fù)是內(nèi)核層的,要么網(wǎng)絡(luò)層丟包,要么就是內(nèi)核層back_log的queue滿了,關(guān)于backlog在本片中就不詳細描述了。
當(dāng)時查看產(chǎn)線上的連接最高能到1000多,同時查看了backlog 的queue的大小
1
|
cat /proc/sys/net/ipv4/tcp_max_syn_backlog |
有8192 在產(chǎn)線上沒有這么多的客戶端的連接,不可能backlog queue會滿,雖然syn_backlog 的設(shè)置是8192 但并不代表服務(wù)器啟動的時候設(shè)置成了8192,所以必須查這個端口所設(shè)置的backlog大小
1
|
ss -lt |
看到Send-Q在8080端口是128 ,原來在服務(wù)器端啟動listen 的時候設(shè)置了128的backlog
查看tomcat 的配置,默認bio的設(shè)置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<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" /> |
產(chǎn)線上已經(jīng)設(shè)置了acceptCount, 默認是100 但是這里設(shè)置了是5000 ,這與通過ss看到的send-q的結(jié)果嚴重不符合
通過內(nèi)核代碼分析,發(fā)現(xiàn)原來內(nèi)核參數(shù)不僅僅是通過tcp_max_syn_backlog控制,同時也受somaxconn控制
查看
1
|
cat /proc/sys/net/core/somaxconn |
發(fā)現(xiàn)值是128, OK 原因找到了,修改/etc/sysctl.conf 添加
1
|
net.core.somaxconn = 8192 |
sysctl -f /etc/sysctl.conf 重新加載一下,這樣就能改變?nèi)至?/p>
問題:是1000多個連接,500個工作線程,因為backlog的大小是受socket.accept控制的,我們通常境況下會單獨起一個線程去serversocket.accept(),而當(dāng)前server的load并不高,不因該會出現(xiàn)back_log queue出現(xiàn)滿的情況,更何況只有1000多個連接,代碼就是真相,查看tomcat的源碼。
原來accptor 線程在accept 之前,會去countUpOrWaitConnection 發(fā)現(xiàn)接受到的的socket數(shù)目大于設(shè)置的work線程數(shù)目的時候,會停止accept.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<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; } |
也就是說當(dāng)并發(fā)超過628個連接以上,就有可能出現(xiàn)backlog queue滿的情況,而出現(xiàn)connect timeout的情況,一切皆清楚了。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
原文鏈接:http://blog.csdn.net/raintungli/article/details/37879907