CDH5.12.0-HiveServer2-java.net.SocketTimeoutException: Read timed out

解决HiveServer2 SocketTimeoutException问题,通过调整参数和升级CDH版本,优化HiveServer2与MetaStore通信,减少作业失败率。

一. 问题背景

基于CDH构建离线数仓,在通过JDBC向HiveServer2提交作业时出现java.net.SocketTimeoutException: Read timed out 错误,导致大批量的作业失败,不能按时产生数据,已严重影响到业务运行。

二. 集群环境

  1. CDH-5.12.0
  2. Hive-1.1.0

三. 错误日志

提交作业客户端报的错误日志如下:

java.sql.SQLException: org.apache.thrift.transport.TTransportException: java.net.SocketTimeoutException: Read timed out
	at org.apache.hive.jdbc.HiveStatement.execute(HiveStatement.java:263)
	at org.apache.hive.jdbc.HiveStatement.executeUpdate(HiveStatement.java:407)
	at com.dtwave.dipper.dubhe.node.executor.runner.impl.Hive2TaskRunner.doRun(Hive2TaskRunner.java:229)
	at com.dtwave.dipper.dubhe.node.executor.runner.BasicTaskRunner.execute(BasicTaskRunner.java:88)
	at com.dtwave.dipper.dubhe.node.executor.TaskExecutor.run(TaskExecutor.java:32)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.thrift.transport.TTransportException: java.net.SocketTimeoutException: Read timed out
	at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:129)
	at org.apache.thrift.transport.TTransport.readAll(TTransport.java:86)
	at org.apache.thrift.transport.TSaslTransport.readLength(TSaslTransport.java:376)
	at org.apache.thrift.transport.TSaslTransport.readFrame(TSaslTransport.java:453)
	at org.apache.thrift.transport.TSaslTransport.read(TSaslTransport.java:435)
	at org.apache.thrift.transport.TSaslClientTransport.read(TSaslClientTransport.java:37)
	at org.apache.thrift.transport.TTransport.readAll(TTransport.java:86)
	at org.apache.thrift.protocol.TBinaryProtocol.readAll(TBinaryProtocol.java:429)
	at org.apache.thrift.protocol.TBinaryProtocol.readI32(TBinaryProtocol.java:318)
	at org.apache.thrift.protocol.TBinaryProtocol.readMessageBegin(TBinaryProtocol.java:219)
	at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:77)
	at org.apache.hive.service.cli.thrift.TCLIService$Client.recv_ExecuteStatement(TCLIService.java:225)
	at org.apache.hive.service.cli.thrift.TCLIService$Client.ExecuteStatement(TCLIService.java:212)
	at sun.reflect.GeneratedMethodAccessor104.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.apache.hive.jdbc.HiveConnection$SynchronizedHandler.invoke(HiveConnection.java:1309)
	at com.sun.proxy.$Proxy86.ExecuteStatement(Unknown Source)
	at org.apache.hive.jdbc.HiveStatement.execute(HiveStatement.java:254)
	... 9 more
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
	at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)
	at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
	at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:127)
	... 27 more

但是HiveServer2及MetaStore服务端的日志里并没有任何错误提示,导致定位不到出错原因。然后就开始了漫长的解决问题之路,这一走就是近20天。

四. 错误周期

  1. 出错后,重跑作业时95%概率不会再出现,但有时还是报相同错误,多尝试几次后,错误又没有了。

  2. 重启HiveServer2后,大概7-9天内不再出现,但超过7-9天以后,又开始出错。

    结论: 此错误具有偶发性并带有一定周期的,我们技术同学观察此错误前后长达2个多月。

五. 第一次尝试解决

时间范围: 2018年11月26日-2018年11月30日

由于是socket timeout问题,因此把目光放在服务端上,开始排查HiveServer2的问题。由于HiveServer2日志里没有任何错误,因此开始摸索。

  1. 在客户端telnet HiveServer2进程,正常。
  2. 用jmap 查看堆内存信息,正常。
  3. 用jstat 查看进程GC信息,正常。
  4. 用netstat 查看连接情况,正常。

