(为什么)我们需要调用缓存还是坚持使用RDD


171

从文本文件或集合(或另一个RDD)创建弹性分布式数据集(RDD)时,我们是否需要显式调用“缓存”或“持久”以将RDD数据存储到内存中?还是默认情况下,RDD数据是否以分布式方式存储在内存中?

val textFile = sc.textFile("/user/emp.txt")

根据我的理解,在完成上述步骤之后,textFile是一个RDD,并且在节点的所有/某些内存中都可用。

如果是这样,为什么我们需要在textFile RDD上调用“缓存”或“持久”呢?

Answers:


300

大多数RDD操作都是惰性的。将RDD视为一系列操作的描述。RDD不是数据。所以这行:

val textFile = sc.textFile("/user/emp.txt")

它什么也没做。它创建一个RDD,上面写着“我们将需要加载该文件”。此时尚未加载该文件。

需要观察数据内容的RDD操作不能偷懒。(这些称为动作。)一个示例是RDD.count-告诉您文件中的行数,需要读取文件。因此,如果您写入textFile.count,这时将读取文件,对行进行计数,并返回计数。

如果您textFile.count再次打电话怎么办?同样的事情:文件将被读取并再次计数。什么都没有存储。RDD不是数据。

那怎么RDD.cache办?如果添加textFile.cache到上面的代码中:

val textFile = sc.textFile("/user/emp.txt")
textFile.cache

它什么也没做。RDD.cache也是一个懒惰的操作。该文件仍然无法读取。但是现在RDD会说“读取此文件,然后缓存内容”。如果您textFile.count是第一次运行,该文件将被加载,缓存和计数。如果您textFile.count再次调用,该操作将使用缓存。它只会从缓存中获取数据并计算行数。

缓存行为取决于可用内存。例如,如果文件不适合存储在内存中,textFile.count则将恢复为通常的行为并重新读取文件。


4
丹尼尔,您好:-当您调用缓存时,这是否意味着未从源(例如文本文件)重新加载RDD-您如何确定文本文件中的数据在缓存时是最新的?(是通过火花来解决这个问题,还是定期手动取消unpersist()来确保源数据在以后的世系中重新计算?)
andrew.butkus 2015年

还-如果必须定期取消持久化-如果已缓存的rdd依赖于另一个已缓存的RDD,是否必须取消持久化两个RDD才能查看重新计算的结果?
andrew.butkus 2015年

21
Spark仅假定文件永远不会更改。它会在任意时间读取文件,并可能在以后需要时重新读取文件的某些部分。(例如,如果一部分数据从缓存中推出。)因此,最好不要更改文件!拥有新数据时,只需使用新名称创建一个新文件,然后将其作为新的RDD加载即可。如果您一直在获取新数据,请查看Spark Streaming。
Daniel Darabos

10
是。RDD是不可变的,因此每个RDD都假定其依赖性也是不可变的。Spark Streaming允许您设置对更改流进行操作的此类树。但是,更简单的解决方案是在以文件名作为参数的函数中构建树。然后,只需为新文件和poof调用函数,便有了新的计算树。
Daniel Darabos 2015年

1
@Humoyun:在Spark UI的“存储”选项卡上,您可以看到每个RDD缓存了多少。数据可能是如此之大,以至于只有40%的数据适合您用于缓存的总内存。在这种情况下,一个选项是使用perisist并选择一个存储选项,该选项允许将缓存数据溢出到磁盘上。
丹尼尔·达拉博斯

197

我认为这个问题最好表述为:

我们什么时候需要调用缓存或保留RDD?

Spark进程是惰性的,也就是说,直到需要时,什么都不会发生。为了快速回答这个问题,val textFile = sc.textFile("/user/emp.txt")发出文件后,数据什么都没有发生,仅HadoopRDD使用文件作为源构造了一个。

假设我们对数据进行了一些转换:

val wordsRDD = textFile.flatMap(line => line.split("\\W"))

同样,数据没有任何反应。现在有一个新的RDD wordsRDD,其中包含对RDD 的引用testFile以及在需要时要应用的功能。

仅当在RDD上调用操作时,例如 wordsRDD.count RDD链)时,才会执行称为血统的动作。也就是说,按分区细分的数据将由Spark集群的执行程序加载,该flatMap函数将被应用并计算结果。

在线性血统中,cache()不需要像本例中那样。数据将被加载到执行器,所有转换将被应用,最后count将被计算,全部在内存中-如果数据适合内存。

