经常在hadoop集群里发现HDFS中有老多长度为0的part-000XX的文件,一看就知道是job的reduce产生的输出文件,但是由于job做partition时由于分桶函数不够优,或数据本身的偏移比较厉害,而且开发job的人又没有进行这种情况进行处理的经验,于是某些reduce上没有分到任何的数据,成了“空桶”,但是以hadoop目前的逻辑,不管该桶有没有数据,最终都会产生一个part-00XXX的输出文件。这其实对job本身没什么问题,反正要处理的数据都算出来了,只要长尾不是严重到令人无法忍受,也不会有人去在意。多一个空文件就多一个空文件了。对于应用程序的开发者,这的确不是什么大不了的事。但是,对namenode,这就是个灾难。namenode的在内存里缓存所有的文件的Inode信息,HDFS中文件越多,namenode的内存就越紧张,由此引来一大堆的namenode和HDFS的问题。这里先不提如何彻底解决namenode的内存问题(That’s gona be a long~ long ~story, 以后再谈),但至少,减少job中空输出文件,能够帮助namendoe降低其内存的消耗,至少能缓解namenode的内存紧张问题,集群大了,job 越多,这样做带来的收益和效果可能就越明显。
怎么做呢?在老0.21以前的版本里,这个可能得依赖一些外围的方法了,比如job完成后check一下,空输出文件删掉,或者通过一些外围工具将输出目录中的文件进行合并什么的。这样虽然能够达到目的,但是会增加hadoop应用程序开发人员的负担,也会增加集群管理员的负担,因为毕竟这是一件额外的工作。但是在0.21及以后的版本中,内置了一个新的东西,叫做LazyOutputFormat,这个东西其实很简单,就是对 所有的OutputFormat(如TextOutputFormat,SequenceFileOutputFormat等等)的一个wrapper,使用了LazyOutputFormat的job,在输出output的时候,会在第一条output的key value输出(调用output.collect (or Context.write).)之前延迟输出文件的创建(part-000XX),那么如果整个task都没有输出,那么输出文件就不会被创建;而如果有输出,则还是按照原本应该的outputFormat格式进行输出。而不像现在所有outputFormat的机制一样,task以开始运行不管有没有输出先把输出文件给创建了再说。这种延迟创建的方式有利于避免job创建过多的空输出文件而占据过多的namendoe内存。
其实这个东西就是个小工具,如果task有输出,那么没有任何影响,如果存在没有输出的task,就会有利于减少空文件数量,是个不错的工具,跟之前不同的是这个小工具集成到了job内部了。其代码也非常简单,一目了然:
public static void setOutputFormatClass(Job job,
Class<? extends OutputFormat> theClass) {
job.setOutputFormatClass(LazyOutputFormat.class);
job.getConfiguration().setClass(OUTPUT_FORMAT,
theClass, OutputFormat.class);
}
要使用LazyOutputFormat,非常简单,如果是mapreduce job,那么
import org.apache.hadoop.mapreduce.lib.output.LazyOutputFormat;
LazyOutputFormat.setOutputFormatClass(job, TextOutputFormat.class);
如果是streaming作业或者pipes作业,那就更简单了,直接在提交参数中加上 -lazyOutput就行了。
不过灰常可惜的是,这个功能目前只有0.21里能用,其他先前的版本如果想用的话,可能还需要对自己的hadoop代码进行一些hunk,不过看了下代码,其实也很容易,感兴趣的可以试一试。