深入源码分析下 HIVE JDBC 的超时机制及其如何配置 socketTimeOut
1. 从一个常见的 HIVE JDBC SocketTimeoutException 问题聊起
在并发较高负载较大的大数据集群中,执行 HIVE SQL 常见的一个问题是 SocketTimeoutException 超时,即 “org.apache.thrift.transport.TTransportException: java.net.SocketTimeoutException: Read timed out”,其完整报错信息如下;
针对 HIVE JDBC 这类 SocketTimeoutException 超时问题,除了想办法提高集群性能或降低集群负载(比如错峰执行),我们也可以在HIVE JDBC 客户端,在应用级别配置使用更大的 hive thrift socket time out,从而减小 SocketTimeoutException 发生的概率;
由于 hive 的 SocketTimeout,其底层直接获取的是 HiveConnection 的 LoginTimeout,而 HiveConnection 的 LoginTimeout,目前所有版本的HIVE,都需要通过静态方法 java.sql.DriverManager.setLoginTimeout(int seconds) 进行配置,所以无论应用是使用线程池如 Hikari/Druid/DBCP 创建和管理 HIVE JDBC 连接,还是直接创建和管理 HIVE JDBC 连接,在本质上,都需要显示或隐式地通过 java.sql.DriverManager.setLoginTimeout(int seconds)配置 LoginTimeout 进而配置 SocketTimeout;
如果应用程序直接创建和管理 HIVE JDBC 连接:用户需要在 JVM 运行时会加载的某个类的成员方法或静态方法或静态代码段中,调用静态方法java.sql.DriverManager.setLoginTimeout(int seconds)配置静态变量 loginTimeout,即可间接配置 hive thrift socketTimeout;
如果应用程序使用线程池如 Hikari 来创建管理 HIVE JDBC 连接: 除了上述方法,用户还可以显示配置 connectionTimeout 为非0值,此时 hikari 会基于用户配置的 connectionTimeout,自动推导然后调用静态方法 java.sql.DriverManager.setLoginTimeout(int seconds) 配置静态变量 loginTimeout,进而间接配置 hive thrift socketTimeout,推荐使用该方法进行配置;
1 | # hive sql 作业 SocketTimeoutException 异常示例 |
2. JDBC 超时相关参数以及超时相关接口和方法总结
总体而言,JDBC 的超时有以下几种:
事务超时transaction timeout:事务超时可以用来限制某个事务中所有 statement 语句的处理时间之和的最大值,简单来说,事务超时 transaction timeout = 语句超时 statement/query timeout * 事务中语句个数 + 其他耗时(如业务代码处理时间,gc 垃圾回收时间等),事务超时一般在应用框架中进行配置, 如 spring 中,可以使用注解 @Transactional 指定;
查询超时 query timeout:有时也被称为语句超时 statement timeout,可以用来限制某个 statement 语句(可以是增删改查)的最大执行时间,若该 sql语句在该超时时间内还没有返回执行结果,应用端的数据库驱动程序就会抛出超时异常,并发送取消执行的信号给远程的数据库管理系统,由数据库管理系统取消该语句的执行;(所以底层依赖健康的TCP连接);
连接超时 connectTimeout:有时也被称为网络超时 NetworkTimeout,是驱动程序建立 JDBC 底层的 TCP 连接的超时时间;
登录超时 loginTimeout: 登录超时是数据库用户成功登录到数据库服务器的超时时间,由于用户登录数据库服务器时,底层包含了和数据库服务器之间的 tcp 连接的建立,也包含了数据库服务器对用户的认证,所以一般而言,需要配置登录超时 > 连接超时;
TCP 套接字超时( TCP socket timeout):由于应用程序通过 TCP 协议读写网络数据包,都是通过 TCP/IP 协议栈的 socket api 进行的,所以常规的套接字超时 socket timeout 同样适用于 JDBC 应用程序,此时应用程序通过 socket timeout 来检测和感知网络层面 TCP 连接的异常,从而避免僵死连接造成的无限等待;(TCP是面向连接的协议,但这里的连接是虚拟的,是动态的,也是不对等的,对这块感兴趣的朋友,可以关注笔者对 tcp/ip 协议栈,对 tcpdump/wireshark/packetdrill 工具的相关分享文章);
关于这些超时的详细分析,见笔者以往编写的另一篇文章《深入源码和内核,一篇文章彻底理解数据库的各种超时参数-事务超时/查询超时/连接超时/登录超时/套接字超时》;
::: hljs-center
:::
3. HIVE JDBC 超时相关接口方法及其对各种超时参数的支持情况
Hive 作为大数据领域数据仓库的解决方案,提供了 SQL 供用户查询分析存储在分布式存储系统如 HDFS 中的海量数据,所以其在底层也必然实现了 JDBC 的相关接口方法;
HIVE3 通过 ACID 事务表进一步提供了对并发读写和记录级别增删改的支持,其对事务的支持进一步加强了,更像传统的 RDBMS 数据库了,但是 HIVE 毕竟不是传统的 RDBMS 数据库,其没有类似 binlog/redolog/undolog 的概念,在执行作业时仍会自动提交每个 statement 且不支持事务的 commit/rollback,同时其 SQL 查询性能一般也是秒或分钟级别而达不到数据库的毫秒级别,在并发支持上也达不到传统 RDBMS 的级别;
通过查看 hive jdbc 源码中的相关接口与方法可知,其对超时相关参数的支持并不完善,远不如 RDBMS,甚至 HiveDataSource 和 HiveConnection 在对 loginTimeout 的配置上也不一致,HiveDataSource不支持配置loginTimeout,而HiveConnection支持配置 loginTimeout;
HiveDataSource 不支持配置 LoginTimeout, 参见源码可见,HiveDataSource#setLoginTimeout 直接抛出了异常 throw new SQLException(“Method not supported”);
HiveConnection 不支持配置 NetworkTimeout,参见源码可见,HiveConnection#setNetworkTimeout 直接抛出异常:throw new SQLException(“Method not supported”);
HiveConnection 可以配置 LoginTimeout,参见源码可知,HiveConnection#setupLoginTimeout,获取的就是 DriverManager.getLoginTimeout());(注意 DriverManager 中该方法为静态方法而不是成员方法)
HiveStatement的不同版本在支持配置 queryTimeout 的情况上并不相同同:参加源码可知,早期 1.3.0 之前的版本,HiveStatement#setQueryTimeout 会直接抛出异常 throw new SQLException(“Method not supported”),中期版本只可以配置 queryTimeout 为0即没有 timeout 机制,而2.1.0 之后的新版本则支持配置该 queryTimeout 参数,相关Jira 为 HIVE-10726 和 HIVE-4924;


