文章目錄
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