其实我觉得学习spark、官网看着很得劲儿,感觉很详细
网址:http://spark.apache.org/docs/latest/rdd-programming-guide.html
下面我就直接上图了,不想搬砖了!
Resilient Distributed Datasets (RDDs)
Spark revolves around the concept of a resilient distributed dataset (RDD), which is a fault-tolerant collection of elements that can be operated on in parallel. There are two ways to create RDDs: parallelizing an existing collection in your driver program, or referencing a dataset in an external storage system, such as a shared filesystem, HDFS, HBase, or any data source offering a Hadoop InputFormat.
RDDs support two types of operations: transformations, which create a new dataset from an existing one, and actions, which return a value to the driver program after running a computation on the dataset. For example, map
is a transformation that passes each dataset element through a function and returns a new RDD representing the results. On the other hand, reduce
is an action that aggregates all the elements of the RDD using some function and returns the final result to the driver program (although there is also a parallel reduceByKey
that returns a distributed dataset).
All transformations in Spark are lazy, in that they do not compute their results right away. Instead, they just remember the transformations applied to some base dataset (e.g. a file). The transformations are only computed when an action requires a result to be returned to the driver program. This design enables Spark to run more efficiently. For example, we can realize that a dataset created through map
will be used in a reduce
and return only the result of the reduce
to the driver, rather than the larger mapped dataset.
By default, each transformed RDD may be recomputed each time you run an action on it. However, you may also persist an RDD in memory using the persist
(or cache
) method, in which case Spark will keep the elements around on the cluster for much faster access the next time you query it. There is also support for persisting RDDs on disk, or replicated across multiple nodes.
中文翻译入下面的图:
Spark中最重要的功能之一是跨操作在内存中持久化(或缓存)数据集。当您持久保存RDD时,每个节点都会存储它在内存中计算的任何分区,并在该数据集(或从中派生的数据集)的其他操作中重用它们。这使得未来的行动更快(通常超过10倍)。缓存是迭代算法和快速交互使用的关键工具。
您可以使用persist()
或cache()
方法标记要保留的RDD 。第一次在动作中计算它,它将保留在节点的内存中。Spark的缓存是容错的 - 如果丢失了RDD的任何分区,它将使用最初创建它的转换自动重新计算。
此外,每个持久化RDD可以使用不同的存储级别进行存储,例如,允许您将数据集保留在磁盘上,将其保留在内存中,但作为序列化Java对象(以节省空间),跨节点复制它。通过传递StorageLevel
对象(Scala, Java, Python)来设置这些级别 persist()
。该cache()
方法是使用默认存储级别的简写,即StorageLevel.MEMORY_ONLY
(在内存中存储反序列化的对象)。完整的存储级别是:
存储级别 | 含义 |
---|---|
MEMORY_ONLY | 将RDD存储为JVM中的反序列化Java对象。如果RDD不适合内存,则某些分区将不会被缓存,并且每次需要时都会重新计算。这是默认级别。 |
MEMORY_AND_DISK | 将RDD存储为JVM中的反序列化Java对象。如果RDD不适合内存,请存储不适合磁盘的分区,并在需要时从那里读取它们。 |
MEMORY_ONLY_SER (Java和Scala) |
将RDD存储为序列化 Java对象(每个分区一个字节数组)。这通常比反序列化对象更节省空间,特别是在使用快速序列化器时,但读取CPU密集程度更高。 |
MEMORY_AND_DISK_SER (Java和Scala) |
与MEMORY_ONLY_SER类似,但将不适合内存的分区溢出到磁盘,而不是每次需要时动态重新计算它们。 |
DISK_ONLY | 仅将RDD分区存储在磁盘上。 |
MEMORY_ONLY_2,MEMORY_AND_DISK_2等 | 与上面的级别相同,但复制两个群集节点上的每个分区。 |
OFF_HEAP(实验性) | 与MEMORY_ONLY_SER类似,但将数据存储在 堆外内存中。这需要启用堆外内存。 |
注意: 在Python中,存储的对象将始终使用Pickle库进行序列化,因此您是否选择序列化级别并不重要。Python中的可用存储级别包括MEMORY_ONLY
,MEMORY_ONLY_2
, MEMORY_AND_DISK
,MEMORY_AND_DISK_2
,DISK_ONLY
,和DISK_ONLY_2
。
reduceByKey
即使没有用户调用,Spark也会在shuffle操作(例如)中自动保留一些中间数据persist
。这样做是为了避免在shuffle期间节点发生故障时重新计算整个输入。我们仍然建议用户persist
在计划重用RDD时调用生成的RDD。
Spark的存储级别旨在提供内存使用和CPU效率之间的不同折衷。我们建议您通过以下流程选择一个:
如果您的RDD与默认存储级别(MEMORY_ONLY
)保持一致,请保持这种状态。这是CPU效率最高的选项,允许RDD上的操作尽可能快地运行。
如果没有,请尝试使用MEMORY_ONLY_SER
并选择快速序列化库,以使对象更节省空间,但访问速度仍然相当快。(Java和Scala)
除非计算数据集的函数很昂贵,否则它们不会溢出到磁盘,或者它们会过滤大量数据。否则,重新计算分区可能与从磁盘读取分区一样快。
如果要快速故障恢复,请使用复制的存储级别(例如,如果使用Spark来处理来自Web应用程序的请求)。所有存储级别通过重新计算丢失的数据提供完全容错,但复制的存储级别允许您继续在RDD上运行任务,而无需等待重新计算丢失的分区。
Spark会自动监视每个节点上的缓存使用情况,并以最近最少使用(LRU)的方式删除旧数据分区。如果您想手动删除RDD而不是等待它退出缓存,请使用该RDD.unpersist()
方法。
通常,当在远程集群节点上执行传递给Spark操作(例如map
or reduce
)的函数时,它将在函数中使用的所有变量的单独副本上工作。这些变量将复制到每台计算机,并且远程计算机上的变量的更新不会传播回驱动程序。支持跨任务的通用,读写共享变量效率低下。但是,Spark确实为两种常见的使用模式提供了两种有限类型的共享变量:广播变量和累加器。
广播变量允许程序员在每台机器上保留一个只读变量,而不是随副本一起发送它的副本。例如,它们可用于以有效的方式为每个节点提供大输入数据集的副本。Spark还尝试使用有效的广播算法来分发广播变量,以降低通信成本。
Spark动作通过一组阶段执行,由分布式“shuffle”操作分隔。Spark自动广播每个阶段中任务所需的公共数据。以这种方式广播的数据以序列化形式缓存并在运行每个任务之前反序列化。这意味着显式创建广播变量仅在跨多个阶段的任务需要相同数据或以反序列化形式缓存数据很重要时才有用。
广播变量是v
通过调用从变量创建的SparkContext.broadcast(v)
。广播变量是一个包装器v
,可以通过调用该value
方法来访问它的值。
scala:
scala> val broadcastVar = sc.broadcast(Array(1, 2, 3))
broadcastVar: org.apache.spark.broadcast.Broadcast[Array[Int]] = Broadcast(0)
scala> broadcastVar.value
res0: Array[Int] = Array(1, 2, 3)
java:
Broadcast<int[]> broadcastVar = sc.broadcast(new int[] {1, 2, 3});
broadcastVar.value();
// returns [1, 2, 3]
创建广播变量后,应该使用它来代替v
群集上运行的任何函数中的值,这样v
就不会多次传送到节点。此外,在v
广播之后不应修改对象 ,以确保所有节点获得相同的广播变量值(例如,如果稍后将变量发送到新节点)。
累加器是仅通过关联和交换操作“添加”的变量,因此可以并行地有效支持。它们可用于实现计数器(如MapReduce)或总和。Spark本身支持数值类型的累加器,程序员可以添加对新类型的支持。
累加器是仅通过关联和交换操作“添加”的变量,因此可以并行地有效支持。它们可用于实现计数器(如MapReduce)或总和。Spark本身支持数值类型的累加器,程序员可以添加对新类型的支持。
作为用户,您可以创建命名或未命名的累加器。如下图所示,命名累加器(在此实例中counter
)将显示在Web UI中,用于修改该累加器的阶段。Spark显示“任务”表中任务修改的每个累加器的值。
跟踪UI中的累加器对于理解运行阶段的进度非常有用
可以通过分别调用SparkContext.longAccumulator()
或SparkContext.doubleAccumulator()
累积Long或Double类型的值来创建数字累加器。然后,可以使用该add
方法将群集上运行的任务添加到群集中。但是,他们无法读懂它的价值。只有驱动程序可以使用其value
方法读取累加器的值。
下面的代码显示了一个累加器用于添加数组的元素:
scala> val accum = sc.longAccumulator("My Accumulator")
accum: org.apache.spark.util.LongAccumulator = LongAccumulator(id: 0, name: Some(My Accumulator), value: 0)
scala> sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum.add(x))
...
10/09/29 18:41:08 INFO SparkContext: Tasks finished in 0.317106 s
scala> accum.value
res2: Long = 10
虽然此代码使用了对Long类型累加器的内置支持,但程序员也可以通过继承AccumulatorV2来创建自己的类型。AccumulatorV2抽象类有几个必须覆盖的方法:reset
用于将累加器重置为零,add
用于将另一个值添加到累加器中, merge
用于将另一个相同类型的累加器合并到这个中。其他必须覆盖的方法包含在API文档中。例如,假设我们有一个MyVector
表示数学向量的类,我们可以写:
class VectorAccumulatorV2 extends AccumulatorV2[MyVector, MyVector] {
private val myVector: MyVector = MyVector.createZeroVector
def reset(): Unit = {
myVector.reset()
}
def add(v: MyVector): Unit = {
myVector.add(v)
}
...
}
// Then, create an Accumulator of this type:
val myVectorAcc = new VectorAccumulatorV2
// Then, register it into spark context:
sc.register(myVectorAcc, "MyVectorAcc1")
请注意,当程序员定义自己的AccumulatorV2类型时,结果类型可能与添加的元素类型不同。
对于仅在操作内执行的累加器更新,Spark保证每个任务对累加器的更新仅应用一次,即重新启动的任务不会更新该值。在转换中,用户应该知道,如果重新执行任务或作业阶段,则可以多次应用每个任务的更新。
累加器不会改变Spark的惰性评估模型。如果在RDD上的操作中更新它们,则只有在RDD作为操作的一部分计算时才更新它们的值。因此,当在惰性变换中进行时,不保证执行累加器更新map()
。以下代码片段演示了此属性:
val accum = sc.longAccumulator
data.map { x => accum.add(x); x }
// Here, accum is still 0 because no actions have caused the map operation to be computed.