前言
超时可以说是除了空指针我们最熟悉的异常了,从系统的接入层,到服务层,再到数据库层等等都能看到超时的身影;超时很多情况下同时伴随着重试,因为某些情况下比如网络抖动问题等,重试是可以成功的;当然重试往往也会指定重试次数上限,因为如果程序确实存在问题,重试多少次都无济于事,那其实也是对资源的浪费。
为什么要设置超时
对于开发人员来说我们平时最常见的就是设置超时时间,比如数据库超时设置、缓存超时设置、中间件客户端超时设置、HttpClient超时设置、可能还有业务超时;为什么要设置超时时间,因为如果不设置超时时间,可能因为某个请求无法即时响应导致整个链路处于长时间等待状态,这种请求如果过多,直接导致整个系统瘫痪,抛出超时异常其实也是及时止损;纵观各种超时时间设置,可以看到其实大多数都是围绕网络超时,而网络超时不得不提Socket超时设置。
Socket超时
Socket是作为网络通信最基础的类,要进行通信基本分为两步:
- 建立连接:在进行读写消息之前必须首先建立连接;连接阶段会有连接超时设置ConnectTimeout;
- 读写操作:读写也就是双方正式交换数据,此阶段会有读写超时设置ReadTimeOut;
连接超时
Socket提供的connect方法提供了连接超时设置:
public void connect(SocketAddress endpoint) throws IOException
public void connect(SocketAddress endpoint, int timeout) throws IOException
不设置timeout
默认是0,理论上应该是没有时间限制,经测试默认还是有一个时间限制大概21秒左右;
在建立连接的时候可能会抛出多种异常,常见的比如:
-
ProtocolException:基础协议中存在错误,例如TCP错误;
java.net.ProtocolException: Protocol error
-
ConnectException:远程拒绝连接(例如,没有进程正在侦听远程地址/端口);
java.net.ConnectException: Connection refused
-
SocketTimeoutException:套接字读取(read)或接受(accept)发生超时;
java.net.SocketTimeoutException: connect timed out java.net.SocketTimeoutException: Read timed out
-
UnknownHostException:指示无法确定主机的IP地址;
java.net.UnknownHostException: localhost1
-
NoRouteToHostException:连接到远程地址和端口时出错。通常,由于防火墙的介入,或者中间路由器关闭,无法访问远程主机;
java.net.NoRouteToHostException: Host unreachable java.net.NoRouteToHostException: Address not available
-
SocketException:创建或访问套接字时出错;
java.net.SocketException: Socket closed java.net.SocketException: connect failed
这里我们重点要讨论的是SocketTimeoutException
,同时Connection refused
也经常出现,这里做一个简单的对比
Connect timed out
本地可以直接使用一个不存在的ip尝试连接:
SocketAddress endpoint = new InetSocketAddress("111.1.1.1", 8080);
socket.connect(endpoint, 2000);
尝试连接报如下错误:
java.net.SocketTimeoutException: connect timed out
at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:589)
Connection refused
本地测试可以使用127.x.x.x来进行模拟,尝试连接报如下错误:
java.net.ConnectException: Connection refused: connect
at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(