Spark NotSerializableException

时间:2015-06-14 11:03:14

标签: java hadoop apache-spark

在我的Spark代码中,我试图从csv文件创建一个IndexedRowMatrix。但是,我收到以下错误:

Exception in thread "main" org.apache.spark.SparkException: Task not serializable
...
Caused by: java.io.NotSerializableException: org.apache.spark.api.java.JavaSparkContext

这是我的代码:

sc = new JavaSparkContext("local", "App",
              "/srv/spark", new String[]{"target/App.jar"});

JavaRDD<String> csv = sc.textFile("data/matrix.csv").cache();


JavaRDD<IndexedRow> entries = csv.zipWithIndex().map(
              new  Function<scala.Tuple2<String, Long>, IndexedRow>() {
                /**
                 * 
                **/ 
                private static final long serialVersionUID = 4795273163954440089L;

                @Override
                public IndexedRow call(Tuple2<String, Long> tuple)
                        throws Exception {
                    String line = tuple._1;
                    long index = tuple._2;
                    String[] strings = line.split(",");
                    double[] doubles = new double[strings.length];
                     for (int i = 0; i < strings.length; i++) {
                         doubles[i] = Double.parseDouble(strings[i]);
                     }
                     Vector v = new DenseVector(doubles);
                     return new IndexedRow(index, v);
                }
            });

6 个答案:

答案 0 :(得分:3)

我有同样的问题。 它驱使我绕着扭曲。 它是匿名实例和Serializability的Java限制。 我的解决方案是将Function的匿名实例声明为实现Serializable并实例化它的命名静态类。 我基本上声明了一个函数库,它是一个外部类,包含我想要使用的函数的静态内部类定义。

当然,如果你在Scala中编写它,它将是一个很可能具有更简洁代码的文件,但在这种情况下这对你没有帮助。

答案 1 :(得分:2)

有些东西闻起来很腥,如果你向我们展示更多的代码,我们可以给出更好的答案。

无论如何,您可以尝试在代表您的映射器函数的单独文件中创建一个公共类:

public class Mapper implements Function<Tuple2<String,Long>, IndexedRow> {

  @Override
  public IndexedRow call(Tuple2<String, Long> tuple) throws Exception {
    String line = tuple._1();
    long index = tuple._2();
    String[] strings = line.split(",");
    double[] doubles = new double[strings.length];
    for (int i = 0; i < strings.length; i++) {
      doubles[i] = Double.parseDouble(strings[i]);
    }
    Vector v = new DenseVector(doubles);
    return new IndexedRow(index, v);
  }
}

然后用它来映射你的JavaRDD:

JavaRDD<String> csv = jsc.textFile("data/matrix.csv").cache();
JavaRDD<IndexedRow> entries = csv.zipWithIndex().map(new Mapper());

这样,对于map()调用,Spark只需要序列化Mapper类,该类没有任何非可序列化的属性。

然而,由于我们无法看到所有涉及的代码,我们无法知道其他原因导致作业失败。

答案 2 :(得分:2)

如果您遇到序列化问题,最好添加以下参数:-Dsun.io.serialization.extendedDebugInfo=true这样您可以更精确地查看失败的位置。

现在,您的代码中可能会发生什么。 JavaSparkContext确实不可序列化(出于一些原因,您可以在网上找到)。在您的代码中,您没有直接对其进行序列化,但您确实持有对它的引用,因为Function不是静态的,因此它包含对封闭类的引用。因此,当您发送映射时,基本上会发生这种情况,它会尝试序列化包含不可序列化的JavaSparkContext的封闭类,这是您的异常应该来自的地方。您可以尝试静态地重写此函数,或者将您的函数编写为非嵌套类,或者使JavaSparkContext本地化,以便它不被序列化。

如果可能的话,我建议您选择最新的选项,原因很简单,最好在本地创建JavaSparkContext,因为否则您会因为每个参考而有数百个不可序列化的问题(有时候很难找到)你可能会坚持到你的班级。例如,您可以通过在主类中实现JavaSparkContext来实现此目的:

public static void main(String[] args) {
   JavaSparkContext sc = new JavaSparkContext();

   // do whatever you need to do, if you need sc inside other classes,
   // store this sc into a static class, say Registry.set(sc) and Registry.getJSC()

   JavaRDD<String> csv = sc.textFile("data/matrix.csv").cache();
   JavaRDD<IndexedRow> entries = csv.zipWithIndex().map(
          new  Function<scala.Tuple2<String, Long>, IndexedRow>() {
            private static final long serialVersionUID = 4795273163954440089L; // won't be serialized

            @Override
            public IndexedRow call(Tuple2<String, Long> tuple)
                    throws Exception {
                String line = tuple._1;
                long index = tuple._2;
                String[] strings = line.split(",");
                double[] doubles = new double[strings.length];
                 for (int i = 0; i < strings.length; i++) {
                     doubles[i] = Double.parseDouble(strings[i]);
                 }
                 Vector v = new DenseVector(doubles);
                 return new IndexedRow(index, v);
            }
        });
}

另请注意,静态字段不是与实例相关联,而是与类相关联,因此我认为您的serialVersionUID也未被序列化(如果它在某些时候成为您的问题)。

答案 3 :(得分:1)

为您的映射器创建一个单独的类并实现Srielizable,有时内部类会导致在spark环境中编译问题。

答案 4 :(得分:0)

任何在驱动程序中编写并在RDD转换中使用的代码都需要序列化。如果您遇到序列化问题,请遵循以下设计原则:

  1. 编写在转换中使用不可序列化对象的所有代码(map)。
  2. 在Spark中使用forEachPartition来执行每个分区的操作。 RDD转换共享的任何代码都必须始终可序列化。

答案 5 :(得分:0)

通常,当任务提交给不同的执行程序时,Rdd对象将由spark序列化。但是你应该使用闭包来避免这个错误。

您可以使用Rdd.mapPartition()处理每个分区并将代码放在其中。通过这种方式,spark本身将负责序列化和反序列化地图对象。