Apache spark Spark流中的序列化问题

Apache spark Spark流中的序列化问题,apache-spark,apache-spark-sql,spark-streaming,apache-spark-ml,Apache Spark,Apache Spark Sql,Spark Streaming,Apache Spark Ml,我很困惑Spark是如何处理引擎盖下的数据的。例如,当我运行流作业并应用foreachRDD时,行为取决于变量是从外部范围捕获还是在内部初始化 val sparkConf = new SparkConf() dStream.foreachRDD(rdd => { val spark = SparkSession.builder.config(sparkConf).getOrCreate() ... }) 在这种情况下,我得到一个例外: java.io.NotSeriali

我很困惑Spark是如何处理引擎盖下的数据的。例如,当我运行流作业并应用
foreachRDD
时,行为取决于变量是从外部范围捕获还是在内部初始化

val sparkConf = new SparkConf()
dStream.foreachRDD(rdd => {
    val spark = SparkSession.builder.config(sparkConf).getOrCreate()
    ...
})
在这种情况下,我得到一个例外:

java.io.NotSerializableException:org.apache.spark.streaming.kafka.DirectKafkaInputDStream$DirectKafkaInputDStreamCheckpointData的对象正在序列化,可能是RDD操作关闭的一部分。这是因为DStream对象是从闭包中引用的。请在此数据流中重写RDD操作以避免此情况。这是为了避免Spark任务因不必要的对象而膨胀

但是如果我把
sparkConf
移到里面,一切似乎都很好:

dStream.foreachRDD(rdd => {
    val sparkConf = rdd.sparkContext.getConf
    val spark = SparkSession.builder.config(sparkConf).getOrCreate()
    ...
})
这在我看来很奇怪,因为我认为
foreachRDD
在驱动程序节点上运行,所以我不希望有任何不同

现在,如果我将SQL会话和配置移到外部
foreachRDD
,它会再次正常工作:

val sparkConf = new SparkConf()
val spark = SparkSession.builder.config(sparkConf).getOrCreate()
dStream.foreachRDD(rdd => {
    val df = spark.read.json(rdd)
    ...
})
Spark中的一个文档建议使用以前的版本(在
foreachRDD
中创建config和SQL上下文),这在我看来效率较低:如果可以只创建一次,为什么要为每个批创建它们


有人能解释一下引发异常的原因,以及创建SQL上下文的正确方法吗?

正如名称所示,foreach rdd在流中运行,foreach rdd为什么要在每个rdd上重新创建spark上下文? 正确的方法是最后一种:

val sparkConf = new SparkConf()
val spark = SparkSession.builder.config(sparkConf).getOrCreate()
dStream.foreachRDD(rdd => {
    val df = spark.read.json(rdd)
    ...
})

val spark=SparkSession.builder.config(sparkConf.getOrCreate()
不创建另一个
SparkSession
。只有一个存在。
worker
上,只需从
job

获取它。在第一种方法中,您试图为每个不正确的分区实例化spark会话对象

正如其他人所回答的,使用第三种方法。但如果您需要使用第一种方法,那么您可以使用以下方法-

val sparkConf = new SparkConf()
dStream.foreachRDD(rdd => {
    lazy val spark = SparkSession.builder.config(sparkConf).getOrCreate()
    ...
})
在这里,延迟评估将有助于避免多次实例化spark会话,从而避免序列化问题


我希望这会有帮助。

我认为foreachRDD在驱动程序节点上运行,传递给
foreachRDD
的方法在工作程序上运行,而不是在驱动程序上运行。@YuvalItzchakov我不这么认为,因为
foreachRDD
在整个RDD上运行,而不是在该RDD的分区或元素上运行。文档明确指出它在驱动程序节点上运行:你说得对,我没有正确表达自己。我的意思是说,传递给
rdd
(即,您希望在数据帧上执行的任何操作)的委托将在worker节点上运行。@lizarisk:您不能在转换或操作操作中使用任何未序列化的类,这些操作正在worker节点上运行。@Shankar我在这里没有看到任何在worker节点上运行的操作