搞懂connectTimeout和socketTimeout的区别
背景
有时候,由于业务的复杂性,在JVM中拼装一些数据,会造成资源的极大浪费。举个例子,从MySQL中查询出一个List,然后在代码里循环查询数据库,进行一些字段的填充。
这种数据组装方式,除了执行效率的问题,往往会有更多的内存占用,对整个JVM计算节点造成了比较大的压力,有时候甚至造成内存溢出。于是,一些比较牛X的开发人员,使用非常复杂的SQL,来把这些耗时的操作,转嫁给数据库。
可怜的数据库,成了最后一道屏障。谁让数据库的配置普遍都比较高呢?活该。
但是可惜的是,数据库完成这些动作,同样要经历耗时的操作。Java线程等的不耐烦了,就会对用户直接返回超时,懵逼的用户会在这种情况下,再次发起重试。
要知道,Java端超时,并不代表发起的请求就结束运行了,这在一些高并发的场景中,可怜的数据库会空跑一些耗时的慢查询,计算着一些无人能知的数据。
可怜的数据库。
1、如何设置数据库超时时间
对于mysql数据库,有两个可用的参数:
connectTimeout
默认值:0,单位:毫秒
配置连接超时时间,通过 Socket 对象的 connect(SocketAddress endpoint, int timeout) 方法来配置
socketTimeout
默认值:0,单位:ms
配置socket的超时时间,通过 Socket 对象的 setSoTimeout(int timeout) 方法来配置
示例:
jdbc:mysql://xxx.xx.xxx.xxx:3306/database?connectTimeout=60000&socketTimeout=60000
2.二者区别
1 connectTimeout与socketTimeout
connect timeout和socket timeout都属于TCP层面的超时。
以mysql为例,我们可以在jdbc url中指定connectTimeout和socketTimeout。如:
jdbc:mysql://localhost:3306/db?connectTimeout=1000&socketTimeout=60000
其中:
- connectTimeout:表示的是数据库驱动(mysql-connector-java)与mysql服务器建立TCP连接的超时时间。
- socketTimeout:是通过TCP连接发送数据(在这里就是要执行的sql)后,等待响应的超时时间。
mysql驱动(mysql-connector-java)在与服务端建立Socket连接时,会将这两个参数设置到socket对象上参见:
提示:这里的mysqlConnection类型为java.net.Socket
如果这两个参数设置的不够合理,都会导致mysql驱动抛出以下异常:
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
相信大部分读者对这个异常都不陌生。接下来笔者将分别演示这两个异常是如何产生的,并提出对应的解决方案。
1.1 connectTimeout
下面首先通过一个案例演示如何模拟connectTimeout
@Testpublic void testConnectTimeout() throws SQLException { DruidDataSource dataSource = new DruidDataSource(); dataSource.setInitialSize(5); dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?connectTimeout=5"); dataSource.setUsername("root"); dataSource.setPassword(“your password"); dataSource.setDriverClassName("com.mysql.jdbc.Driver”); dataSource.init();//初始化,底层通过mysql-connector-java建立数据库连接}
笔者这里将connectTimeout设置为了5ms,表示mysql驱动与服务端建立一个连接最多不能超过5ms。由于这里是与本地(127.0.0.1)数据库建立一个连接,5ms已经足够。然而,如果你是与一个远程数据库建立连接,那么5ms可能无法完成建立一个连接,此时你极有可能会遇到类似以下异常:
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failureThe last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ...Caused by: java.net.SocketTimeoutException: connect timed out at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) ...
到这里,我们看到了:
CommunicationsException异常,异常的Caused by部分是
java.net.SocketTimeoutException: connect timed out
也就是说,建立底层socket 连接超时了。这通常意味着我们需要将connectTimeout值调大。
这个问题并非无关紧要,特别是在公司有多个数据中心的情况下,尤其需要注意。笔者曾经遇到过有业务开发同学,应用部署在北京,数据库集群在北京和上海都有部署,如下图:
1.2 socketTimeout
socket timeout是我们实际开发中最容易遇到的另外一个导致CommunicationsException异常的原因,通常是在sql的执行时间超过了socket timeout设置的情况下出现。例如socket timeout设置的是3s,但是sql执行确需要5s,那么将会出现异常。
socket timeout异常演示:
@Test public void testSocketTimeout() throws SQLException { org.apache.tomcat.jdbc.pool.DataSource datasource = new org.apache.tomcat.jdbc.pool.DataSource(); //设置socketTimeout=3000,单位是ms datasource.setUrl("jdbc:mysql://localhost:3306/test?socketTimeout=3000"); datasource.setUsername("root"); datasource.setDriverClassName("com.mysql.jdbc.Driver"); datasource.setPassword(“your password"); Connection connection = datasource.getConnection(); PreparedStatement ps = connection.prepareStatement("select sleep(5)"); ps.executeQuery();}
在这个案例中,我们模拟了一个慢查询,通过执行"select sleep(5)",sleep是mysql提供的函数,其接受一个休眠时间,单位是s,当我们把这个sql发送给mysql时,mysql服务端会休眠5秒后,再返回结果。
然而,由于我们在jdbc url中设置了socketTimeout=3000,意味着单条sql最大执行时间不能超过3s。因此运行以上案例,将会抛出类似以下异常:
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failureThe last packet successfully received from the server was 3,080 milliseconds ago. The last packet sent successfully to the server was 3,005 milliseconds ago. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ... Caused by: java.net.SocketTimeoutException: Read timed out at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) ...
这个异常看起来与connectTimeout导致的异常很相似,但是实际却有很大不同。这里我们是执行了一条sql,Caused By部分的异常提示为Read timed out,而之前是建立连接时抛出的异常,异常提示为connect timeout。
在异常信息的开始部分,我们看到了详细的错误提示信息:最后一次接收到服务端返回的报文是3080ms之前,最后一次发送报文给服务端是3005ms之前。
细心的读者已经发现,3005ms与我们设置的socketTimeout=3000如此接近,事实上,你可以认为多出的5ms是系统检测到超过socketTimeout的耗时,之后抛出异常。当然,在实际开发中,系统检测socket timeout的耗时并不是固定为5ms,每次检测的耗时可能都不同,一般不过超过几十毫秒。
另外,socketTimeout是配置在jdbc url上的,对于所有执行的sql都会有这个超时限制。因此在配置这个值的时候,应该比应用中耗时最长的sql还要稍大一点。
socketTimeout默认值也是0,也就是不超时。
本篇文章如有帮助到您,请给「翎野君」点个赞,感谢您的支持。
出处:http://www.cnblogs.com/lingyejun/
若本文如对您有帮助,不妨点击一下右下角的【推荐】。
如果您喜欢或希望看到更多我的文章,可扫描二维码关注我的微信公众号《翎野君》。
转载文章请务必保留出处和署名,否则保留追究法律责任的权利。