很久没写blog,有很多值得写的地方。慢慢补。
Hadoop中有个常用的方法org.apache.hadoop.io.IOUtils.copyBytes(InputStream in, OutputStream out, int buffSize),用于将本地文件上传至hdfs。
入参数in和out用完后是否需要关闭呢?显而易见的是流用完后需要关闭,但是经验告诉我,每次写mapreduce,我从未关闭过,也未曾出过问题。
本以为是方法中替我关闭了它,直到我遇见了一个意外。
我在一段程序中调用copyBytes(),之后流依旧未关闭。我测试的时候使用Main()去调用程序,每次程序都能正常结束。
而我将程序入口改为一个Web端的点击。用于启动这个方法的时候,程序却永远无法输出到目标位置上。很是奇怪。
Web端的点击和Main()的区别在哪里?
Main()方法结束后,程序会自动释放,难道释放后程序才关闭了流?显然Web程序是个永不结束的长程序,不满足这个条件。
我最先想到的finalize(),我们知道这个方法不靠谱。但是我去检索hadoop源码,发现有些地方会重写这个方法,多少还都和流有点关系。
但是我在IOUtils中并没有找到finalize()方法,其它类的finalize()调用了流的关闭?依旧没有检索到,很头疼。
就在我快要穷尽所能之时,一个念头闪了过来,钩子!(应该是无意中看到了某处源码)
在org.apache.hadoop.fs.FileSystem中2442行有如下代码:
ShutdownHookManager.get().addShutdownHook(clientFinalizer, SHUTDOWN_HOOK_PRIORITY);
我们去看clientFinalizer这个类:
private class ClientFinalizer implements Runnable {
@Override
public synchronized void run() {
try {
closeAll(true);
} catch (IOException e) {
LOG.info("FileSystem.Cache.closeAll() threw an exception:\n" + e);
}
}
}
由于hdfs的out都是由FileSystem根据对应Path创建而来,跟踪源码,发现Close就是delete(Path)
我们有足够的理由相信,就是这些代码造成了我们的困惑。
Web端点击,FileSystem是一个长驻内存的类,所以除非容器Shutdown,否则该类不会被释放,也就是说HOOK也永远不会被执行。
而Main方法或者MapReduce的结束,则HOOK被调用起来,流也就被关闭。
对程序进行debug的时候,我手动FileSystem.CloseAll()会立即提交到HDFS,而不用等到程序运行结束。这也验证了我的猜想。
当然我们还有其它方法解决这个问题:
例如copyBytes(InputStream in, OutputStream out, int buffSize, boolean close) ,最后一个参数就是一个不错的扩展。