cache当RDD的血统分支出去时,此功能很有用。假设您要将前面示例中的单词过滤为正数和负数的计数。您可以这样做:

val positiveWordsCount = wordsRDD.filter(word => isPositive(word)).count()
val negativeWordsCount = wordsRDD.filter(word => isNegative(word)).count()

在这里,每个分支都会重新加载数据。添加显式cache语句将确保保留并重复使用以前完成的处理。该工作将如下所示:

val textFile = sc.textFile("/user/emp.txt")
val wordsRDD = textFile.flatMap(line => line.split("\\W"))
wordsRDD.cache()
val positiveWordsCount = wordsRDD.filter(word => isPositive(word)).count()
val negativeWordsCount = wordsRDD.filter(word => isNegative(word)).count()

是因为, cache据说它“破坏了血统”,因为它创建了可重新用于进一步处理的检查点。

经验法则:cache当RDD的谱系分支出来或像循环中一样多次使用RDD时使用。


1
太棒了 谢谢。还有一个相关的问题。当我们缓存或持久化时,数据将存储在执行者的内存或工作程序节点的内存中。如果它是执行者的内存,Spark怎样识别哪个执行者拥有数据。
Ramana'3

1
@RamanaUppala使用执行程序内存。执行程序用于缓存的内存部分由config控制spark.storage.memoryFraction。关于哪个执行程序具有哪些数据,RDD将跟踪其在执行程序上分布的分区。
maasg 2015年

5
@maasg纠正我,如果我错了,但也cache还是persist 可以打破沿袭
zero323 '16

如果上面的示例中没有.cache()语句,则将单词RDD存储在哪里?
sun_dare

如果在两个计数之前,我们将两个分支合并回一个rdd并计数呢?在这种情况下,缓存是否有益?
Xiawei Zhang

30

我们是否需要显式调用“缓存”或“持久”将RDD数据存储到内存中?

是的,仅在需要时。

默认情况下,RDD数据以分布式方式存储在内存中吗?

没有!

这些就是原因:

  • Spark支持两种类型的共享变量:广播变量(可用于在所有节点上的内存中缓存值)和累加器(累加器)是仅“加”到变量的变量,例如计数器和总和。

  • RDD支持两种类型的操作:转换(从现有操作创建新的数据集)和动作(在操作数据集上进行计算后,将值返回给驱动程序)。例如,map是一种转换,它将每个数据集元素通过一个函数传递,并返回代表结果的新RDD。另一方面,reduce是使用某些函数聚合RDD的所有元素并将最终结果返回给驱动程序的操作(尽管也有并行的reduceByKey返回分布式数据集)。

  • Spark中的所有转换都是惰性的,因为它们不会立即计算出结果。相反,他们只记得应用于某些基本数据集(例如文件)的转换。仅当动作要求将结果返回给驱动程序时才计算转换。这种设计使Spark可以更有效地运行-例如,我们可以认识到通过map创建的数据集将用于reduce中,并且仅将reduce的结果返回给驱动程序,而不是将较大的maped数据集返回给驱动程序。

  • 默认情况下,每次在其上执行操作时,可能会重新计算每个转换后的RDD。但是,您也可以使用persist(或缓存)方法将RDD保留在内存中,在这种情况下,Spark会将元素保留在群集中,以便下次查询时可以更快地进行访问。还支持将RDD持久保存在磁盘上,或在多个节点之间复制。

有关更多详细信息,请查看Spark编程指南


1
那没有回答我的问题。
Ramana'3

什么不回答呢?
eliasah 2015年

1
当RDD的数据默认存储在内存中时,为什么我们需要调用Cache或Persist?
Ramana'3

RDD默认情况下不存储在内存中,因此持久保留RDD使Spark在群集上执行转换的速度更快
eliasah 2015年

2
这是一个很好的答案,我不知道为什么它被否决了。这是一个自上而下的答案,它从高级概念上解释了RDD是如何工作的。我添加了另一个自下而上的答案:从“此行的作用”开始。对于刚开始使用Spark的人来说,可能更容易遵循。
Daniel Darabos 2015年

11

下面是应缓存RDD的三种情况:

多次使用RDD

在同一RDD上执行多项操作

用于长链(或非常昂贵的)转换


7

添加另一个原因来添加(或临时添加)cache方法调用。

用于调试内存问题

with cache方法,spark将提供有关RDD大小的调试信息。因此,在spark集成UI中,您将获得RDD内存消耗信息。事实证明,这对于诊断内存问题非常有帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.