【Spark】Task not serializable exception while running apache spark job

1、概述

在使用spark編寫分佈式數據計算作業的過程中,我遇到了很多問題,今天跟大家分享一個 spark 作業序列化的問題,我們看一下異常信息,是不是覺得很眼熟:

org.apache.spark.SparkException: Job aborted due to stage failure: Task not serializable: java.io.NotSerializableException: ...

2、問題重現

object SparkTaskNotSerializable {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("test")
    val sc = new SparkContext(conf)
    val rdd = sc.parallelize(1 to 50, 5)
    val usz = new UnserializableClass()
    rdd.map(x=>usz.method(x)).foreach(println(_))
  }
}
class UnserializableClass {
  def method(x:Int):Int={
    x*2
  }
}

運行以上代碼,將會出現異常信息:

Exception in thread "main" org.apache.spark.SparkException: Task not serializable
	at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:345)
	at org.apache.spark.util.ClosureCleaner$.org$apache$spark$util$ClosureCleaner$$clean(ClosureCleaner.scala:335)
	at org.apache.spark.util.ClosureCleaner$.clean(ClosureCleaner.scala:159)
	at org.apache.spark.SparkContext.clean(SparkContext.scala:2292)
	at org.apache.spark.rdd.RDD$$anonfun$map$1.apply(RDD.scala:371)
	at org.apache.spark.rdd.RDD$$anonfun$map$1.apply(RDD.scala:370)
	at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:151)
	at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:112)
	at org.apache.spark.rdd.RDD.withScope(RDD.scala:363)
	at org.apache.spark.rdd.RDD.map(RDD.scala:370)
	at com.lkf.spark.SparkTaskNotSerializable$.main(SparkTaskNotSerializable.scala:17)
	at com.lkf.spark.SparkTaskNotSerializable.main(SparkTaskNotSerializable.scala)
Caused by: java.io.NotSerializableException: com.lkf.spark.UnserializableClass
Serialization stack:
	- object not serializable (class: com.lkf.spark.UnserializableClass, value: com.lkf.spark.UnserializableClass@136ccbfe)
	- field (class: com.lkf.spark.SparkTaskNotSerializable$$anonfun$main$1, name: usz$1, type: class com.lkf.spark.UnserializableClass)
	- object (class com.lkf.spark.SparkTaskNotSerializable$$anonfun$main$1, <function1>)
	at org.apache.spark.serializer.SerializationDebugger$.improveException(SerializationDebugger.scala:40)
	at org.apache.spark.serializer.JavaSerializationStream.writeObject(JavaSerializer.scala:46)
	at org.apache.spark.serializer.JavaSerializerInstance.serialize(JavaSerializer.scala:100)
	at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:342)
	... 11 more

3、問題分析

spark 處理的數據單元爲RDD(即彈性分佈式數據集),當我們要對RDD做map,filter等操作的時候是在excutor上完成的。但是如果我們在 driver 中定義了一個變量,在 map 等操作中使用了,則這個變量就會被分發到各 個excutor。

因爲 driver 和 excutor 運行在不同的jvm中,會涉及到對象的序列化與反序列化。如果這個變量沒法序列化就會報異常。還有一種情況就是引用的對象可以序列化,但是該對象本身引用的其他對象無法序列化,也會有異常。

在這裏插入圖片描述

4、解決方法

4.1、僅在map中傳遞lambda函數中聲明實例

def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("test")
    val sc = new SparkContext(conf)
    val rdd = sc.parallelize(1 to 50, 5)
    // 在 map 中實例化對象 UnserializableClass
    rdd.map(x => new UnserializableClass().method(x)).foreach(println(_))
  }

4.2、將方法封裝爲高階函數

將方法修改爲函數

class UnserializableClass {
  //method方法
  /*def method(x:Int):Int={
    x * 2
  }*/

  //method函數
  val method = (x:Int)=>x*2
}

直接使用函數

 def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("test")
    val sc = new SparkContext(conf)
    val rdd = sc.parallelize(1 to 50, 5)
    val usz  = new UnserializableClass()
    //傳入函數
    rdd.map(usz.method).foreach(println(_))
  }

4.3、使未序列化的類繼承 java.io.Serializable 接口

class UnserializableClass extends java.io.Serializable {
  def method(x: Int): Int = {
    x * 2
  }
}
def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("test")
    val sc = new SparkContext(conf)
    val rdd = sc.parallelize(1 to 50, 5)
    val usz = new UnserializableClass()
    rdd.map(usz.method).foreach(println(_))
  }

4.4、註冊序列化類(適用第三方包)

如果 UnserializableClass 來自於第三方包,我們將無法修改其源碼該怎麼辦,此時我們可以使用註冊序列化類的方法。

 def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("test")
    //指定序列化類爲KryoSerializer
    conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
    //將UnserializableClass註冊到kryo需要序列化的類中
    conf.registerKryoClasses(Array(classOf[com.lkf.spark.UnserializableClass]))
    val sc = new SparkContext(conf)
    val rdd = sc.parallelize(1 to 50, 5)
    val usz = new UnserializableClass()
    rdd.map(x => usz.method(x)).foreach(println(_))
  }

5、避免序列化問題的經驗

1、避免使用匿名類,使用靜態類,因爲匿名類將迫使您將外部類序列化。

2、避免使用靜態變量來解決序列化問題,因爲“多個任務”可以在同一JVM內運行,並且靜態實例可能不是線程安全的。

3、使用Transient變量來避免序列化問題

4、使用靜態類代替匿名類。

5、在“ lambda函數”內部永遠不要直接引用outclass方法,因爲這將導致外部類的序列化。

6、如果需要直接在Lambda函數中使用方法,請將方法設爲靜態;否則,請使用Class :: func(),而不要直接使用func()

7、Java Map <>沒有實現Serializable,但是HashMap實現了。

8、在決定使用廣播還是原始數據結構時要斟酌考慮,如果您很確定請儘量使用廣播。

參考資料:

https://stackoverflow.com/questions/25914057/task-not-serializable-exception-while-running-apache-spark-job

https://databricks.gitbooks.io/databricks-spark-knowledge-base/content/troubleshooting/javaionotserializableexception.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章