`
Tyrion
  • 浏览: 261009 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

深入理解 JDBC 的超时

阅读更多

本文引自我的博客:https://juejin.im/post/5a67f836518825732b1a0086

 

这是最近读到的讲关于 JDBC 的超时问题最透彻的文章,原文是http://www.cubrid.org/blog/understanding-jdbc-internals-and-timeout-configuration ,网上现有的翻译感觉磕磕绊绊的,很多上下文信息丢失了,这里用我的理解重新翻译一下。

应用程序中配置恰当的 JDBC 超时时间能减少服务失败的时间,这篇文章我们将讨论不同种类的超时和推荐的配置。

Web 应用服务器在 DDoS 攻击后变得无响应

(这是一个真实案例的发生过程复述)

在 DDoS 攻击之后,整个服务都不能正常工作了,因为第四层交换机不能工作,网络连接断开了,这也导致 WAS (可以将 WAS 理解为作者公司的应用程序)不能正常工作。攻击发生后不久,安全团队拦截了所有 DDoS 攻击,然后网络恢复正常,但 WAS 还是不能工作。

通过分析系统的 dump 日志发现,业务系统停在了 JDBC API 的调用上。20分钟后系统仍处于等待状态无法响应,大概过了30分钟,系统突然发生异常,然后服务恢复正常。

为什么已经将查询超时时间设置成3秒, WAS 却等待了30分钟?为什么30分钟后 WAS 又开始工作了?

如果理解了 JDBC 的超时机制就能找到答案。

为什么我们需要知道 JDBC 驱动

当有性能问题或系统级错误时,WAS 和数据库是我们关注的两个重要层面。在我公司 WAS 和数据库通常由不同的部门负责,因此每个部门聚焦在各自负责的领域来设法弄清楚状况。此时 WAS 和数据库之间的部分会因为得不到足够的关注而产生盲区。对于 Java 应用,这个盲区在数据库连接池和 JDBC 之间,本文我们将重点讨论 JDBC。

什么是 JDBC 驱动

JDBC 是 Java 应用程序中用于访问数据库的一套标准 API,Sun 公司定义了4种类型的 JDBC 驱动。我公司主要用的是第4种,该类型驱动由纯 Java 语言编写,在 Java 应用中通过 socket 与数据库通信。


图1: 类型4驱动

类型4驱动是通过 socket 来处理字节流的,它的基本操作和 HttpClient 这种网络操作类库相同。同其他网络类库一样,也会在发生超时的时候占用大量的 CPU 资源从而失去响应。如果你之前用过 HttpClient ,肯定遇到过因为没有设置超时导致的错误。如果 socket 超时设置不合适,类型4驱动也可能有同样的错误(连接被阻塞)。

下面让我们了解如何配置 JDBC 驱动的 socket 超时,以及设置时需考虑哪些问题。

WAS 与数据库间的设置超时的层次


图2: 超时的层次

图2展示了简化的 WAS 和数据库通信时的超时层次。

更上层的超时依赖于下层的超时,只有当较低层的超时机制正常工作,上层的超时才会正常。如果 JDBC 驱动程序的 socket 超时工作不正常,那么更上层的超时比如 Statement 超时和事务超时都不会正常工作。

我们收到很多评论说:

即使配置了 Statement 超时,应用程序还是不能从故障中恢复,因为 Statement 超时在网络故障时不起作用。

Statement 超时在网络故障时不起作用。它只能做到:限制一次Statement 执行的时间,处理超时以防网络故障必须由 JDBC 驱动来做。

JDBC 驱动的 socket 超时还会受操作系统的 socket 超时配置的影响。这解释了为什么案例中的 JDBC 连接在网络故障后阻塞了30分钟才恢复,即使没配置 JDBC 驱动的 socket 超时。

DBCP 连接池位于图2的左边。你会发现各种层面的超时与 DBCP 是分开的。DBCP 负责数据库连接(即本文中说到的Connection)的创建和管理,并不涉及超时的处理。当在 DBCP 中创建了一个数据库连接或发送了一条查询校验的 sql 语句用于检查连接有效性时,socket 超时会影响这些过程的处理,但并不直接影响应用程序。

然而在应用程序中调用 DBCP 的 getConnection() 方法时,你能指定应用程序获取数据库连接的超时时间,但这和 JDBC 的连接超时无关。


图3: 每一层级的超时

什么是事务超时

事务超时是在框架(Spring、EJB容器)或应用程序层面上才有效的超时。

事务超时可能是个不常见的概念。简单讲,事务超时等于 Statement 超时 * N(需要执行的 Statement 的数量) + 其它(垃圾回收等其他时间)。事务超时被用来限制执行一个事务之内所有 Statement 执行的总时长。

比如,假设执行一次 Statement 执行需0.1秒,那执行几次 Statement
并不是什么问题,但如果是执行十万次则需要一万秒(大约7个小时),这就可以用上事务超时了。

EJB 的声明式事务管理 (容器管理事务) 就是一种典型的使用场景,但声明式事务管理只是定义了相应的规范,容器内事务的处理过程和具体实现由容器的开发者负责。我们公司并没有用 EJB,用的是最常见的 Spring 框架,所以事务超时的配置也由 Spring 来管理。在 Spring 中,事务超时可以在 XML 文件显式配置或在 Java 代码中用 Transactional 注解来配置。

<tx:attributes>
        <tx:method name="…" timeout="3"/>
</tx:attributes>

Spring 提供的事务超时的配置非常简单,它会记录每个事务的开始时间和消耗时间,当特定的事件发生时会对已消耗掉的时间做校验,如果超出了配置将抛出异常。

Spring 中数据库连接被保存在线程本地变量(ThreadLocal)中,这被称作事务同步(Transaction Synchronization)。当数据库连接被保存到 ThreadLocal 时,同时会记录事务的开始时间和超时时间。所以通过数据库连接的代理创建的 Statement 在执行时就会校验这个时间。

EJB 的声明式事务管理的实现也是类似,实现的思路非常简单。如果事务超时非常重要,但你所使用的容器或框架不提供此功能,你也可以选择自己实现,关于事务超时并没有制定标准的 API。

Lucy 框架的1.5和1.6版不支持事务超时,但你可以通过 Spring 的事务管理达到相同的效果。

假设一个事务里有5条 Statement ,每条 Statement 执行时间是200毫秒,其它业务逻辑或框架操作的执行时间是100毫秒,那事务允许的超时时间至少应该1100毫秒(200 * 5 + 100)。

什么是 Statement 超时

Statement 超时是用来限制 Statement 的执行时间的,它的具体值是通过 JDBC API 来设置的。JDBC 驱动程序基于这个值进行 Statement 执行时的超时处理。Statement 超时是通过 JDBC API 中java.sql.Statement 类的 setQueryTimeout(int timeout) 方法配置的。不过现在的开发者已经很少直接在代码中配置它了,更多是通过框架来进行设置。

以 iBatis 为例,可以通过 SqlMapConfig.xml 中的 setting 属性defaultStatementTimeout 来设置全局的 statement 超时缺省值。你也可以通过在具体的 sql 映射文件中的 select insert update 标签的 statement 属性来覆盖。

当你用 Lucy 1.5或1.6版时,可以通过设置 queryTimeout 属性在数据源层面设置 Statement 超时。 

Statement 超时的具体数值需要根据每个应用自身的情况而定,并没有推荐的配置。

JDBC 驱动中的 Statement 超时处理过程

每个数据库和驱动程序的 Statement 超时的处理也是不同的。Oracle 和 SQLServer 的工作方式比较像,MySQL 和 CUBRID 比较像。

Oracle 中的 Statement 超时处理

  1. 调用 Connection 的 createStatement() 方法创建一个 Statement 对象
  2. 调用 Statement 的 executeQuery() 方法
  3. Statement 对象通过内部绑定的 Connection 对象将查询命令发送到 Oracle 数据库
  4. Statement 对象向 Oracle 的超时处理线程 OracleTimeoutPollingThread(每个类加载器一个该线程)注册一个 statement 用于处理超时
  5. 发生超时
  6. Oracle 的超时处理线程调用 OracleStatement 对象的 cancel() 方法
  7. 通过 Statement 的 Connection 对象发送一条消息取消还在执行的查询

图4 Oracle 的 Statement 超时执行过程

JTDS (MS SQLServer) 中的 Statement 超时处理

1.调用 Connection 的 createStatement() 方法创建一个 Statement 对象

  1. 调用 Statement 的 executeQuery() 方法
  2. Statement 对象通过内部的 Connection 对象将查询命令发送到 MS SqlServer 数据库
  3. Statement 对象向 MS SQLServer 的 TimerThread 线程注册一个 statement 用于处理超时
  4. 发生超时
  5. TimerThread 调用 JtdsStatement 对象内部的 TsdCore.cancel()方法
  6. 通过 ConnectionJDBC 发送一条消息取消还在执行的查询

图5 MS SQLServer 的 Statement 超时执行过程

MySQL (5.0.8) 中的 Statement 超时处理

  1. 调用 Connection 的 createStatement() 方法创建一个 Statement 对象
  2. 调用 Statement 的 executeQuery() 方法
  3. Statement 对象通过内部的 Connection 对象将查询命令传输到 MySqlServer 数据库
  4. Statement 创建一个新的超时执行线程(timeout-execution)来处理超时
  5. 5.1以上版本改为每个连接分配一个线程
  6. 向 timeout-execution 线程注册当前的 Statement 对象
  7. 发生超时
  8. timeout-execution 线程创建一个相同配置的 Connection 对象
  9. 用新创建的 Connection 发送取消查询的命令

图6 MySQL 的 Statement 超时执行过程

CUBRID中的 Statement 超时处理

  1. 调用 Connection 的 createStatement() 方法创建一个 Statement 对象
  2. 调用 Statement 的 executeQuery() 方法
  3. Statement 对象通过内部的 Connection 对象将查询命令发送到 CUBRID 数据库
  4. Statement 对象创建一个新的超时执行线程(timeout-execution)来处理超时
  5. 向 timeout-execution 线程注册当前的 Statement 对象
  6. 发生超时
  7. timeout-execution 线程创建一个相同配置的Connection 对象
  8. 用新创建的 Connection 发送取消查询的命令

图7 CUBRID 的 Statement 超时执行过程

什么是 Socket 超时

类型4的 JDBC 驱动是用 socket 方式与数据库连接的,应用程序和数据库之间的连接超时并不是由数据库处理的。

当数据库突然宕掉或发生网络错误(设备故障等)时,JDBC 驱动的 socket 超时的值是必须的。由于 TCP/IP 的结构,socket 没有办法检测到网络错误,因此应用不能检测到与数据库到连接断开了。如果没有设置 socket 超时,应用程序会一直等待数据库返回结果。(这个连接也被叫做“死连接”) 为了避免死连接,socket 必须要设置超时时间。socket 超时可以通过 JDBC 驱动程序配置。通过设置 socket 超时,可以防止出现网络错误时一直等待的情况并缩短故障时间。

不推荐使用 socket 超时来限制一个 statement 的执行时间,因此socket 超时的值必须要高于 statement 的超时时间,否则 socket 超时将会先生效,这样 statement 超时就没有意义,也无法生效。

下面展示了 socket 超时设置的连个选项,其配置因不同的驱动而异。

  • socket 连接时的超时:通过 Socket 对象的 connect(SocketAddress endpoint, int timeout) 方法来配置
  • socket 读写时的超时:通过 Socket 对象的 setSoTimeout(int timeout) 方法来配置

通过查看CUBRID,MySQL,MS SQL Server (JTDS) 和 Oracle 的JDBC 驱动源码,我们确认以上所有驱动都是使用上面的2个 API 来设置socket 超时的。

下面列出了如何配置 socket 超时

JDBC 驱动 连接超时配置 socket 超时配置 JDBC Url 格式 示例
MySQL connectTimeout(默认值:0,单位:毫秒) socketTimeout(默认值:0,单位:ms) jdbc:mysql://[host:port],[host:port].../[database] 
[?propertyName1][=propertyValue1][&propertyName2][=propertyValue2]...
jdbc:mysql://xxx.xx.xxx.xxx:3306/database?connectTimeout=60000&socketTimeout=60000
MS-SQL , jTDS loginTimeout(默认值:0,单位:秒) socketTimeout(默认值:0,单位:s) jdbc:jtds:<server_type>://<server>[:<port>][/<database>][;<property>=<value>[;...]] jdbc:jtds:sqlserver://server:port/database;loginTimeout=60;socketTimeout=60
Oracle oracle.net.CONNECT_TIMEOUT (默认值:0,单位:毫秒) oracle.jdbc.ReadTimeout(默认值:0,单位:毫秒) 不支持通过url配置,只能通过OracleDatasource.setConnectionProperties() API设置,使用DBCP时可以调用BasicDatasource.setConnectionProperties()或BasicDatasource.addConnectionProperties()进行设置 -
CUBRID 无单独配置项(默认值:5,000,单位:毫秒) 无单独配置项(默认值:5,000,单位:毫秒) - -
  • connectTimeout 和 socketTimeout 的默认值是 0 ,这意味着不会发生超时。
  • 你也可以通过属性进行配置,而无需直接使用 DBCP 的 API 。

通过属性进行配置时,需要传入的 key 为 "connectionProperties",其 value 的格式为" [propertyName=property;]*"。下面是 iBatis 中通过 xml 文件配置属性的例子。

<transactionManager type="JDBC">
  <dataSource type="com.nhncorp.lucy.db.DbcpDSFactory">
     ....
     <property name="connectionProperties" value="oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=6000"/> 
  </dataSource>
</transactionManager>

操作系统层面的 socket 超时配置

如果没设置 socket 超时或连接超时,应用程序多数情况下无法检测到网络错误。此时,应用程序将一直等待下去,直到连接上数据库或能读取到数据。然而,如果你查看 NHN 服务遇到的实际情况会发现问题常常在在应用程序(WAS)在30分钟后尝试重新连接到网络后被解决了。这是因为操作系统也配置了 socket 超时时间。NHN 使用的 Linux 服务器将 socket 超时时间设置为30分钟。它将在操作系统层面对网络连接做校验。因为公司的 Linux 服务器的 KeepAlive 检查周期为30分钟,因此即使应用程序里将 socket 超时设置为0,由网络原因引起的数据库网络连接问题也不会超过30分钟。

通常,应用程序会在调用 socket 的 read() 方法时由于网络问题而阻塞住。然而很少在调用 socket 的 write() 方法时处于等待状态,这取决于网络构成和错误类型。当应用程序调用 socket 的 write() 方法时,数据被记录到操作系统的内核缓冲区,然后将控制权立即交还给应用程序。因此,一旦数据已经写入内核缓冲区,socket.write() 的调用始终是成功。但是,如果操作系统内核缓冲区由于特殊的网络错误而满了的话,socket.write() 也会进入等待状态。这种情况下,操作系统会尝试重新发送数据包一段时间,并在达到超时限制时产生错误。 在公司的 Linux服务器上这种情况的超时时间设置为15分钟。

至此,我已经解释了 JDBC 的内部操作,希望这将帮助你正确的超时配置超时时间从而减少错误。

至此,我已经对JDBC的内部操作做了讲解,希望能够让大家学会如何正确的配置超时时间,从而减少错误的发生。

1
0
分享到:
评论

相关推荐

    揭秘JDBC超时机制完整版

    在本文中,我们将深入探讨JDBC的超时机制,以及在实际应用中可能出现的问题和解决方案。 首先,我们来看一个真实的案例:在DDoS攻击后,尽管网络恢复,但业务系统仍然无法正常工作,这是因为JDBC API调用出现了问题...

    JDBC.JDBC.JDBC.

    通过了解JDBC的基本概念、`java.sql`包以及`DriverManager`类的使用,开发者可以编写出能够连接和操作数据库的Java程序。在实际开发中,还需要学习更多关于处理`SQLException`、批处理、事务管理以及更高效的`...

    jdbc连接db2

    通过本文,我们不仅学习了如何使用 JDBC 连接 DB2 数据库,而且还深入了解了相关的概念和技术细节。在实际开发中,这些知识将帮助开发者更高效地管理数据库操作,提高应用程序的性能和可靠性。希望本文能为你在使用 ...

    尚硅谷jdbc视频教程

    ### 尚硅谷JDBC视频教程知识点详解 #### 一、JDBC简介 JDBC(Java Database ...通过系统学习这些内容,不仅可以快速掌握JDBC的基本用法,还能深入了解如何在实际项目中应用这些技术,从而提升自己的技术水平。

    JDBC连接SQL Server例子

    在本例中,我们将深入探讨如何使用JDBC连接到SQL Server数据库。 首先,确保你已经安装了SQL Server数据库,并且在本地或远程服务器上有一个可用的实例。同时,你也需要获取到数据库的连接信息,包括服务器名、...

    greenplum_jdbc_5.1.4.zip

    为了方便Java开发者与Greenplum数据库进行交互,Pivotal公司提供了Greenplum JDBC驱动,本次我们将深入探讨的是版本为5.1.4的JDBC驱动。 1. **Greenplum数据库简介** Greenplum是一种开源、分布式的关系型数据库...

    sqljdbc4.jar

    本文将深入探讨sqljdbc4.jar及其在Java数据库连接(JDBC)中的重要角色,帮助开发者更好地理解和运用这一工具。 sqljdbc4.jar是微软公司提供的一个驱动程序,用于支持Java应用程序通过JDBC接口与Microsoft SQL ...

    jdbc.rar_jdbc

    本教程将深入讲解JDBC在Java开发环境中的使用方法。 ### JDBC基本概念 1. **驱动程序**:JDBC驱动是连接Java应用程序和数据库的桥梁,分为四种类型:JDBC-ODBC桥接驱动、网络协议驱动、部分Java驱动(Type 2、Type...

    spring-jdbc源码

    在本篇文章中,我们将深入探讨Spring-JdbcTemplate、DataSourceTransactionManager以及相关的核心概念,帮助你理解Spring-JDBC的内部工作机制。 首先,Spring-JdbcTemplate是Spring对JDBC API的一种封装,它消除了...

    MSSQL JDBC 资源包

    解决这些问题通常需要对JDBC API有深入的理解,并且在代码中进行相应的配置和处理。 总的来说,MSSQL JDBC资源包为Java开发者提供了一个方便的工具,使他们能够轻松地在Java应用程序中连接和操作SQL Server 2008...

    Cassandra JDBC Driver 0.8.2

    2. **遵循 JDBC 规范**:该驱动程序按照 JDBC(Java Database Connectivity)规范设计,提供了一个标准化的接口,使得开发人员能够在不深入了解 Cassandra 本身的底层机制的情况下,使用熟悉的 JDBC 代码来操作 ...

    Impala的JDBC编程驱动

    标题中的“Impala的JDBC编程驱动”指的是Impala(一个开源的、高性能的SQL查询引擎,用于处理存储在Hadoop集群中的数据)与...通过深入理解这个驱动的工作原理和使用方法,开发者可以构建出强大的数据处理和分析工具。

    JDBC 4 ORACLE

    **JDBC 4 for Oracle: 深入理解与实践** 在Java开发中,JDBC(Java Database Connectivity)是连接数据库的重要桥梁,而Oracle作为一款广泛使用的商业级数据库系统,其与JDBC的结合使用是许多企业级应用的基石。...

    mysqljdbc src

    在"mysqljdbc src"中,我们探讨的是MySQL JDBC 5的源代码,这为我们提供了深入理解其内部工作原理的机会。 源代码分析通常有助于开发者调试问题、优化性能、学习设计模式以及自定义功能。MySQL JDBC 5源码包含了...

    最新Oracle_11g JDBC驱动包

    本文将深入探讨Oracle JDBC驱动的不同类型、其工作原理以及如何在项目中正确配置和使用。 Oracle JDBC驱动主要分为四种类型: 1. ** Thin驱动(Type 4)**:这是一种纯Java实现的驱动,无需中间件或本地库。它直接...

    tomcat-jdbc-7.0.42.jar

    《深入理解Tomcat JDBC连接池7.0.42版》 在Java Web开发中,数据库连接管理是至关重要的部分,而Tomcat JDBC连接池(也称为Tomcat JNDI DataSource Provider)则是一个高效、轻量级且功能丰富的连接池实现。这个...

    jdbc4文档PDF版

    JDBC4.0文档详尽地介绍了这一版本的所有新特性,帮助开发者更好地理解和利用这些功能。 1. **自动注册驱动** JDBC4引入了一个新的特性,允许驱动自动注册到Java虚拟机(JVM)。这意味着开发人员不再需要使用`Class...

    Microsoft SQL Server 2005 JDBC

    解决这些问题通常需要对SQL Server配置、JDBC驱动参数设置以及数据库和Java编程有深入理解。 在提供的压缩包文件"mssql2005"中,可能包含了JDBC驱动的jar文件、示例代码、文档或配置指南。这些资源可以帮助开发者更...

    java超时控制

    这篇我们将深入探讨Java中的超时控制技术及其应用。 1. **Socket超时设置** Java的`Socket`类提供了设置连接超时、读取超时和写入超时的方法。`connect()`方法可以设置连接超时,防止程序在尝试建立连接时无限期...

    JDBC与Java数据库编程

    本教程将深入探讨JDBC的核心概念、工作原理以及如何在实际项目中应用。 一、JDBC概述 JDBC是Java SDK的一部分,由Sun Microsystems开发并维护,现在归Oracle公司所有。它是一个接口和类的集合,允许Java程序员编写...

Global site tag (gtag.js) - Google Analytics