最近在把同事的MR作业输出文件转成Parquet,因为占空间太大,结果踩了不少坑。。。各种 null,现在看到null我就生气,(╯^╰)
输出时使用的是
import org.apache.parquet.example.data.Group;
1.第一个null - Unable to initialize any output collector
原因:mapper输出类型不可序列化
这个问题在网上查了半天,大家众说纷纭,有的说内存不够之类的,最后查了MR作业的日志(查日志非常关键),发现是MapTask中createSortingCollector方法的报错,该方法主要部分如下:
int remainingCollectors = collectorClasses.length;
for (Class clazz : collectorClasses) {
try {
if (!MapOutputCollector.class.isAssignableFrom(clazz)) {
throw new IOException("Invalid output collector class: " + clazz.getName() +
" (does not implement MapOutputCollector)");
}
Class<? extends MapOutputCollector> subclazz =
clazz.asSubclass(MapOutputCollector.class);
LOG.debug("Trying map output collector class: " + subclazz.getName());
MapOutputCollector<KEY, VALUE> collector =
ReflectionUtils.newInstance(subclazz, job);
collector.init(context);
LOG.info("Map output collector class = " + collector.getClass().getName());
return collector;
} catch (Exception e) {
String msg = "Unable to initialize MapOutputCollector " + clazz.getName();
if (--remainingCollectors > 0) {
msg += " (" + remainingCollectors + " more collector(s) to try)";
}
LOG.warn(msg, e);
}
}
throw new IOException("Unable to initialize any output collector");
大致就是会根据Mapper输出的结构在SerializationFactory中寻找对应的keySerializer,我在idea中Download source后,发现是我将mapper的输出设置为了<Void,Group>,而Group没有对应的Serializer,将Group更换为Writable类型的数据后问题解决。(具体原因还是要看日志是哪一行报错。
2.第二个null - parquet.example.schema should not be null
原因:writeSupport setSchema的位置错误。
看到这个错误我是肥肠郁闷的,因为我的代码里明明白白的写着:GroupWriteSupport.setSchema(schema, conf); 我思来想去,又查看了一下源码,也没有发现这个到底有什么问题,倒是同事的一句话提醒了我,我set了,拿到是空的,说明get的位置比set靠前,将该部分内容尽可能提前,在获得conf之后就设置模式,问题解决。
3.第三个null - java.lang.NullPointerException
原因:万恶的Combiner
非常标准的空指针异常,错误栈如下:
at org.apache.hadoop.mapred.IFile$Writer.append(IFile.java:190)
at org.apache.hadoop.mapred.Task$CombineOutputCollector.collect(Task.java:1313)
at org.apache.hadoop.mapred.Task$NewCombinerRunner$OutputConverter.write(Task.java:1630)
at org.apache.hadoop.mapreduce.task.TaskInputOutputContextImpl.write(TaskInputOutputContextImpl.java:89)
at org.apache.hadoop.mapreduce.lib.reduce.WrappedReducer$Context.write(WrappedReducer.java:105)
at com.xiaomi.vip.hadoop.app.data.statistics.AppDataStatistics$IntSumReducer.reduce(AppDataStatistics.java:408)
at com.xiaomi.vip.hadoop.app.data.statistics.AppDataStatistics$IntSumReducer.reduce(AppDataStatistics.java:382)
at org.apache.hadoop.mapreduce.Reducer.run(Reducer.java:192)
at org.apache.hadoop.mapred.Task$NewCombinerRunner.combine(Task.java:1651)
at org.apache.hadoop.mapred.MapTask$MapOutputBuffer.sortAndSpill(MapTask.java:1637)
at org.apache.hadoop.mapred.MapTask$MapOutputBuffer.flush(MapTask.java:1489)
at org.apache.hadoop.mapred.MapTask$NewOutputCollector.close(MapTask.java:727)
注意到错误栈中出现了一个Combine,于是回去翻代码,发现同事原本的程序中有这样一句:
job.setCombinerClass(IntSumReducer.class);
这句是做什么的呢? 我们知道map reduce 是分布式的,这句就是在reduce之前,将单个节点map之后的数据先做一次节点内的reduce,以提高效率,问题就出在这里,Combine 与 reduce使用的是相同的class,但由于parquet文件的输出需要,reduce时输出的数据结构为<Void,Group>,而reduce接受的结构为<Text,LongWritable>,这就导致先做了Combine的数据无法正常进行Reduce,这里就需要重写Combiner,或者向我一样,直接把这句干掉(对效率要求不高的懒人。
hhhh,至此我的job终于能欢快的打印百分比了,但能不能正常跑完,我也不知道,哈哈哈哈哈哈,如果还有坑,后续接着补充。