背景
在远程调用的世界里,Timeout的情况非常常见,几乎每段时间就会听到几个同事关于Timeout各种情况的讨论,偶尔的会出现不同开发语言间的同事的讨论,例如read timeout, 语言的隔阂使得大家讨论的都不知道是否是同一回事。
对于Java,各种远程调用,http,hessian,dubbo什么的,抛个timeout异常也是常见的事情,timeout是什么,一般追追源码,追到最后发现是个native方法,看着javadoc, 了解得不甚透彻。 所以本文尽量从Java到操作系统层面尝试说明常见的各种Timeout。
主要内容
现象
对于Java开发来说,最常见的异常莫过于SocketTimeoutException,从异常日志,一般会有两种情况
- connect timed out
- read timed out
1 | Caused by: java.net.SocketTimeoutException: connect timed out |
原理 connect timed out
“connect timed out”从字面上看就是连接的超时时间,那么超时时间是怎么控制的?
java.net.Socket
从Sokcet的connect方法可以看出,timeout参数会一致往下传递,最后到了PlainSocketImpl.socketConnect的native方法, java native方法是否真的很神秘?也不神秘,让我们一起看下JVM底层的实现,以下是jdk8-openjdk的源码
PlainSocketImpl.c
以下只截取部分重要的源码, 从源码上看,没设置超时时间时,jvm采用 connect的传统阻塞式方式,反之,则采用select/poll非阻塞式的方式, 由于poll/select都是得采用轮询的方式,在客户端没有设置超时的时候,采用轮询会带来不必要的开销,所以没设置超时时采用connect的阻塞方式是合理的
1 | JNIEXPORT void JNICALL |
原理 Read timed out
从下面这个时序图看, read timedout的原理就是通过系统调用 poll, 传入对应的socket文件句柄,在timeout时间内没有数据返回
当调用NET_Timeout没返回任何数据的时候, 根据情况会抛出 SocketTimeoutException或者SokcetException, 这个SocketTimeoutException就是我们经常遇到的read timed out
if (timeout) {
nread = NET_Timeout(fd, timeout);
if (nread <= 0) {
if (nread == 0) {
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
"Read timed out");
} else if (nread == JVM_IO_ERR) {
if (errno == EBADF) {
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
} else {
NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
"select/poll failed");
}
} else if (nread == JVM_IO_INTR) {
JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
"Operation interrupted");
}
if (bufP != BUF) {
free(bufP);
}
return -1;
}
}
总结
- “connect timed out” 是在指定时间内TCP连接未创建成功时jdk抛出的异常
- “Read timed out”是在调用socketread后,指定时间内未收到响应时 jdk抛出的异常, 假如一个http响应10k, 每次socket read 4k, 那么就需要发起3次read的请求,假如timeout设置3秒,那么就允许每次read都等待3秒,最差的情况就是大概6秒读完数据,当然这得是极端的网络情况, 所以大部分情况下都是客户端发起请求后,在指定时间内收到的服务器的回包响应。
##