我有一个小型Scala程序,可以在单个节点上运行。但是,我正在扩展它,因此它在多个节点上运行。这是我的第一次尝试。我只是想了解RDD如何在Spark中工作,所以这个问题是基于理论的,可能不是100%正确。
假设我创建了一个RDD:
val rdd = sc.textFile(file)
现在,一旦我这样做了,这是否意味着file
处的文件现在已经跨节点分区(假设所有节点都可以访问文件路径)?
其次,我想计算RDD中的对象数量(足够简单),但是,我需要在计算中使用该数字,这需要应用于RDD中的对象 - 伪代码示例:
rdd.map(x => x / rdd.size)
假设rdd
中有100个对象,并且说有10个节点,因此每个节点有10个对象的计数(假设这是RDD概念的工作方式),现在当我调用方法时节点将rdd.size
作为10
或100
执行计算?因为,总的来说,RDD的大小为100
,但在每个节点上本地只有10
。我是否需要在进行计算之前制作广播变量?这个问题与下面的问题有关。
最后,如果我转换到RDD,例如rdd.map(_.split("-"))
,然后我想要RDD的新size
,我是否需要对RDD执行操作,例如count()
,所以所有信息都会发送回驱动程序节点
答案 0 :(得分:20)
val rdd = sc.textFile(file)
这是否意味着该文件现在已跨节点分区?
文件保留在原处。结果RDD[String]
的元素是文件的行。 RDD被分区以匹配底层文件系统的自然分区。分区数不依赖于您拥有的节点数。
重要的是要理解,当执行此行时,不会读取文件。 RDD是一个懒惰的对象,只有在必要时才会执行某些操作。这很好,因为它避免了不必要的内存使用。
例如,如果你写val errors = rdd.filter(line => line.startsWith("error"))
,仍然没有任何反应。如果您随后编写val errorCount = errors.count
,则需要执行您的操作序列,因为count
的结果是整数。然后,每个工作者核心(执行程序线程)将并行执行的操作是读取文件(或文件片段),遍历其行,并计算以&#34开头的行;错误"。除了缓冲和GC之外,每个核心只有一行将一次存储在内存中。这样就可以在不占用大量内存的情况下处理非常大的数据。
我想计算RDD中的对象数量,但是,我需要在计算中使用该数字,该计算需要应用于RDD中的对象 - 伪代码示例:
rdd.map(x => x / rdd.size)
没有rdd.size
方法。有rdd.count
,它计算RDD中的元素数量。 rdd.map(x => x / rdd.count)
无效。代码将尝试将rdd
变量发送给所有工作人员,并且将失败并显示NotSerializableException
。你能做的是:
val count = rdd.count
val normalized = rdd.map(x => x / count)
这样做有效,因为count
是Int
并且可以序列化。
如果我转换到RDD,例如
rdd.map(_.split("-"))
,然后我想要RDD的新大小,我是否需要对RDD执行操作,例如count()
,所以所有信息都会被发送回驱动程序节点?
map
不会更改元素的数量。我不知道你的意思是" size"。但是,是的,您需要执行一个操作,例如count
以从RDD中获取任何内容。你看,在你执行一个动作之前,根本不会执行任何工作。 (当您执行count
时,只有每个分区计数将被发送回驱动程序,当然,不是"所有信息"。)
答案 1 :(得分:6)
通常,文件(或文件的一部分,如果它太大)被复制到集群中的N个节点(默认情况下,HDFS上的N = 3)。并不打算在所有可用节点之间拆分每个文件。
但是,对于您(即客户端)使用Spark处理文件应该是透明的 - 您不应该在rdd.size
中看到任何差异,无论它分割和/或复制多少个节点。有一些方法(至少在Hadoop中)可以找出目前可以找到哪些节点(部分)文件。但是,在简单的情况下,您很可能不需要使用此功能。
更新:描述RDD内部的文章:https://cs.stanford.edu/~matei/papers/2012/nsdi_spark.pdf