我很担心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对象。请在此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文档中的snippet建议使用以前的版本(其中配置和SQL上下文都是在foreachRDD
中创建的),这对我来说似乎效率低下:如果可能的话,为什么要为每个批次创建它们?创造了一次?
有人可以解释为什么抛出异常以及创建SQL上下文的正确方法是什么?
答案 0 :(得分:0)
ForeachRDD运行,顾名思义,foreach rdd在流媒体中为什么你应该在每个rdd重新创建火花上下文? 正确的方法是最后一个:
val sparkConf = new SparkConf()
val spark = SparkSession.builder.config(sparkConf).getOrCreate()
dStream.foreachRDD(rdd => {
val df = spark.read.json(rdd)
...
})
答案 1 :(得分:0)
val spark = SparkSession.builder.config(sparkConf).getOrCreate()
不会创建另一个SparkSession
。仅存在一个。
在worker
上,只需从job
中获取它即可。
答案 2 :(得分:0)
在第一种方法中,您尝试为每个不正确的分区实例化spark会话对象。
其他人回答,请使用第三种方法。但是,如果您需要使用第一种方法,则可以使用以下方法-
val sparkConf = new SparkConf()
dStream.foreachRDD(rdd => {
lazy val spark = SparkSession.builder.config(sparkConf).getOrCreate()
...
})
此处懒惰评估将有助于避免多次实例化spark会话,从而避免序列化问题。
我希望这会有所帮助。