Spark的官方文檔再三強調那些將要作用到RDD上的操作,不管它們是一個函數還是一段代碼片段,它們都是“閉包”,Spark會把這個閉包分發到各個worker節點上去執行,這裏涉及到了一個容易被忽視的問題:閉包的“序列化”。
顯然,閉包是有狀態的,這主要是指它牽涉到的那些自由變量以及自由變量依賴到的其他變量,所以,在將一個簡單的函數或者一段代碼片段(就是閉包)傳遞給類似RDD.map這樣的操作前,Spark需要檢索閉包內所有的涉及到的變量(包括傳遞依賴的變量),正確地把這些變量序列化之後才能傳遞到worker節點並反序列化去執行
問題:
Exception in thread "main" org.apache.spark.SparkException: Job aborted due to stage failure: Task not serializable: java.io.NotSerializableException: org.json4s.DefaultFormats$
Serialization stack:
- object not serializable (class: org.json4s.DefaultFormats$, value: org.json4s.DefaultFormats$@5897047d)
- field (class: com.demo.cn.mlib.SparkJSON$$anonfun$1, name: formats$1, type: class org.json4s.DefaultFormats$)
- object (class com.demo.cn.mlib.SparkJSON$$anonfun$1, <function1>)
- field (class: org.apache.spark.sql.execution.MapPartitionsExec, name: func, type: interface scala.Function1)
- object (class org.apache.spark.sql.execution.MapPartitionsExec, MapPartitions <function1>, obj#19: scala.Tuple2
+- DeserializeToObject createexternalrow(content#8.toString, StructField(content,StringType,true)), obj#18: org.apache.spark.sql.Row
+- LocalTableScan [content#8]
其中extract方法爲
def extract[A](implicit formats : org.json4s.Formats, mf : scala.reflect.Manifest[A]) : A = { /* compiled code */ }
問題的癥結就在於:閉包沒有辦法序列化。在這個例子裏,閉包的範圍是:函數parser以及它所依賴的一個隱式參數: formats , 而問題就出在這個隱式參數上, 它的類型是DefaultFormats,這個類沒有提供序列化和反序列自身的說明,所以Spark無法序列化formats,進而無法將task推送到遠端執行。
解決方法:
實際上我們根本不需要序列化formats, 對我們來說,它是無狀態的。所以,我們只需要把它聲明爲一個全局靜態的變量就可以繞過序列化。所以改動的方法就是簡單地把implicit val formats = DefaultFormats的聲明從方法內部遷移到App Object的字段位置上即可。
最終代碼:
package com.demo.cn.mlib
import com.alibaba.fastjson.JSONArray
import org.apache.spark.sql.SparkSession
import com.alibaba.fastjson.JSON
import scala.collection.mutable.ArrayBuffer
import org.json4s._
import org.json4s.jackson.JsonMethods._
object SparkJSON {
implicit val formats = DefaultFormats
def main(args: Array[String]): Unit = {
val spark=SparkSession.builder()
.appName("PiPlineLR")
.master("local[*]")
.config("spark.serializer","org.apache.spark.serializer.KryoSerializer")
.getOrCreate()
import spark.implicits._
val training = spark.createDataFrame(
Seq(
("1001","{\"name\":\"Tom\", \"age\":25}","12"),
("1002","{\"name\":\"Jack\", \"age\":20}","19"),
("1003","{\"name\":\"Andy\", \"age\":34}","14")
)).toDF("id","content","age")
val tmpDF=training.selectExpr("content").mapPartitions(x=>{
x.map(x=>{
val json= x.getAs[String]("content")
val mess=parse(json).extract[info]
(mess.name,mess.age)
})
}).toDF("name","age")
// tmpDF.repartition(1).write.parquet("/")
tmpDF.show()
spark.stop()
}
case class info(name:String,age:Int)
}