匿名内部类导致的Spark序列化异常

场景

spark程序中报没有序列化的错:

org.apache.spark.SparkException: Task not serializable

建一段测试代码:

public class SparkTest {

    public static void main(String[] args) throws AnalysisException {
        SparkTest sparkTest = new SparkTest();
        sparkTest.test();
    }

    private void test() throws AnalysisException {
        String logFile = "D:/test/hadoop_test.txt";
        SparkSession spark = SparkSession.builder()
                .config("spark.master", "local")
                .appName("Simple Application")
                .getOrCreate();
        spark.sparkContext().setLogLevel("warn");
        Dataset<String> logData = spark.read().textFile(logFile);
        logData.show();
        Dataset<String> words = logData.flatMap(new FlatMapFunction<String, String>() {
            @Override
            public Iterator<String> call(String s) throws Exception {
                return Arrays.asList(s.split(" ")).iterator();
            }
        }, Encoders.STRING());
        words.createOrReplaceTempView("test");
        spark.sql("select value,count(0) sum from test group by value order by sum desc ").show();
        spark.stop();
    }
}

原因

使用了匿名内部类FlatMapFunction,导致无法序列化。

解决

2个解决方法:

1.实现Serializable接口

public class SparkTest implements Serializable

2.使用λ表达式而不使用内部类

 Dataset<String> words = logData.flatMap((FlatMapFunction<String, String>) s -> Arrays.asList(s.split(" ")).iterator(), Encoders.STRING());

扩展

1.匿名内部类和λ表达式的区别(引用自百度):

1.匿名内部类可以为任意接口创建实例——不管有多少个抽象方法,只要匿名内部类实现了所有方法即可。
但是Lambda表达式只能为函数式接口创建实例。

2.匿名内部类可以为抽象类甚至普通类创创建实例,
但lambda表达式只能为函数式接口创建实例。

扫描二维码关注公众号,回复: 11536883 查看本文章

3.匿名内部类实现的抽象方法体允许调用接口中的默认方法,
但Lambda表达式的代码块不允许调用接口中的默认方法。

2.匿名内部类和λ表达式在序列化时的区别:

使用匿名内部类编译出的class文件一般都会自动生成一个类且以$+数字结尾,其本质是一个实现了接口的类,本次使用FlatMapFunction这个接口作为匿名内部类时也生成了这样的一个类:

编译一下:

class SparkTest$1 implements FlatMapFunction {

   // $FF: synthetic field
   final SparkTest this$0;


   SparkTest$1(SparkTest this$0) {
      this.this$0 = this$0;
   }

   public Iterator call(String s) throws Exception {
      return Arrays.asList(s.split(" ")).iterator();
   }
}

它实现了FlatMapFunction这个接口,并且持有一个sparkTest的对象,而调用时总是和这个对象相关的,但是呢,SparkTest却没有实现序列化的接口,所以this$0不是可序列化的,所以会出现一开始序列化失败的问题。

使用λ表达式时,其本质也是生成了一个类,但是呢并没有生成class文件,所以是看不到的,但是呢可以通过开启-Djdk.internal.lambda.dumpProxyClasses来生成lambda的一个代理类文件就可以查看了:

	final class SparkTest$$Lambda$2011 implements FlatMapFunction {
    private SparkTest$$Lambda$2011() {
    }

    @Hidden
    public Iterator call(Object var1) {
        return SparkTest.lambda$test$1fbfce19$1((String)var1);
    }

    private final Object writeReplace() {
        return new SerializedLambda(SparkTest.class, "org/apache/spark/api/java/function/FlatMapFunction", "call", "(Ljava/lang/Object;)Ljava/util/Iterator;", 6, "spark/SparkTest", "lambda$test$1fbfce19$1", "(Ljava/lang/String;)Ljava/util/Iterator;", "(Ljava/lang/String;)Ljava/util/Iterator;", new Object[0]);
    }
}

这个类是实现了FlatMapFunction的,而FlatMapFunction extends Serializable,并且它没有需要序列化的字段,所以序列化是没有问题的。

猜你喜欢

转载自blog.csdn.net/wxgxgp/article/details/103566394