最近在外网试运行了一个月以来,hive在hadoop平台中的一个主要问题是:
经常报此异常:
org.apache.hadoop.ipc.Server: IPC Server handler 495 on 8020 caught: java.nio.channels.ClosedChannelException
导致任务执行失败(每次任务失败,关闭的连接都是hive连接到hadoop的master机器上的hdfs端口,后来也观察过日志,发现namenode与datanode交互时,也会出现关闭连接的情况,但这不影响任务的执行)
之前有几篇blog都是于此有关联的,大家可以自己去看看。
目前我们这个应用每天的计算规模在2亿多的数据量,而且必须保证数据计算的正确性和可用性。
hive运行情况如下:
因为刚开始应用不多,hive和hadoop的NameNode在一台机器上面,另外调度器也没有去限制最大运行JOB个数,
job采用的是hive的并行模式(设置参数hive.exec.parallel),并且多脚本也是可以并行运行(也就是多个hive进程,目前采用的命令模式运行)。
当采用这种方式后,在多个job同时提交,并且hive与hadoop交互比较紧密的时候,就会出现以上这种异常。
通过官方的issue(https://issues.apache.org/jira/browse/MAPREDUCE-2450)来看,他也只是初步的分析了问题的原因,至于解决方案各有不同。
通过3个多礼拜的不断地的尝试和测试(在此过程中修过了3遍hive脚本,尝试了过很多参数的修改,各种调度器的使用,操作系统相关修改),大致摸索了点东西出来。
原来在hadoop的RPC机制中有个小问题,该问题与以下几个方面有关:
1、一定要同步好集群里所以节点的时间
2、参数ipc.server.read.threadpool.size设置,该参数配置在core-site.xml(该参数不在官方的文档里体现出来),简单介绍下该参数的作用:
默认情况下取值为1,也就是1个读线程操作,为了提高NameNode的读能力,可以将该参数设置更大。
但是当该参数设置更大的时候,将引起一个问题(关于hadoop里RPC的原理,这里不详细描述):
就是当一个连接要关闭的时候, 之前的请求处理来不及返回。这样就会在请求返回时抛出该连接已被关闭的异常,这种异常将影响到任务的正常运行(之前hive出现的相关问题),经过仔细的排查,也是由于这个原因导致(也有其他因素,跟ipc.ping.interval这个参数的设置也有关系)
解决方式:
第一:就是不要轻易去设置ipc.server.read.threadpool.size的值(但是一般随着集群的规模越来越大,还是需要设置的)
第二:为了提高hadoop的读能力,我修改RPC交互方式
第三:研究hive的调用机制,查找是否有什么可疑之处
第四:修改Client类里,捕获IOException异常,再重新创建一个连接
第五:在Server类里用handler类来关闭这个连接,而不是在读的时候进行关闭
以上几种方式,除了第二点外,其他都是要调整hadoop里的东东。
在这里我选择了第二种方式,处理如下:
我稍微在Server.java这个类做了点变动,代码如下:
publicvoid run() {
LOG.info("Starting SocketReader");
synchronized (this) {
while (running) {
SelectionKey key =null;
try {
int cons=readSelector.select();
if (LOG.isDebugEnabled())
LOG.debug(getName()+" readSelector="+cons);
while (adding) {
this.wait(1000);
}
this.wait(100);//读取时再暂停100毫秒 会出现之前的请求还没返回,连接被关闭的情况
Iterator
while (iter.hasNext()) {
key = iter.next();
if (LOG.isDebugEnabled())
LOG.debug("Iterator "+key);
iter.remove();
if (key.isValid()) {
if (key.isReadable()) {
if (LOG.isDebugEnabled())
LOG.debug("Key is readable---------------" +key.attachment());
doRead(key);
}
}
key =null;
}
} catch (InterruptedException e) {
if (running) { // unexpected -- log it
LOG.info(getName() + " caught: " +
StringUtils.stringifyException(e));
}
} catch (IOException ex) {
LOG.error("Error in Reader", ex);
}
}
}
}
注意以上代码红色字体部分,注意:有部分DEBUG日志,是我自己加的,大家不必介意。
目前我测试过第二种方式,没有出现过问题,不过通过两天的测试效果来看,感觉可以把暂停的时间还设置小点,(后面打算测试下50毫秒的情况)
测试下来发现,虽然不会出现sever端读到-1后主动关闭连接了,但是会出现server端读异常。
关于第一种方式,是系统默认的,目前我还没有尝试过(刚开始上线的时候就设置为5),但也会对这种方式进行测试,看看是否还报连接关闭的异常。
第三种方式也在进行中,主要是研究下hive为什么在没有接收到上一个请求的响应信息,就发出了一个中断连接的指令。
测试三天发现:设置100毫秒将很影响job的运行速度。现在已经改到50毫秒进行测试并且同步进行不设置读线程数的测试工作。
目前第一种方式也不行,而且报连接关闭异常更频繁。
第四种和第五种方式初步考虑后,感觉比较复杂,所以不采用此方式。
在正常运行了两天后,又再次出现了NIO的异常,所以同步时间后和修改相关参数还是不够稳定。
再次进行了三天的测试,发现最终的原因(在没修改任何源码的情况下)是:
服务器端收到count值为-1,所以服务器端会关闭这个连接。但客户端这时候会wait等待1分钟(默认设置值),在等待的过程中已经抛出了写异常(因为服务器端关闭了这个连接导致,也就抛出了java.nio.channels.ClosedChannelException),所以导致最终这个hive这个脚本任务终止。详细查看博客《Client与Server交互的错误信息》。
服务器端关闭连接的情况如下:
1、Listener当连接超时会主动关闭,但是关闭的原因是抛出OutOfMemoryError异常,会有以下操作:
关闭当前的连接closeCurrentConnection,强制清理超时连接cleanupConnections(true)
2、Responder类当一个Call超时时也会清理连接(默认机制是上次返回response时间+PURGE_INTERVAL,大致是15分钟)
3、在读操作(doRead)的时候,当count<0时,就主动关闭连接
4、在写操作(doAsyncWrite)的时候,如果出现异常也将关闭连接
5、在读操作时会有验证操作,如果验证失败也将主动关闭连接
6、在读操作时有sasl处理,如果验证失败也将主动关闭连接
客户端的关闭连接情况如下:
1、创建一个连接时出现了Throwable异常,将主动关闭连接
2、在读的时候,如果waitForWork返回false的话,将主动关闭连接
在这里就有个问题就是当客户端在写之前,就无法知道知道当前连接是否被关闭。
关注新动向博客《 NIO异常新动向2 》