我们有在Apache Spark中运行的代码。在对代码进行详细检查之后,我确定我们的一个映射器正在修改RDD中的一个对象,而不是为输出复制该对象。也就是说,我们具有字典的RDD,并且map函数正在向字典中添加内容,而不是返回新的字典。
RDD应该是不可变的。我们的被变异了。
我们也遇到内存错误。
问题:如果RDD的大小突然增加,Spark会感到困惑吗?
答案 0 :(得分:2)
虽然它可能不会崩溃,但可能导致某些未指定的行为。例如,此代码段
val rdd = sc.parallelize({
val m = new mutable.HashMap[Int, Int]
m.put(1, 2)
m
} :: Nil)
rdd.cache() // comment out to change behaviour!
rdd.map(m => {
m.put(2, 3)
m
}).collect().foreach(println) // "Map(2 -> 3, 1 -> 2)"
rdd.collect().foreach(println) // Either "Map(1 -> 2)" or "Map(2 -> 3, 1 -> 2)" depending if caching is used
行为的变化取决于是否缓存了RDD。在Spark API中,有一堆允许对数据进行突变的函数,文档中已明确指出,请参见https://spark.apache.org/docs/2.4.0/api/java/org/apache/spark/rdd/PairRDDFunctions.html#aggregateByKey-U-scala.Function2-scala.Function2-scala.reflect.ClassTag-
请考虑使用RDD[(K, V)]
的地图条目而不是地图,即RDD [Map [K,V]]。这样可以使用flatMap
或mapPartitions
以标准方式添加新条目。如果需要,可以通过分组等最终生成地图表示形式。
答案 1 :(得分:0)
好的,我开发了一些代码来测试如果RDD中引用的对象被映射器突变了会发生什么,并且我很高兴地报告说,如果您是使用Python进行编程,那是不可能的。
这是我的测试程序:
from pyspark.sql import SparkSession
import time
COUNT = 5
def funnydir(i):
"""Return a directory for i"""
return {"i":i,
"gen":0 }
def funnymap(d):
"""Take a directory and perform a funnymap"""
d['gen'] = d.get('gen',0) + 1
d['id' ] = id(d)
return d
if __name__=="__main__":
spark = SparkSession.builder.getOrCreate()
sc = spark.sparkContext
dfroot = sc.parallelize(range(COUNT)).map(funnydir)
dfroot.persist()
df1 = dfroot.map(funnymap)
df2 = df1.map(funnymap)
df3 = df2.map(funnymap)
df4 = df3.map(funnymap)
print("===========================================")
print("*** df1:",df1.collect())
print("*** df2:",df2.collect())
print("*** df3:",df3.collect())
print("*** df4:",df4.collect())
print("===========================================")
ef1 = dfroot.map(funnymap)
ef2 = ef1.map(funnymap)
ef3 = ef2.map(funnymap)
ef4 = ef3.map(funnymap)
print("*** ef1:",ef1.collect())
print("*** ef2:",ef2.collect())
print("*** ef3:",ef3.collect())
print("*** ef4:",ef4.collect())
如果运行此命令,则会发现字典d
的ID在每个数据框中都不同。显然,当对象从映射器传递到映射器时,Spark正在对对象进行反序列化。因此每个人都有自己的版本。
如果这不是真的,那么对funnymap
进行第一次调用以使df1也将更改dfroot
数据帧中的世代,结果ef4将具有与df4不同的世代号。