- HIVE JDBC 超时相关的接口类与方法,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# JDBC API 相关接口类与方法
- java.sql.DriverManager#setLoginTimeout
- java.sql.Driver
- javax.sql.CommonDataSource#setLoginTimeout
- javax.sql.DataSource#setLoginTimeout
- javax.sql.XADataSource#setLoginTimeout
- javax.sql.ConnectionPoolDataSource#setLoginTimeout
- java.sql.Connection#setNetworkTimeout
- java.sql.Statement#setQueryTimeout
- javax.sql.RowSet#setQueryTimeout
# hive jdbc 相关接口类与方法
- org.apache.hive.jdbc.HiveDriver
- org.apache.hive.jdbc.HiveDataSource#setLoginTimeout: 直接抛出异常 throw new SQLException("Method not supported");
- org.apache.hive.jdbc.HiveConnection#setNetworkTimeout: 直接抛出异常:throw new SQLException("Method not supported");
- org.apache.hive.jdbc.HiveConnection#setupLoginTimeout:查看源码可知,获取的就是 DriverManager.getLoginTimeout());
- org.apache.hive.jdbc.HiveStatement#setQueryTimeout:不同版本实现不同,早期1.3.0之前的版本, 直接抛出异常 throw new SQLException("Method not supported"),中期版本只可以配置 queryTimeout 为0,2.1.0之后的新版本则支持配置该 queryTimeout 参数;
4. 从源码角度细致分析 HiveStatement 中的 thrift socket timeout 是如何赋值的
仔细查看并跟踪源码可以获悉,HiveStatement 中的 thrift socket timeout,其底层直接获取的是 HiveConnection 的 LoginTimeout;
也就是说,hive 会基于 loginTimeout 自动推导并配置 socketTimeout;
而 HiveConnection 的 LoginTimeout,目前所有版本的HIVE,都是通过静态方法 java.sql.DriverManager.getLoginTimeout 获取的静态变量loginTimeout,其默认值为0;


- 相关源码即调用链如下,大家可以自己分析下;
1
2org.apache.hive.jdbc.HiveConnection#createUnderlyingTransport =》org.apache.hive.service.auth.HiveAuthFactory#getSSLSocket(java.lang.String, int, int) =》org.apache.thrift.transport.TSSLTransportFactory#getClientSocket(java.lang.String, int, int) =》org.apache.thrift.transport.TSSLTransportFactory#createClient =》
java.net.Socket#setSoTimeout




