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)
}