linux上TCP connection timeout问题解决办法
linux上TCPconnectiontimeout问题解决办法
最近在产线上经常出现connectiontimeout的问题,先看看Java中关于connectiontimeout的异常如何产生
JAVA中的timeout
java.net.SocketTimeoutException:connecttimedout 客户端异常:connecttimedout atjava.net.PlainSocketImpl.socketConnect(NativeMethod) atjava.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:345) atjava.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) atjava.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) atjava.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) atjava.net.Socket.connect(Socket.java:589)
我们能经常看到的connecttimedout异常产生,看一下java是如何生成这个异常
plainsocketimpl.c中
while(1){
jlongnewTime;
#ifndefUSE_SELECT
{
structpollfdpfd;
pfd.fd=fd;
pfd.events=POLLOUT;
errno=0;
connect_rv=NET_Poll(&pfd,1,timeout);
}
#else
{
fd_setwr,ex;
structtimevalt;
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;
}
/*
*Thepollwasinterruptedsoadjusttimeoutand
*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",
"connecttimedout");
/*
*Timeoutoutbutconnectionmaystillbeestablished.
*Atthehighlevelitshouldbeclosedimmediatelybut
*justincasewemakethesocketblockingagainand
*shutdowninput&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的时候才抛出connecttimeout
什么是connecttimeout?
也就是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在产线上没有这么多的客户端的连接,不可能backlogqueue会满,虽然syn_backlog的设置是8192但并不代表服务器启动的时候设置成了8192,所以必须查这个端口所设置的backlog大小
ss-lt
看到Send-Q在8080端口是128,原来在服务器端启动listen的时候设置了128的backlog
查看tomcat的配置,默认bio的设置
产线上已经设置了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_logqueue出现满的情况,更何况只有1000多个连接,代码就是真相,查看tomcat的源码。
原来accptor线程在accept之前,会去countUpOrWaitConnection发现接受到的的socket数目大于设置的work线程数目的时候,会停止accept.
countUpOrAwaitConnection();
Socketsocket=null;
try{
//Acceptthenextincomingconnectionfromtheserver
//socket
socket=serverSocketFactory.acceptSocket(serverSocket);
}catch(IOExceptionioe){
countDownConnection();
//Introducedelayifnecessary
errorDelay=handleExceptionWithDelay(errorDelay);
//re-throw
throwioe;
}
也就是说当并发超过628个连接以上,就有可能出现backlogqueue满的情况,而出现connecttimeout的情况,一切皆清楚了。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!