5. 当应用程序直接创建和管理 HIVE JDBC 连接时,如何配置 HiveStatement 中的 thrift socket timeout
如上文分析所述,hive thrift socket time out,其底层直接获取的是 HiveConnection 的 LoginTimeout,也就是说,hive 会基于 loginTimeout 自动推导并配置 socketTimeout;
而 HiveConnection 的 LoginTimeout,目前所有版本的HIVE,都是通过静态方法 java.sql.DriverManager.getLoginTimeout 获取的静态变量loginTimeout,其默认值为0;
所以应用程序无论是使用线程池如 Hikari/Druid/DBCP 创建管理 HIVE JDBC 连接,还是直接创建管理 HIVE JDBC 连接,在本质上,都需要通过静态方法java.sql.DriverManager.setLoginTimeout(int seconds)配置静态变量 loginTimeout,从而间接配置配置 socketTimeout;
由于方法 java.sql.DriverManager.setLoginTimeout(int seconds) 和 java.sql.DriverManager.getLoginTimeout() 是静态方法而不是实例方法,所以同一 JVM 中,所有引用了静态类 DriverManager 中的静态变量 loginTimeout 的 JDBC 驱动,都可能会收到影响,具体某 JDBC 驱动是否受到影响,取决于其具体实现;
通过查看常见数据库驱动的源码可知,mysql/pg/oracle 驱动的 JDBC 连接,都没有引用该静态类 DriverManager 中的静态变量 loginTimeout以配置其 loginTimeout/connectTimeout/socketTimeout 等超时参数:
- mysql 可以通过 url 指定 connectTimeout/socketTimeout 超时参数,其单位是毫秒,如:jdbc:mysql://localhost:3306/ag_admin?useUnicode=true&characterEncoding=UTF8&connectTimeout=60000&socketTimeout=60000
- pg 也可以通过url 指定 指定 connectTimeout/socketTimeout 超时参数,不过其单位是秒:jdbc:postgresql://localhost/test?user=fred&password=secret&&connectTimeout=60&socketTimeout=60
- oracle 的 thin jdbc driver,不支持通过 URL 参数指定套接字的连接超时和读写超时,而是需要通过系统参数 oracle.net.CONNECT_TIMEOUT 和 oracle.jdbc.ReadTimeout 来分别指定,这两个参数的单位都是毫秒,默认值都是0,(读写超时参数,在 10.1.0.5 以下版本的驱动中是 oracle.net.READ_TIMEOUT,在 10.1.0.5 以上的版本中才是 oracle.jdbc.ReadTimeout),比如可以通过 OracleDatasource.setConnectionProperties(java.util.Properties prop) 指定,使用 DBCP 时可以通过 BasicDatasource.setConnectionProperties(java.util.Properties prop)或 BasicDatasource.addConnectionProperties(java.util.Properties prop)指定;
所以,如果应用程序直接创建和管理 HIVE JDBC 连接,只需要在 JVM 运行时会加载的某个类的成员方法或静态方法或静态代码段中,调用静态方法java.sql.DriverManager.setLoginTimeout(int seconds)配置静态变量 loginTimeout,即可间接配置 hive thrift socketTimeout;
事实上,有不少案例,并没有显示在创建 HIVE 连接的相关代码段中调用该静态方法,但由于 JVM 加载的其它类中有些地方隐式地调用了该静态方法,导致 hive thrift socketTimeout受到了影响;
6. 当应用程序通过数据库连接池创建和管理 HIVE JDBC 连接时,如何配置 HiveStatement 中的 thrift socket timeout
如上文分析所述,hive thrift socket time out,其底层直接获取的是 HiveConnection 的 LoginTimeout,也就是说,hive 会基于 loginTimeout 自动推导并配置 socketTimeout;
而 HiveConnection 的 LoginTimeout,目前所有版本的HIVE,都是通过静态方法 java.sql.DriverManager.getLoginTimeout 获取的静态变量loginTimeout,其默认值为0;
所以应用程序无论是使用线程池如 Hikari/Druid/DBCP 创建管理 HIVE JDBC 连接,还是直接创建管理 HIVE JDBC 连接,在本质上,都需要通过静态方法java.sql.DriverManager.setLoginTimeout(int seconds)配置静态变量 loginTimeout,从而间接配置配置 socketTimeout;
z查看数据库连接池 hikari 的源码可知,hikari 会基于用户配置的 connectionTimeout,自动推导然后调用上述方法配置 DriverManager 中的静态变量 LoginTimeout:当 connectionTimeout 配置为非0值时,LoginTimeout 的值即是 connectionTimeout 加上500毫秒,且其值最小为1;
Hikari 通过 DriverDataSource 创建 JDBC 连接时(该类是 hikari 创建 JDBC 连接的方法之一),配置 LoginTimeout 的方法,就是通过调用静态方法 java.sql.DriverManager.setLoginTimeout(int seconds) 和 java.sql.DriverManager.getLoginTimeout() 来设置和获取静态变量 LoginTimeout;
所以,如果应用程序使用线程池如 Hikari 来创建管理 HIVE JDBC 连接时:
- 用户可以在 JVM 运行时会加载的某个类的成员方法或静态方法或静态代码段中,调用静态方法java.sql.DriverManager.setLoginTimeout(int seconds) 配置静态变量 loginTimeout,进而间接配置 hive thrift socketTimeout;
- 用户可以显示配置 connectionTimeout 为非0值,此时 hikari 会基于用户配置的 connectionTimeout 自动推导然后调用静态方法 java.sql.DriverManager.setLoginTimeout(int seconds) 配置静态变量 loginTimeout,进而间接配置 hive thrift socketTimeout;
- 推荐使用后一种方法,即用户显示配置 connectionTimeout 为非0值,由 hikari 基于用户配置的 connectionTimeou自动推导并配置静态变量 loginTimeout,进而间接配置 hive thrift socketTimeout;
1 | public static HikariDataSource getHikariDataSource() throws Exception { |




7. 相关源码与参考连接
1 | # JDBC API 相关类与方法 |
本文转载于 michaelli














