Spark的执行模型
我们遇到的大多数必须处理的问题需要先理解代码的执行环境。Spark作业是在分布式数据集上执行并行操作的驱动程序。为了解决问题需要理解代码的不同部分在哪里运行。
下面是官网的WordCount例子:
file = spark.textFile("hdfs://...")
file.flatMap(line => line.split(" "))
.map(word => (word, 1))
.reduceByKey(_ + _)
下图是官网的架构图:
我们知道,对于Spark开发的分布式应用程序,和写普通的scala程序基本类似。所以这时候往往会陷入一些误区:
在Spark开发的应用程序的对象里,我给他们分为2类对象:
1、闭包内的对象:即在类似map,filter,reduceByKey这样的闭包内的对象,通过会有一个函数映射。
2、闭包外的对象:反之,即闭包外的对象。
这2类对象其实是混杂在Driver和Worker里的,即并不是所有对象都会随着程序序列化到Worker里。
Spark出现NotSerializableException
如果强制将一个不支持序列化的对象方到闭包里,会报异常NotSerializableException
object MyFirstSparkJob {
def main(args: Array[String]) {
val ssc = new StreamingContext(args(0), "BeaconCount", Seconds(1))
val parser = new JSONParser // <-- INSTANTIATED HERE
val lines = ssc.textFileStream("beacons.txt")
lines.map(line => parser.parse(line)) // <-- IN THE CLOSURE
lines.foreach(line => println(line))
ssc.start()
}
}
对象JSONParser在并行操作的闭包中被引用,并被序列化到worker。但是JSONParser类不是可序列化的,会导致错误。为了解决这个问题,我们需要将JSONParser实现序列化,或者将对象移动到闭包中进行创建。
Spark任务执行特别慢并且使用的是单例线程工作
在并行的闭包操作中引用单例对象将会阻碍进程,因为这些引用将在Driver程序中发生,
object JSONParser {
def parse(raw: String): String = ...
}
object MyFirstSparkJob {
def main(args: Array[String]) {
val ssc = new StreamingContext(args(0), "BeaconCount", Seconds(1))
val lines = ssc.textFileStream("beacons.txt")
lines.map(line => JSONParser.parse(line))
lines.foreach(line => println(line))
ssc.start()
}
}
这和上一个例子的代码类似,但是这里的JSONParser对象现在是在我们的Driver程序范围内创建的单例对象。这个对象不会再worker的上下文中传递,所以Spark只会在驱动程序中执行引用该对象的代码,这对job会造成瓶颈。
为了解决这个问题,你可以把这个对象变成一个可以传递给worker进程的可序列化的类。或者你可以使用广播变量在每个worker的上下文中使这个对象可用。
Spark报java.lang.IllegalArgumentException:shuffle id XXXX registered twice
关于异常捕捉,如果在Spark Driver程序中对job的异常没有捕捉的话会导致java.lang.IllegalArgumentException: Shuffle Id Nnnn Registered Twice