[转]JDBC如何进行超时设置

文档来源:https://jingyan.baidu.com/article/fc07f98922615a12ffe519ce.htmlhtml

       恰当的JDBC超时设置可以有效地减小服务失效的时间。本文将对数据库的各类超时设置及其设置方法作介绍。java

  真实案例:应用服务器在遭到DDos攻击后没法响应sql

  在遭到DDos攻击后,整个服务都垮掉了。因为第四层交换机不堪重负,网络变得没法链接,从而致使业务系统也没法正常运转。安全组很快屏蔽了全部的DDos攻击,并恢复了网络,但业务系统却仍是没法工做。 经过分析系统的thread dump发现,业务系统停在了JDBC API的调用上。20分钟后,系统仍处于WAITING状态,没法响应。30分钟后,系统抛出异常,服务恢复正常。数据库

  为何咱们明明将query timeout设置成了3秒,系统却持续了30分钟的WAITING状态?为何30分钟后系统又恢复正常了? 当你对理解了JDBC的超时设置后,就能找到问题的答案。安全

  为何咱们要了解JDBC?服务器

  当遇到性能问题或系统出错时,业务系统和数据库一般是咱们最关心的两个部分。在公司里,这两个部分是交由两个不一样的部门来负责的,所以各个部门都会集中精力地在自身领域内寻找问题,这样的话,在业务系统和数据库之间的部分就会成为一个盲区。对于Java应用而言,这个盲区就是DBCP数据库链接池和JDBC,本文将集中介绍JDBC。网络

  什么是JDBC?框架

  JDBC是Java应用中用来链接关系型数据库的标准API。Sun公司一共定义了4种类型的JDBC,咱们主要使用的是第4种,该类型的Driver彻底由Java代码实现,经过使用socket与数据库进行通讯。socket

 

图1 JDBC Type 4.性能

  第4种类型的JDBC经过socket对字节流进行处理,所以也会有一些基本网络操做,相似于HttpClient这种用于网络操做的代码库。当在网络操做中遇到问题的时候,将会消耗大量的cpu资源,而且失去响应超时。若是你以前用过HttpClient,那么你必定遇到过未设置timeout形成的错误。一样,第4种类型的JDBC,若没有合理地设置socket timeout,也会有相同的错误——链接被阻塞。

  接下来,就让咱们来学习一下如何正确地设置socket timeout,以及须要考虑的问题。

  应用与数据库间的timeout层级

 

图2 Timeout Class.

  上图展现了简化后应用与数据库间的timeout层级。(译者注:WAS/BLOC是做者公司的具体应用名称,无需深究)

  高级别的timeout依赖于低级别的timeout,只有当低级别的timeout无误时,高级别的timeout才能确保正常。例如,当socket timeout出现问题时,高级别的statement timeout和transaction timeout都将失效。

  咱们收到的不少评论中提到:

  引用:

  即便设置了statement timeout,当网络出错时,应用也没法从错误中恢复。

  statement timeout没法处理网络链接失败时的超时,它能作的仅仅是限制statement的操做时间。网络链接失败时的timeout必须交由JDBC来处理。

  JDBC的socket timeout会受到操做系统socket timeout设置的影响,这就解释了为何在以前的案例中,JDBC链接会在网络出错后阻塞30分钟,而后又奇迹般恢复,即便咱们并无对JDBC的socket timeout进行设置。

  DBCP链接池位于图2的左侧,你会发现timeout层级与DBCP是相互独立的。DBCP负责的是数据库链接的建立和管理,并不干涉timeout的处理。当链接在DBCP中建立,或是DBCP发送校验query检查链接有效性的时候,socket timeout将会影响这些过程,但并不直接对应用形成影响。

  当在应用中调用DBCP的getConnection()方法时,你能够设置获取数据库链接的超时时间,可是这和JDBC的timeout绝不相关。

 

