问题研究
项目中需要利用DataImportHandler从hive中sync数据到solr。发现有时候hive sql已经执行完几个小时了,sync任务还没有完成,貌似哪里卡住了。重启solr后重新sync又成功了,所以之前虽然遇到很多次这个问题也没有去深究。今天又出现了同样的问题,决定花点时间研究下到底是什么原因造成的。
利用jstack dump了几遍线程栈,发现很多个线程都卡在同一个地方,at java.net.SocketInputStream.socketRead0(Native Method),显然这些进程都在等待jdbc从hive中读取数据。初步判断可能是网络原因导致网络中断,TcpIp没有侦测到连接已经失败,我们在data-config.xml中配置hive数据源的时候又没有设置超时时间,导致线程一直傻傻等待接收数据sync任务一直无法完成,也没有任何异常抛出。之所以问题出现的这么频繁应该是因为我们的hive服务器设在美国,而solr服务器在国内。看来加上超时时间是很有必要的,难怪很多文章都提到为了不让比较慢的客户端拖垮服务器程序的性能必须加上超时时间。这些无限等待的线程都是在做无用功啊,又浪费服务器资源,设置超时时间后就会在超时时间到达时抛出timeout异常,退出线程。
"Thread-32" prio= tid=0x00007f077c052800 nid=0x3b04 runnable [0x00007f06fec7d000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:)
at java.net.SocketInputStream.read(SocketInputStream.java:)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:)
at java.io.BufferedInputStream.read(BufferedInputStream.java:)
- locked <0x00000000d8ccf078> (a java.io.BufferedInputStream)
at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:)
at org.apache.thrift.transport.TTransport.readAll(TTransport.java:)
at org.apache.thrift.transport.TSaslTransport.readLength(TSaslTransport.java:)
at org.apache.thrift.transport.TSaslTransport.readFrame(TSaslTransport.java:)
at org.apache.thrift.transport.TSaslTransport.read(TSaslTransport.java:)
at org.apache.thrift.transport.TSaslClientTransport.read(TSaslClientTransport.java:)
at org.apache.thrift.transport.TTransport.readAll(TTransport.java:)
at org.apache.thrift.protocol.TBinaryProtocol.readAll(TBinaryProtocol.java:)
at org.apache.thrift.protocol.TBinaryProtocol.readI32(TBinaryProtocol.java:)
at org.apache.thrift.protocol.TBinaryProtocol.readMessageBegin(TBinaryProtocol.java:)
at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:)
at org.apache.hive.service.cli.thrift.TCLIService$Client.recv_ExecuteStatement(TCLIService.java:)
at org.apache.hive.service.cli.thrift.TCLIService$Client.ExecuteStatement(TCLIService.java:)
at org.apache.hive.jdbc.HiveStatement.execute(HiveStatement.java:)
at org.apache.solr.handler.dataimport.JdbcDataSource$ResultSetIterator.<init>(JdbcDataSource.java:)
at org.apache.solr.handler.dataimport.JdbcDataSource.getData(JdbcDataSource.java:)
at org.apache.solr.handler.dataimport.JdbcDataSource.getData(JdbcDataSource.java:)
at org.apache.solr.handler.dataimport.SqlEntityProcessor.initQuery(SqlEntityProcessor.java:)
at org.apache.solr.handler.dataimport.SqlEntityProcessor.nextRow(SqlEntityProcessor.java:)
at org.apache.solr.handler.dataimport.EntityProcessorWrapper.nextRow(EntityProcessorWrapper.java:)
at org.apache.solr.handler.dataimport.DocBuilder.buildDocument(DocBuilder.java:)
at org.apache.solr.handler.dataimport.DocBuilder.buildDocument(DocBuilder.java:)
at org.apache.solr.handler.dataimport.DocBuilder.doDelta(DocBuilder.java:)
at org.apache.solr.handler.dataimport.DocBuilder.execute(DocBuilder.java:)
at org.apache.solr.handler.dataimport.DataImporter.doDeltaImport(DataImporter.java:)
at org.apache.solr.handler.dataimport.DataImporter.runCmd(DataImporter.java:)
扩展阅读
遇到这个问题以后想了解下jdbc的超时机制,所以google了一把,看到两篇文档对我很有帮助,文档链接在文章的最后。一篇是关于jdbc超时机制的,另一篇是关于socket的keepalive。
总结一下jdbc相关的timeout有以下几种:
Transaction Timeout,用来限制一个事务的执行时间。
Statement Timeout,限制一条查询的执行时间,设置方法:java.sql.Statement.setQueryTimeout(int timeout)。
Socket Timeout,又包含connection timeout和read/write timeout,限制建立连接的时间和等待读取或者等待写数据的时间。
OS level socket Timeout,这个其实是Linux系统环境下的Socket的KeepAlive机制。java的Socket类里有个setKeepAlive(boolean on)方法可能就是用来设置KeepAlive机制是否生效。
KeepAlive的作用:
之前一直不明白为什么很多系统都要自己实现一个心跳机制,比如微信,导致信令风暴啊什么的很多问题。看完KeepAlive的文章后才有这样的感觉,原来如此啊!
1,检查连接是否活着
KeepAlive可以侦测到一些原因造成的失效的socket连接,比如内核错误或者连接的进程强制退出,还比如虽然连接的机器和进程都正常,但是网络已经出现故障。这些情况下,TCP是不知道连接已经失效的。
考虑下面这种情况,A发送SYN给B发起连接,B回复一个ACK/SYN,A又发送ACK到B,完成TCP的三次握手,连接建立成功。这个时候如果B突然崩溃,没有发送任何消息给A告知其已经崩溃,A就会一直以为B还活着。如果A正在等待B发送过来的数据,那么就会一直在那傻等着,而B即使重启成功它也不知道之前和A有连接,所以A永远等不到B的数据。如果有KeepAlive的话,它就会定时的发送数据给B,B收到数据后找不到对应的连接就会回复一个reset信息,那么A收到reset信息以后就知道连接已经失效。
_____ _____
| | | |
| A | | B |
|_____| |_____|
^ ^
|--->--->--->-------------- SYN -------------->--->--->---|
|---<---<---<------------ SYN/ACK ------------<---<---<---|
|--->--->--->-------------- ACK -------------->--->--->---|
| |
| system crash ---> X
|
| system restart ---> ^
| |
|--->--->--->-------------- PSH -------------->--->--->---|
|---<---<---<-------------- RST --------------<---<---<---|
| | 2,防止连接长时间无数据传输而被断开
比如下图的情况,NAT会监控A和B之间的所有连接,如果连接长时间处于闲置状态就会被断开。KeepAlive定期的发送数据可以促使连接不会被断开。
_____ _____ _____
| | | | | |
| A | | NAT | | B |
|_____| |_____| |_____|
^ ^ ^
|--->--->--->---|----------- SYN ------------->--->--->---|
|---<---<---<---|--------- SYN/ACK -----------<---<---<---|
|--->--->--->---|----------- ACK ------------->--->--->---|
| | |
| | <--- connection deleted from table |
| | |
|--->- PSH ->---| <--- invalid connection |
| | |
下午的时候还做了个小测试,在一台机器上运行一个SocketServer的程序,另外一台运行Socket程序去连接SocketServer,这个时候我并没有设置Socket的任何timeout。然后断开网线,发现Socket抛出ReadTimeout异常。当时觉得很奇怪,还以为是底层有KeepAlive呢。后来想想可能是操作系统侦测到网线被拔掉了吧O(∩_∩)O~。
参考文献
http://www.cubrid.org/blog/dev-platform/understanding-jdbc-internals-and-timeout-configuration/