3

我有一个映射器,其输出值设置为这样的接口:

public interface OutValue extends Writable {}

在映射期间,我使用此签名创建对象并发出它们:

public class OutRecord implements OutValue {}

我的映射器是这样的:

public class ExampleMapper extends
    Mapper<LongWritable, Text, ExampleKey, OutValue > {}

但是我收到此错误:

java.io.IOException: Type mismatch in value from map: expected OutValue, recieved OutRecord
    at org.apache.hadoop.mapred.MapTask$MapOutputBuffer.collect(MapTask.java:850)
    at org.apache.hadoop.mapred.MapTask$NewOutputCollector.write(MapTask.java:541)
    at org.apache.hadoop.mapreduce.TaskInputOutputContext.write(TaskInputOutputContext.java:80)

所以我的第一个直觉是尝试像这样投射它:

context.write(key, (OutValue) record);

但是我仍然遇到同样的错误。这在我将映射器输出类型从 OutRecord 类更改为 OutValue 接口之前有效。我这样做的原因是我想从这个映射器发出许多类型的 OutRecord 类。

这可能吗?OutValue 必须是类而不是接口吗?

更新:

我挖掘了 Hadoop 0.20.205.0 的一些源代码,发现了这个:

public synchronized void collect(K key, V value, int partition) throws IOException {
...
if (value.getClass() != valClass) {
  throw new IOException("Type mismatch in value from map: expected "
                        + valClass.getName() + ", recieved "
                        + value.getClass().getName());
}

所以他们使用的运行时检查需要类中的严格相等,他们不检查子类/接口等。当然这是一个常见的用例,有没有人尝试过这样做?

4

1 回答 1

2

对类型进行这种严格检查有几个原因:

如果要输出到序列文件,则此文件的标头包含 Key 和 Value 类的类型。然后,当序列文件被读回时,Hadoop 使用注册的序列化程序来创建这些对象的新实例。

如果您注册为输出类型的类是接口,或者您输出的实际对象是声明类型的子类,那么您将无法在运行时实例化接口,或者实例化的类将不是子类您期望的类(并且您的反序列化很可能会因 IOException 而失败)。

(当我开始输入这个时,我想到了另一个原因,但它暂时让我忘记了)。

现在,如果您希望能够输出不同的类型(子类),那么请考虑使用GenericWritable来“包装”您的对象——在这种情况下,每个对象输出都以一个类型开头——查看源代码和 javadocs 以获取更多详细信息。

请注意,这是有代价的——输入和输出不会利用在 hadoop 其他地方看到的对象重用,但您可能不会注意到这个成本。您可以通过为每个看到的实例类型汇集一个对象并以通常的方式重新使用它来重写 GenericWritable 以提高效率。

于 2012-06-21T00:57:23.370 回答