图3 Timeout for Each Levels.

  什么是Transaction Timeout?

  transaction timeout通常存在于框架(Spring, EJB)或应用级。transaction timeout或许是个相对陌生的概念,简单地说,transaction timeout就是“statement Timeout * N(须要执行的statement数量) + @(垃圾回收等其余时间)”。transaction timeout用来限制执行statement的总时长。

  例如,假设执行一个statement须要0.1秒,那么执行少许statement不会有什么问题,但如果要执行100,000个statement则须要10,000秒(约7个小时)。这时,transaction timeout就派上用场了。EJB CMT (Container Managed Transaction)就是一种典型的实现,它提供了多种方法供开发者选择。但咱们并不使用EJB,Spring的transaction timeout设置会更经常使用一些。在Spring中,你可使用下面展现的XML或是在源码中使用@Transactional注解来进行设置。

  xml代码:

  Spring提供的transaction timeout配置很是简单,它会记录每一个事务的开始时间和消耗时间,当特定的事件发生时就会对消耗时间作校验,当超出timeout值时将抛出异常。

  Spring中,数据库链接被保存在ThreadLocal里,这被称为事务同步(Transaction Synchronization),与此同时,事务的开始时间和消耗时间也被保存下来。当使用这种代理链接建立statement时,就会校验事务的消耗时间。EJB CMT的实现方式与之相似,其结构自己也十分简单。

  当你选用的容器或框架并不支持transaction timeout这一特性,你能够考虑本身来实现。transaction timeout并无标准的API。Lucy框架的1.5和1.6版本都不支持transaction timeout,可是你能够经过使用Spring的Transaction Manager来达到与之一样的效果。

  假设某个事务中包含5个statement,每一个statement的执行时间是200ms,其余业务逻辑的执行时间是100ms,那么transaction timeout至少应该设置为1,100ms(200 * 5 + 100)。

  什么是Statement Timeout?

  statement timeout用来限制statement的执行时长,timeout的值经过调用JDBC的java.sql.Statement.setQueryTimeout(int timeout) API进行设置。不过如今开发者已经不多直接在代码中设置,而可能是经过框架来进行设置。

  以iBatis为例,statement timeout的默认值能够经过sql-map-config.xml中的defaultStatementTimeout 属性进行设置。同时,你还能够设置sqlmap中select,insert,update标签的timeout属性,从而对不一样sql语句的超时时间进行独立的配置。

  若是你使用的是Lucy1.5或1.6版本,经过设置queryTimeout属性能够在datasource层面对statement timeout进行设置。

  statement timeout的具体值须要依据应用自己的特性而定,并无可供推荐的配置。

  JDBC的statement timeout处理过程

  不一样的关系型数据库,以及不一样的JDBC驱动,其statement timeout处理过程会有所不一样。其中,Oracle和MS SQLServer的处理相相似,MySQL和CUBRID相似。

  Oracle JDBC Statement的QueryTimeout处理过程

  1. 经过调用Connection的createStatement()方法建立statement

  2. 调用Statement的executeQuery()方法

  3. statement经过自身connection将query发送给Oracle数据库

  4. statement在OracleTimeoutPollingThread(每一个classloader一个)上进行注册

  5. 达到超时时间

  6. OracleTimeoutPollingThread调用OracleStatement的cancel()方法

  7. 经过connection向正在执行的query发送cancel消息

 

图4 Query Timeout Execution Process for Oracle JDBC Statement.

  JTDS (MS SQLServer) Statement的QueryTimeout处理过程

  1. 经过调用Connection的createStatement()方法建立statement

  2. 调用Statement的executeQuery()方法

  3. statement经过自身connection将query发送给MS SqlServer数据库

  4. statement在TimerThread上进行注册

  5. 达到超时时间

  6. TimerThread调用JtdsStatement实例中的TsdCore.cancel()方法

  7. 经过ConnectionJDBC向正在执行的query发送cancel消息

 

图5 QueryTimeout Execution Process for JTDS (MS SQLServer) Statement.

  MySQL JDBC Statement的QueryTimeout处理过程

  1. 经过调用Connection的createStatement()方法建立statement

  2. 调用Statement的executeQuery()方法

  3. statement经过自身connection将query发送给MySQL数据库

  4. statement建立一个新的timeout-execution线程用于超时处理

  5. 5.1版本后改成每一个connection分配一个timeout-execution线程

  6. 向timeout-execution线程进行注册

  7. 达到超时时间

  6. TimerThread调用JtdsStatement实例中的TsdCore.cancel()方法

  7. timeout-execution线程建立一个和statement配置相同的connection

  8. 使用新建立的connection向超时query发送cancel query(KILL QUERY “connectionId”)

 

图6 QueryTimeout Execution Process for MySQL JDBC Statement (5.0.8).

  CUBRID JDBC Statement的QueryTimeout处理过程

  1. 经过调用Connection的createStatement()方法建立statement

  2. 调用Statement的executeQuery()方法

  3. statement经过自身connection将query发送给CUBRID数据库

  4. statement建立一个新的timeout-execution线程用于超时处理

  5. 5.1版本后改成每一个connection分配一个timeout-execution线程

  6. 向timeout-execution线程进行注册

  7. 达到超时时间

  6. TimerThread调用JtdsStatement实例中的TsdCore.cancel()方法

  7. timeout-execution线程建立一个和statement配置相同的connection

  8. 使用新建立的connection向超时query发送cancel消息

 

