问题描述及原因分析
在编写Spark程序中,由于在map等算子内部使用了外部定义的变量和函数,从而引发Task未序列化问题。然而,Spark算子在计算过程中使用外部变量在许多情形下确实在所难免,比如在filter算子根据外部指定的条件进行过滤,map根据相应的配置进行变换等。为了解决上述Task未序列化问题,这里对其进行了研究和总结。
出现“org.apache.spark.SparkException: Task not serializable”这个错误,一般是因为在map、filter等的参数使用了外部的变量,但是这个变量不能序列化( 不是说不可以引用外部变量,只是要做好序列化工作 ,具体后面详述)。其中最普遍的情形是:当引用了某个类(经常是当前类)的成员函数或变量时,会导致这个类的所有成员(整个类)都需要支持序列化。虽然许多情形下,当前类使用了“extends Serializable”声明支持序列化,但是由于某些字段不支持序列化,仍然会导致整个类序列化时出现问题,最终导致出现Task未序列化问题。
引用成员变量的实例分析
如上所述, 由于Spark程序中的map、filter等算子内部引用了类成员函数或变量导致需要该类所有成员都需要支持序列化,又由于该类某些成员变量不支持序列化,最终引发Task无法序列化问题 。为了验证上述原因,我们编写了一个实例程序,如下所示。该类的功能是从域名列表中(rdd)过滤得到特定顶级域名(rootDomain,如.com,.cn,.org)的域名列表,而该特定顶级域名需要函数调用时指定。
class MyTest1(conf:String) extends Serializable{
val list = List("a.com", "www.b.com", "a.cn", "a.com.cn", "a.org");
private val sparkConf = new SparkConf().setAppName("AppName");
private val sc = new SparkContext(sparkConf);
val rdd = sc.parallelize(list);
private val rootDomain = conf
def getResult(): Array[(String)] = {
val result = rdd.filter(item => item.contains(rootDomain))
result.take(result.count().toInt)
}
}
依据上述分析的原因,由于依赖了当前类的成员变量,所以导致当前类全部需要序列化,由于当前类某些字段未做好序列化,导致出错。实际情况与分析的原因一致,运行过程中出现错误,如下所示。分析下面的错误报告得到错误是由于sc(SparkContext)引起的。
Exception in thread "main" org.apache.spark.SparkException: Task not serializable
at org.apache.spark.util.ClosureCleaner$.ensureSerializable(Closu