如果RDD变大,Spark将如何反应?

时间:2019-03-11 03:15:08

标签: apache-spark

我们有在Apache Spark中运行的代码。在对代码进行详细检查之后,我确定我们的一个映射器正在修改RDD中的一个对象,而不是为输出复制该对象。也就是说,我们具有字典的RDD,并且map函数正在向字典中添加内容,而不是返回新的字典。

RDD应该是不可变的。我们的被变异了。

我们也遇到内存错误。

问题:如果RDD的大小突然增加,Spark会感到困惑吗?

2 个答案:

答案 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]]。这样可以使用flatMapmapPartitions以标准方式添加新条目。如果需要,可以通过分组等最终生成地图表示形式。

答案 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不同的世代号。