图7 QueryTimeout Execution Process for CUBRID JDBC Statement.

  什么是JDBC的socket timeout?

  第4种类型的JDBC使用socket与数据库链接,数据库并不对应用与数据库间的链接超时进行处理。

  JDBC的socket timeout在数据库被忽然停掉或是发生网络错误(因为设备故障等缘由)时十分重要。因为TCP/IP的结构缘由,socket没有办法探测到网络错误,所以应用也没法主动发现数据库链接断开。若是没有设置socket timeout的话,应用在数据库返回结果前会无期限地等下去,这种链接被称为dead connection。

  为了不dead connections,socket必需要有超时配置。socket timeout能够经过JDBC设置,socket timeout可以避免应用在发生网络错误时产生无休止等待的状况,缩短服务失效的时间。

  不推荐使用socket timeout来限制statement的执行时长,所以socket timeout的值必需要高于statement timeout,不然,socket timeout将会先生效,这样statement timeout就变得毫无心义,也没法生效。

  下面展现了socket timeout的两个设置项,不一样的JDBC驱动其配置方式会有所不一样。

socket链接时的timeout:经过Socket.connect(SocketAddress endpoint, int timeout)设置

socket读写时的timeout:经过Socket.setSoTimeout(int timeout)设置

  经过查看CUBRID,MySQL,MS SQL Server (JTDS)和Oracle的JDBC驱动源码,咱们发现全部的驱动内部都是使用上面的2个API来设置socket timeout的。

  下面是不一样驱动的socket timeout配置方式。

     

connectTimeout和socketTimeout的默认值为0时,timeout不生效。

除了调用DBCP的API之外,还能够经过properties属性进行配置。

  经过properties属性进行配置时,须要传入key为“connectionProperties”的键值对,value的格式为“[propertyName=property;]*”。下面是iBatis中的properties配置。

  xml代码:

  操做系统的socket timeout配置

  若是不设置socket timeout或connect timeout,应用多数状况下是没法发现网络错误的。所以,当网络错误发生后,在链接从新链接成功或成功接收到数据以前,应用会无限制地等下去。可是,经过本文开篇处的实际案例咱们发现,30分钟后应用的链接问题奇迹般的解决了,这是由于操做系统一样可以对socket timeout进行配置。公司的Linux服务器将socket timeout设置为了30分钟,从而会在操做系统的层面对网络链接作校验,所以即便JDBC的socket timeout设置为0,由网络错误形成的数据库链接问题的持续时间也不会超过30分钟。

  一般,应用会在调用Socket.read()时因为网络问题被阻塞住,而不多在调用Socket.write()时进入waiting状态,这取决于网络构成和错误类型。当Socket.write()被调用时,数据被写入到操做系统内核的缓冲区,控制权当即回到应用手上。所以,一旦数据被写入内核缓冲区,Socket.write()调用就必然会成功。可是,若是系统内核缓冲区因为某种网络错误而满了的话,Socket.write()也会进入waiting状态。这种状况下,操做系统会尝试从新发包,当达到重试的时间限制时,将产生系统错误。在咱们公司,从新发包的超时时间被设置为15分钟。

  至此,我已经对JDBC的内部操做作了讲解,但愿可以让你们学会如何正确的配置超时时间,从而减小错误的发生。

  最后,我将列出一些常见的问题。

  FAQ

  Q1. 我已经使用Statement.setQueryTimeout()方法设置了查询超时,但在网络出错时并无产生做用。

  ➔ 查询超时仅在socket timeout生效的前提下才有效,它并不能用来解决外部的网络错误,要解决这种问题,必须设置JDBC的socket timeout。

  Q2. transaction timeout,statement timeout和socket timeout和DBCP的配置有什么关系?

  ➔ 当经过DBCP获取数据库链接时,除了DBCP获取链接时的waitTimeout配置之外,其余配置对JDBC没有什么影响。

  Q3. 若是设置了JDBC的socket timeout,那DBCP链接池中处于IDLE状态的链接是否也会在达到超时时间后被关闭?

  ➔ 不会。socket的设置只会在产生数据读写时生效,而不会对DBCP中的IDLE链接产生影响。当DBCP中发生新链接建立,老的IDLE链接被移除,或是链接有效性校验的时候,socket设置会对其产生必定的影响,但除非发生网络问题,不然影响很小。

  Q4. socket timeout应该设置为多少?

  ➔ 就像我在正文中提的那样,socket timeout必须高于statement timeout,但并无什么推荐值。在发生网络错误的时候,socket timeout将会生效,可是再当心的配置也没法避免网络错误的发生,只是在网络错误发生后缩短服务失效的时间(若是网络恢复正常的话)。

相关文章
相关标签/搜索