上述方法尝试完成后,一切正常,开始查看Hive源代码,几个小时过去了,还是没有发现任何异常,开始陷入思考中…

时间来到第二天,早上11点13分运行作业又出现socket timeout 问题,碰巧在CDH管理界面上看到Hive MetaStore的异常情况, 如下图:
在这里插入图片描述
在这里插入图片描述

因为HiveServer2执行任务的时候会调用MetaStore获取元数据信息,执行作业的时候刚好MetaStore挂了,感觉找到问题原因了,内心里有点小欢喜。于是乎开始把阵地转移到MetaStore中…

经过排查,发现MetaStore内存只有2G,最近还发生2次Full GC。而且这个时候在网上找到相关博客报表使用hive数据源报java.net.SocketTimeoutException: Read timed out
java.net.SocketTimeoutException: Read timed out
,前者内容如下:
在这里插入图片描述
认定原因是: HiveServer2和MetaStore通讯时,因为MetaStore发生Full GC或者hive.metastor.client.socket.timeout设置时间过短(配置的是300),于是开始修复MetaStore。
由于客户的MetaStore是单点,每天HiveServer2、Impala、Spark作业都会去连接,而且部署MetaStore的机器上还有很多其他Hadoop相关服务。于是给客户三条修复建议:

  1. 增加hive.metastore.client.socket.timeout的值,目前是300。 建议改为1000
  2. 增加hive metastore的最大内存。
  3. 把hive metastore 部署成多个,避免现在的单点故障。

经过上述三个操作后,HiveServer2和MetaStore都被重启了。

上述修复完成后,就开始了数绵羊的日子:
1天、2天、3天…都没有问题。

就这样到了12月10号,终于熬过了10天(以前是7-9天),以为问题彻底解决了,心里暗暗舒了一口气…

但是12月11号早上,收到客户运维同学发来的消息:
在这里插入图片描述

问题复现,第一次解决尝试失败,又开始第二次尝试解决…

六. 第二次尝试解决

时间范围: 2018年12月12日-2018年12月14日

由于错误周期性复现,这个时候寻求运维同学的帮助,两个人在CDH管理界面上苦苦查看HiveServer2的每个指标,终于功夫不负有心人,找到了一个异常的、周期性的指标!
在这里插入图片描述

每间隔7天多,发现JVM daemon线程都出现一次波峰,然后值降到0。和客户的运维同学确认后,这是由于在这几天进行了重启操作。最终得到分析结果:每次重启后,HiveServer2的deamon thread 就一直逐天增加, 2k、4k…一直到10k多,最终运行作业就会出现socket timeout问题。

于是第一时间用jstack开始查看HiveServer2内的线程信息:
在这里插入图片描述
统计后居然发现有6481个线程处于BLOCKED状态,大惊!!!
分析:
HiveServer的内存配置的是4G,假设每个线程的栈的大小是1MB,理论上最多能创建4096个线程。因此怀疑是进程里线程数过多,导致服务越来越慢,对一些作业响应出现问题,导致客户端出现socket timeout问题。

同时开启Google模式,找到CDH官网一篇文章HiveServer2-has-more-than-10K-waiting-daemon-threads-named。文章内容的核心点如下面两张图:
在这里插入图片描述
在这里插入图片描述
线程增长曲线、堆栈调用关系 跟我们遇到的错误一模一样,此时理论上定位到了错误原因。

PS: 我在客户CDH及公司内部CDH的服务器上jstack的结果都是没有线程名(自己本地的Apache社区是有线程名的),只有线程ID,例如: Thread 3667。博客中的截图是有线程名的,例如: “Get-Input-Paths-1” 。因此这一点不是100%和博客吻合,看此博客的哪位高手如有其它方法,请留言告知下,多谢!

在博客下面,有人提到这是HiveServer2的一个BUG: HIVE-16949
在这里插入图片描述

打开 https://issues.apache.org/jira/browse/HIVE-16949,内容如下:
在这里插入图片描述

