Hadoop多文件输出:MultipleOutputFormat和MultipleOutputs深究 http://www.iteblog.com/archives/842  (一)

Hadoop多文件输出:MultipleOutputFormat和MultipleOutputs深究 http://www.iteblog.com/archives/848  (二)

直到目前,我们看到的所有Mapreduce作业都输出一组文件。但是,在一些场合下,经常要求我们将输出多组文件或者把一个数据集分为多个数据集更为方便;比如将一个log里面属于不同业务线的日志分开来输出,并交给相关的业务线
用过旧API的人应该知道,旧API中有 org.apache.hadoop.mapred.lib.MultipleOutputFormat和org.apache.hadoop.mapred.lib.MultipleOutputs,文档对MultipleOutputFormat的解释(MultipleOutputs 解释在后面)是:

  MultipleOutputFormat allowing to write the output data to different output files.

MultipleOutputFormat可以将相似的记录输出到相同的数据集。在写每条记录之前,MultipleOutputFormat将调用generateFileNameForKeyValue方法来确定需要写入的文件名。通常,我们都是继承MultipleTextOutputFormat类,来重新实现generateFileNameForKeyValue方法以返回每个输出键/值对的文件名。generateFileNameForKeyValue方法的默认实现如下:

返回默认的name,我们可以在自己的类中重写这个方法,来定义自己的输出路径,比如:

这样相同country的记录将会输出到同一目录下的name文件中。完整的例子如下:

将上面的程序打包成jar文件(具体怎么打包,就不说),并在Hadoop2.2.0上面运行(测试数据请在这里下载:http://pan.baidu.com/s/1td8xN):

运行完程序之后,可以去/home/wyp/out目录看下运行结果:

从上面的结果可以看出,所有country相同的结果都输出到同一个文件夹下面了。MultipleOutputFormat对完全控制文件名和目录名很方便。大家也看到了上面的程序是基于行的split,如果我们要基于列的split,MultipleOutputFormat就无能为力了。这时MultipleOutputs就用上场了。MultipleOutputs在很早的版本就存在,那么我们先看看官方文档是怎么解释MultipleOutputs的:

  MultipleOutputs creates multiple OutputCollectors. Each OutputCollector can have its own OutputFormat and types for the key/value pair. Your MapReduce program will decide what to output to each OutputCollector.

与MultipleOutputFormat类不一样的是,MultipleOutputs可以为不同的输出产生不同类型,到这里所说的MultipleOutputs类还是旧版本的功能,后面会提到新版本类库的强化版MultipleOutputs类,下面我们来用旧版本的MultipleOutputs类说明它是如何为不同的输出产生不同类型,MultipleOutputs类不是要求给每条记录请求文件名,而是创建多个OutputCollectors。每个OutputCollector可以有自己的OutputFormat和键值对类型,Mapreduce程序将决定如何向每个OutputCollector输出数据(看看上面的英文文档),说的你很晕吧,来看看代码吧!下面的代码将地理相关的信息存储在geo开头的文件中;而将时间相关的信息存储在chrono开头的文件中,具体的代码如下:

上面程序来源《Hadoop in action》。同样将上面的程序打包成jar文件(具体怎么打包,也不说了),并在Hadoop2.2.0上面运行(测试数据请在这里下载:http://pan.baidu.com/s/1td8xN):

运行完程序之后,可以去/home/wyp/out5目录看下运行结果:

大家可以看到在输出的文件中还存在以part开头的文件,但是里面没有信息,这是程序默认的输出文件,输出的收集器的名称是不能为part的,这是因为它已经被使用为默认的值了。
从上面的程序可以看出,旧版本的MultipleOutputs可以将文件基于列来进行分割,但是如果你想进行基于行的分割,这就要求你自己去实现代码了,恨不方便,针对这个问题,新版本的MultipleOutputs已经将旧版本的MultipleOutputs和MultipleOutputFormat的功能合并了,也就是说新版本的MultipleOutputs类具有旧版本的MultipleOutputs功能和MultipleOutputFormat功能;同时,在新版本的类库中已经不存在MultipleOutputFormat类了,因为MultipleOutputs都有它的功能了,还要她干嘛呢?看看官方文档是怎么说的:

  The MultipleOutputs class simplifies writing output data to multiple outputs
Case one: writing to additional outputs other than the job default output. Each additional output, or named output, may be configured with its own OutputFormat, with its own key class and with its own value class.
Case two: to write data to different files provided by user

再看看下面摘自Hadoop:The.Definitive.Guide(3rd,Early.Release)P251,它是怎么说的:

  In the old MapReduce API there are two classes for producing multiple outputs: MultipleOutputFormat and MultipleOutputs. In a nutshell, MultipleOutputs is more fully featured, but MultipleOutputFormat has more control over the output directory structure and file naming. MultipleOutputs in the new API combines the best features of the two multiple output classes in the old API.

也就是说MultipleOutputs合并了旧版本的MultipleOutputs功能和MultipleOutputFormat功能,新api都是用mapreduce包。好了,刚刚也说了新版本的MultipleOutputs有了旧版本的MultipleOutputFormat功能,那么我该怎么在新版的MultipleOutputs中实现旧版本MultipleOutputFormat的多文件输出呢?也就是上面第一个程序。看看下面的代码吧。

上面的程序通过setup(Context context)来初始化MultipleOutputs对象,并在mapper函数中调用MultipleOutputs的write方法将数据输出到根据value的值不同的文件夹中(通过调用generateFileName函数来处理)。MultipleOutputs类有多个不同版本的write方法,它们的函数原型如下:

我们可以根据不同的需求调用不同的write方法。
好了,大家来看看上面程序运行的结果吧:

测试数据还是上面给的地址。看下/home/wyp/out11文件中有什么吧:

现在输出的结果和用旧版本的MultipleOutputFormat输出的结果很类似了;但是在输出的结果中还有两个以part开头的文件夹,而且里面什么都没有,这是怎么回事?和第二个测试程序一样,这也是程序默认的输出文件名。那么我们可以在程序输出的结果中不输出两个文件夹吗?当然可以了,呵呵。如何实现呢?其实很简单,在上面的代码的main函数中加入以下一行代码:

如果加入了上面的一行代码,请同时注释掉你代码中下面一行代码(如果有)

再去看下输出结果吧:

结果完全和例子一一样。