工作中有人问 MultiOutputs 实现为啥在指定reduce数为1时 结果文件数依然是 好多个?这其实由其实现逻辑决定的。
在MR中 一般job都可以通过map reduce 默认的OutputCollector 实现 写入作业初始化时指定格式的输出中,只能一个文件格式。当需要将结果分门别类区分或者使用不同格式存储在多个文件结果中时 就需要 MultiOutputs了。
MultiOutputs 添加输出得api
/**
* Adds a named output for the job.
*
*
* @param conf job conf to add the named output
* @param namedOutput named output name, it has to be a word, letters
* and numbers only, cannot be the word 'part' as
* that is reserved for the
* default output.
* @param outputFormatClass OutputFormat class.
* @param keyClass key class
* @param valueClass value class
*/
public static void addNamedOutput(JobConf conf, String namedOutput,
Class extends OutputFormat> outputFormatClass,
Class> keyClass, Class> valueClass) {
addNamedOutput(conf, namedOutput, false, outputFormatClass, keyClass,
valueClass);
}
/**
* Adds a multi named output for the job.
*
*
* @param conf job conf to add the named output
* @param namedOutput named output name, it has to be a word, letters
* and numbers only, cannot be the word 'part' as
* that is reserved for the
* default output.
* @param outputFormatClass OutputFormat class.
* @param keyClass key class
* @param valueClass value class
*/
public static void addMultiNamedOutput(JobConf conf, String namedOutput,
Class extends OutputFormat> outputFormatClass,
Class> keyClass, Class> valueClass) {
addNamedOutput(conf, namedOutput, true, outputFormatClass, keyClass,
valueClass);
}
在MultiOutputs 实现中 通过getCollector方法实例化一个OutputCollector,实现输出
public OutputCollector getCollector(String namedOutput, String multiName,
Reporter reporter)
throws IOException {
checkNamedOutputName(namedOutput);
if (!namedOutputs.contains(namedOutput)) {
throw new IllegalArgumentException("Undefined named output '" +
namedOutput + "'");
}
boolean multi = isMultiNamedOutput(conf, namedOutput);
if (!multi && multiName != null) {
throw new IllegalArgumentException("Name output '" + namedOutput +
"' has not been defined as multi");
}
if (multi) {
checkTokenName(multiName);
}
String baseFileName = (multi) ? namedOutput + "_" + multiName : namedOutput;
final RecordWriter writer =
getRecordWriter(namedOutput, baseFileName, reporter);
return new OutputCollector() {
@SuppressWarnings({"unchecked"})
public void collect(Object key, Object value) throws IOException {
writer.write(key, value);
}
};
}
baseFileName
是文件名前缀,后面用于 初始化文件名,如果是multi的话 文件名前缀由
namedOutput + "_" + multiName
组成,否则是
namedOutput
,看下该类里getRecordWriter()实现中 关键出 得到文件名是如何实现的?
String nameOutput = job.get(CONFIG_NAMED_OUTPUT, null);
String fileName = getUniqueName(job, baseFileName);
其中getUniqueName 方法 沿用的FileOutputFormat 的实现。
public static String getUniqueName(JobConf conf, String name) {
int partition = conf.getInt("mapred.task.partition", -1);
if (partition == -1) {
throw new IllegalArgumentException(
"This method can only be called from within a Job");
}
String taskType = (conf.getBoolean("mapred.task.is.map", true)) ? "m" : "r";
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setMinimumIntegerDigits(5);
numberFormat.setGroupingUsed(false);
return name + "-" + taskType + "-" + numberFormat.format(partition);
}
所以 使用该类时 文件数并不是由reduce个数决定,而是 name 的组成(或者说是否multi)以及 mapred.task.partition决定。