MySQL JBDC驱动抛出SocketTimeoutException背后的细节

才工作一两年用Oracle的时候,一些网络问题会造成应用线程一直卡在 SocketInputStream.read() 上,或者卡了某个时间段后报出java.sql.SQLRecoverableException: IO 错误: Socket read timed out。 当时周围没人知道这个问题的原因,我在排查过程中发现,大家都百度的时候,谁能Google反而往往更能找到问题的真正原因。。。最终在http://www-01.ibm.com/support/docview.wss?uid=swg1PM91941 里找到了答案,设置超时参数oracle.jdbc.ReadTimeout,继续搜索又碰到了极品好文 Understanding JDBC Internals & Timeout Configuration

当时因为只要有SocketTimeoutException发生,再调用Connection对象的一些方法就会报错,所以下意识以为类似socketTimeout的参数超时会直接导致socket不可用。在我弄分库分表中间之后,需要了解mysql通信协议,测试的时候发现,Socket read timed out后socket仍然可用,并且jdbc驱动会发送数据库协议层的挥手请求,正常关闭连接!(其实人家javadoc就是这么写的啊)

Connection层面关于socket的两个超时参数

  • connect timeout
    连接到server的超时时间,对应java.net.Socket#connect(java.net.SocketAddress, int)的第二个参数
public void connect(SocketAddress endpoint, int timeout) throws IOException
  • socket timeout
    等待读取数据的超时时间,传递给java.net.Socket#setSoTimeout方法
public synchronized void setSoTimeout(int timeout) throws SocketException

java.net.Socket#setSoTimeout的javadoc

Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. With this option set to a non-zero timeout, a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout.

javadoc清楚的写着,超时以后java.net.SocketTimeoutException会被抛出,但是Socket还是可用的。
在mysql-jdbc驱动里面,SocketTimeoutException被包装成CommunicationsException向上抛出,连接关闭清理资源是会调用com.mysql.jdbc.MysqlIO#quit,这里会用当前socket继续向mysql发送QUIT包。如果客户端连接的是分库分表中间件的话,QUIT包确保了中间件那里不会有类似java.io.IOException: 远程主机强迫关闭了一个现有的连接这样的报错出现。

"main@1" prio=5 tid=0x1 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
      at com.mysql.jdbc.MysqlIO.quit(MysqlIO.java:2261)
      at com.mysql.jdbc.ConnectionImpl.realClose(ConnectionImpl.java:4232)
      at com.mysql.jdbc.ConnectionImpl.cleanup(ConnectionImpl.java:1338)
      at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2517)
      - locked <0x3cf> (a com.mysql.jdbc.JDBC4Connection)
      at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2449)
      at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1381)
      at com.tankilo.App.main(App.java:26)

java.net.Socket#setSoTimeout可以被多次调用

查看mysql jdbc驱动源码可以看出,驱动自己执行某些内置SQL时,用户的socketTimeout参数很大程度不合适,所以执行前会把用户设置的socketTimeout参数暂存,然后调用java.net.Socket#setSoTimeout临时设置合理的超时参数,在执行完内置SQL后再通过java.net.Socket#setSoTimeout将用户设置的socketTimeout还原。


image.png

用户如何临时调整socketTimeout参数

在mysql-jdbc驱动里,上面两个超时参数可以在获取连接时,通过【jdbc url(例如jdbc:mysql://localhost:3306/db1?connectTimeout=60000&socketTimeout=60000)】或者【java.sql.DriverManager#getConnection里的Properties对象】去设置。但是如果用户想在连接建立后,像驱动源码里一样临时为某个sql调整socketTimeout应该怎么办,侵入到驱动实现里获取socket对象?

不需要,JDBC驱动接口类已经提供了对应的API.

// java.sql.Connection#setNetworkTimeout
void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException;

这个方法的javadoc也是非常清楚的,

Sets the maximum period a Connection or objects created from the Connection will wait for the database to reply to any one request. If any request remains unanswered, the waiting method will return with a SQLException, and the Connection or objects created from the Connection will be marked as closed. Any subsequent use of the objects, with the exception of the close, isClosed or Connection.isValid methods, will result in a SQLException.
Note: This method is intended to address a rare but serious condition where network partitions can cause threads issuing JDBC calls to hang uninterruptedly in socket reads, until the OS TCP-TIMEOUT (typically 10 minutes).

This method can be invoked more than once, such as to set a limit for an area of JDBC code, and to reset to the default on exit from this area. Invocation of this method has no impact on already outstanding requests.

When the driver determines that the setNetworkTimeout timeout value has expired, the JDBC driver marks the connection closed and releases any resources held by the connection.

这个方法的问题

java.sql.Connection#setNetworkTimeout需要两个参数,一个java.util.concurrent.Executor对象,一个超时参数值。mysql-jdbc驱动会在用java.util.concurrent.Executor运行com.mysql.jdbc.ConnectionImpl.NetworkTimeoutSetter类,最终会去调用java.net.Socket#setSoTimeout
如果第一个参数传入线程池的话,这个过程是个异步过程,而java.sql.Connection#setNetworkTimeout本身也没返回像java.util.concurrent.Future这样的操作结果占位符。如果你调用完java.sql.Connection#setNetworkTimeout立马执行SQL,新的超时参数可能不会在这条SQL上生效,因为你的SQL和执行NetworkTimeoutSetter的异步线程在并发执行。

image.png

保险起见应该这样设置? 在当前线程直接执行。

conn.setNetworkTimeout(new Executor() {
            @Override public void execute(Runnable command) {
                command.run();
            }
}, 4000);
image.png

总结

本文只是一些比较零碎的个人发现,系统的介绍jdbc超时参数的文章,开头也提到过。只是在学习分库分表中间的过程中,对这些参数有了自我实践的认识。回想以前查oracle jdbc驱动的时候,大家都是百度,凭什么别人百度到,你百度不到?(别人能看Oracle付费知识库Orz...)。现在用mysql,最大的好处是开源网上信息很多,自己也可以看源码,即使老同事口口相传,你不问,他不说,你也可以自己去从代码层级确定某些问题。开放,大家一起讨论,其乐无穷。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容