这个BUG是因为作业完成后没有关闭线程池,调用pool.shutdown()操作。此BUG是在HIVE3.0.0才修复的,然后我开始下载hive-1.1.0-cdh5.12.0源代码,计划把HIVE-16949.1.patch补到hive源代码中,然后重新编译打包。
但是…
在这里插入图片描述
没成功,我们的版本是1.1.0和3.0修复的这个补丁差异太大,尝试多次后,熬到凌晨2点多发现代码还是没法合并进来(哪位高手如有方法,请留言告知下,多谢!)。于是开始尝试其他方法…

隔天,幸好,在CDH官网上找到此BUG的描述,如下:
在这里插入图片描述

这个BUG影响的CDH版本是5.11.0, 5.11.1, 5.11.2, 5.12.0, 5.12.1,很不幸我们刚好是5.12.0。文中给出两种办法:

  1. 升级CDH,5.12.2、5.13.0 and higher
  2. 修改参数 hive.exec.input.listing.max.threads=1

方法二经过测试后,线程数确实不增加,但是任务的速度减慢很多,原来2分钟的作业改后需要5分钟,因此很可能导致晚上的数仓作业跑不完。因此后续计划采用方法一。

七. 验证

目前暂时采用的是上述方法2,今晚会跑大量作业,然后等明天早上观察HiveServer2的Thread数目。

期待明天结果…
同时还要验证7-9天后不再会周期性出现socked timeout问题。

八. 总结

  1. 对于这种周期的偶发性错误排查一定要彻底、要全面。
    例如在第一次尝试解决中,用netstat、jmap、jstat分别查看了连接数、堆内存、GC等信息,唯独忘记了用jstack查看,结果问题就出现在了大量线程处于BLOCKED中。
  2. 商业公司不建议用Apache 社区原生的,因为监控、运维基本没有,对于这种没有报错日志的BUG基本无处下手。

九. 致谢

  1. 感谢客户积极配合我们解决错误,以及对数澜的耐心;
  2. 感谢运维同学安邦的帮忙;
  3. 感谢开发同学项栋、帅豪 2个多月熬夜人工修复客户的作业。
  4. 感谢PM、商务积极的跟客户沟通、协调,给技术缓冲解决问题的时间。

感想:
数栖平台目前已支持HDP、CDH、TDH、FusionInsight、腾讯EMR,但未来肯定还会遇到底层平台各种问题,导致客户业务出错。过程中无论客户对数澜-数栖平台的稳定性产生怀疑、甚至是不信任,我们都不会做任何争辩,只会踏踏实实的用行动去协助、帮助客户解决问题,一起打造起企业的数据中台!

<think>我们正在解决的是 `java.net.URISyntaxException: Illegal character in path` 问题。根据引用内容,这类异常通常发生在尝试创建包含非法字符的URI时。URI的路径部分必须遵循特定的编码规则,不能包含空格、中文字符等未编码的特殊字符。 ### 问题分析 在Java中,`java.net.URI` 类对URI的格式有严格的要求。当路径中包含非法字符(如空格、中文、花括号等)而未进行编码时,就会抛出 `URISyntaxException`。 引用中的例子: 1. 引用[1]:`Relative path in absolute URI: ${system:java.io.tmpdir%7D/$%7Bsystem:user.name%7D`,这里出现了`%7D`(即`}`的编码)和`$`,但整个字符串被当作了绝对URI的一部分,而其中可能包含非法字符。 2. 引用[2]:`rsrc:hbase-common-1.0.0-cdh5.5.0.jar`,使用自定义协议`rsrc`,但路径中可能包含非法字符。 3. 引用[3]:通过修改Hive配置文件,将路径中的变量替换为实际路径,避免了非法字符。 ### 解决方案 #### 1. 识别非法字符 URI中允许的字符由RFC 3986规定。在路径中,以下字符必须进行百分号编码(Percent-Encoding): - 空格(必须编码为`%20`) - 非ASCII字符(如中文字符) - 保留字符:`! * ' ( ) ; : @ & = + $ , / ? # [ ]` - 其他非安全字符,如`{ } | \ ^ [ ] `等。 #### 2. 编码非法字符 使用`java.net.URI`类创建URI时,它会自动检查字符的合法性。但有时我们需要手动编码,特别是当路径中包含变量时。 **方法一:使用`URLEncoder`(注意:适用于URL的查询部分,不适用于整个路径)** 注意:`URLEncoder`会将空格编码为`+`,而在URI路径中应使用`%20`。因此,在编码路径时,应使用以下方法: **方法二:使用URI类的多参数构造方法** 最安全的方式是使用`URI`类的构造方法,它允许你分别提供方案(scheme)、主机(host)、路径(path)和片段(fragment)等部分,并自动进行编码。 ```java URI uri = new URI("http", "example.com", "/path with spaces", null); ``` **方法三:手动编码路径部分** 如果必须处理整个路径字符串,可以先将路径分割成多个部分,对每一部分进行编码,然后拼接。但更简单的方法是使用`URI.create`方法,但注意它要求整个字符串是合法的URI字符串(即已经编码)。 #### 3. 修复示例 假设我们有一个包含空格和中文字符的路径: ```java String path = "/测试目录/a b c"; ``` **错误做法:** ```java URI uri = new URI("file:" + path); // 会抛出URISyntaxException ``` **正确做法:** ```java // 方法1:使用多参数构造方法(适用于file协议) URI uri = new URI("file", null, path, null); ``` 或者,如果路径是相对路径,可以使用: ```java URI baseUri = new URI("file:/base/dir/"); URI resolved = baseUri.resolve(new URI(null, null, path, null)); ``` #### 4. 处理变量路径(如引用[3]的情况) 在引用[3]中,Hive配置中使用了`${system:user.name}`这样的变量。在构建URI之前,需要将这些变量替换为实际值,并确保替换后的路径不包含非法字符。 例如: ```java String user = System.getProperty("user.name"); // 获取用户名,可能包含空格 String path = "/data/" + user; // 如果用户名有空格,则path包含空格 // 构建URI时,需要将空格编码 URI uri = new URI("file", null, path, null); ``` ### 实际案例 根据引用[3],他们通过修改配置文件,将路径中的变量替换为实际路径,从而避免了非法字符问题。但更通用的做法是在代码中处理: ```java // 原始配置字符串,可能包含变量 String rawPath = "/data/${system:user.name}"; // 替换变量 String resolvedPath = rawPath.replace("${system:user.name}", System.getProperty("user.name")); // 然后创建URI URI uri = new URI("file", null, resolvedPath, null); ``` ### 总结步骤 1. **识别路径中的非法字符**:检查路径中是否包含空格、中文、特殊符号等。 2. **使用正确的构造方法**:使用`URI`类的多参数构造方法(指定scheme, host, path等)来创建URI,它会自动对非法字符进行编码。 3. **避免手动拼接URI字符串**:不要直接拼接字符串来创建URI,特别是当字符串包含变量时。 4. **预处理变量**:在构建URI之前,将路径中的变量替换为实际值,并确保这些值已经正确编码(或者让URI构造方法处理)。 5. **使用工具方法**:如果需要单独编码路径,可以使用`java.net.URLEncoder`,但注意替换空格为`%20`(因为`URLEncoder`使用`+`)。 ### 代码示例 ```java import java.net.URI; import java.net.URISyntaxException; public class URIFixExample { public static void main(String[] args) { try { String path = "/测试目录/a b c"; // 包含中文和空格 // 正确创建URI URI uri = new URI("file", null, path, null); System.out.println("Encoded URI: " + uri); // 输出:file:/%E6%B5%8B%E8%AF%95%E7%9B%AE%E5%BD%95/a%20b%20c // 如果必须使用字符串,确保字符串已经编码(不推荐,容易出错) // 但有时我们可能从外部获取一个已经编码的字符串,此时可以直接使用URI.create String encodedPath = "/%E6%B5%8B%E8%AF%95%E7%9B%AE%E5%BD%95/a%20b%20c"; URI uri2 = URI.create("file:" + encodedPath); // 注意:这里要求整个字符串合法 System.out.println("URI from encoded string: " + uri2); } catch (URISyntaxException e) { e.printStackTrace(); } } } ``` ### 相关问题 1. Java中URI和URL有什么区别? 2. 如何在Java中对URL路径进行编码? 3. 为什么在URI中空格需要被编码为%20? 4. 如何处理URI中的中文字符? [^1]: 参考引用[1]中的异常信息 [^2]: 参考引用[2]中的异常原因 [^3]: 参考引用[3]中的解决方案</think>### Java中`URISyntaxException: Illegal character in path`问题分析与解决方案 当Java程序尝试创建包含非法字符的URI时,会抛出`java.net.URISyntaxException`异常。这通常发生在路径中包含未编码的特殊字符(如空格、中文、花括号等)时。根据您提供的引用信息,以下是完整解决方案: --- ### 问题根本原因 1. **非法字符存在路径中**: - URI规范要求路径中的特殊字符必须进行百分比编码(如空格→`%20`) - 常见非法字符:空格、中文、`{ } | \ ^ [ ]`等未编码字符 - 示例错误:`file:${system:java.io.tmpdir}/$ {system:user.name}`(包含未编码的花括号和变量) 2. **变量未正确解析**: - 配置文件中使用`${variable}`格式,但运行时未替换为实际值 - 如引用[3]中`${system:user.name}`未解析,导致路径无效 3. **相对路径与绝对路径混淆**: - 错误提示`Relative path in absolute URI`表明尝试在绝对URI中使用相对路径语法 --- ### 解决方案(逐步操作) #### 方法1:手动编码特殊字符(推荐) 使用`URLEncoder`对路径进行编码: ```java import java.net.URLEncoder; import java.net.URI; String rawPath = "/data/测试目录/a b c"; // 包含中文和空格 String encodedPath = URLEncoder.encode(rawPath, StandardCharsets.UTF_8) .replace("+", "%20") // 空格特殊处理 .replace("%2F", "/"); // 保留路径分隔符 URI uri = new URI("file:" + encodedPath); // 正确格式: file:/data/%E6%B5%8B%E8%AF%95%E7%9B%AE%E5%BD%95/a%20b%20c ``` #### 方法2:使用URI构造器(避免手动拼接) ```java URI uri = new URI( "file", // scheme null, // authority "/data/测试目录/a b c", // 路径(自动编码) null, // query null // fragment ); ``` #### 方法3:修复配置文件(针对引用[3]案例) 在`hive-site.xml`中: ```xml <!-- 错误示例:包含未解析变量 --> <property> <name>hive.exec.local.scratchdir</name> <value>${system:java.io.tmpdir}/${system:user.name}</value> </property> <!-- 正确做法:预定义有效路径 --> <property> <name>hive.exec.local.scratchdir</name> <value>/Users/yourname/hive_temp/${system:user.name}</value> <!-- 确保父目录存在 --> </property> ``` #### 方法4:变量预处理 当路径包含环境变量时: ```java String tmpDir = System.getProperty("java.io.tmpdir"); // 获取实际值 String user = System.getProperty("user.name"); // 先替换变量再编码 String path = "/" + tmpDir + "/" + user; URI uri = new URI("file", null, path, null, null); ``` --- ### 关键预防措施 1. **编码规则**: - 空格 → `%20` - 中文 → `%E4%B8%AD`(UTF-8编码值) - `{ }` → `%7B%7D` - 保留字符如`/ ? : @ & = + $ ,` **不编码** 2. **路径规范**: - 绝对路径以`/`开头(Linux/Mac)或盘符开头(Windows) - 避免混合使用相对路径(`../`)和绝对路径 3. **调试技巧**: ```java try { new URI(yourPath); } catch (URISyntaxException e) { System.out.println("非法位置: " + e.getIndex()); // 显示错误字符位置 } ``` --- ### 相关问题 1. Java中URL编码与URI编码有什么区别? 2. 如何在Spring Boot中安全处理文件路径? 3. 为什么Windows和Linux的文件路径编码方式不同? 4. 如何批量处理路径中的中文编码问题? > 通过正确处理特殊字符编码和变量解析,可彻底解决`URISyntaxException`问题。实际应用中建议优先使用`URI`构造器而非字符串拼接[^1][^2][^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值