Spark Programming Guide

本文深入探讨Apache Spark的核心概念,包括弹性分布式数据集(RDD)的创建与操作、共享变量的使用,以及如何部署Spark应用至集群。文章强调了RDD在容错与并行处理中的关键作用,详细介绍了如何通过序列化和并行化处理大规模数据集。同时,对如何在Spark中高效使用共享变量和管理数据持久化进行了讲解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Spark Programming Guide

1.Overview

​ 每一个Spark应用程序都包含一个驱动程序,该驱动程序负责运行用户的main函数并且在集群上执行各种并行操作。弹性分布式数据集RDD是Spark中主要的抽象概念,它是跨集群节点划分的元素的集合,可以并行操作。RDD通常是由Hadoop文件系统中的文件或者驱动程序中存在的Scala集合转换而来。用户还可以要求Spark将RDD保留在内存中,以便使其能够在并行操作中有效的重用。最后,RDD能够自动的从故障节点中恢复。

​ 共享变量 shared variables是Spark中第二个抽象概念。在默认情况下,当Spark在不同节点上将并行的运行函数作为一组任务时,它将函数中使用的每个变量的副本发送给每个任务。有时,variable需要在任务之间或者任务与驱动程序之间共享。Spark支持两种类型的共享变量, broadcast variables广播变量(可用于在所有节点上的内存中缓存值)和accumulators累加器(添加到变量的变量),例如 counters and sums。

2.Linking with Spark连接

​ 编写一个Spark应用程序之前,需要在Spark上添加Maven依赖。可通过Maven Central获得Spark:

groupId = org.apache.spark
artifactId = spark-core_2.11
version = 2.2.0

​ 如果需要访问HDFS集群,需要为HDFS版本添加hadoop-client依赖。

groupId = org.apache.hadoop
artifactId = hadoop-client
version = <your-hdfs-version>

​ 最后,需要将一些Spark类导入程序,添加以下行

import org.apache.spark.SparkContext
import org.apache.spark.SparkConf

(在Spark 1.3.0之前,您需要显式导入org.apache.spark.SparkContext._以启用必要的隐式转换。)

3.Initializing Spark

​ Spark程序必须做的一件事就是创建一个SparkContext对象,该对象会告诉Spark如何访问集群。在创建SparkContext对象之前,还需要创建一个SparkConf对象,这包含了有关应用程序的信息。

​ 每个JVM只能激活一个SparkContext,在创建一个新的SparkContext之前,必须先要激活它。

val conf = new SparkConf().setAppName(appName).setMaster(master)
new SparkContext(conf)

appName 是应用程序显示在集群UI上的名称。masterSpark, Mesos or YARN cluster URL, 或者是以本地模式运行的特殊的 “local” 字符串。实际上,当以集群模式运行时,你不想在程序中对master进行编程处理,而是启动spark-submit](http://spark.apache.org/docs/2.2.0/submitting-applications.html)应用程序。当然,对于本地测试和单元测试而言,可以使用“local”来运行Spark处理程序。

Using the Shell

​ 在Spark Shell中,sc variable是一个特殊的可识别的解释器,代表SparkContext。自己创建的SparkContext将不会起作用。你可以使用-master参数设置context连接到哪个服务器,也可以通过将逗号分隔的列表传递–jars参数来奖JARs添加到classpath。您还可以通过在–packages参数中提供逗号分隔的Maven坐标列表,从而将依赖项(例如Spark Packages)添加到Shell会话中。例如,要在正好四个核的机器上运行bin / spark-shell,请使用:

$ ./bin/spark-shell --master local[4]

​ 将code.jar添加到classpath:

$ ./bin/spark-shell --master local[4] --jars code.jar

​ 利用Maven cordinates包含Maven依赖。

$ ./bin/spark-shell --master local[4] --packages "org.example:example:0.1"

​ 运行spark-shell-hellp查看更完整的列表。

4.Resilient Distributed Datasets (RDDs)弹性分布式数据集

​ Spark围绕着弹性分布式数据集(RDD)的概念展开,RDD是可并行操作元素的容错集合。创建RDD的方法由两种:在驱动程序中并行化现有集合,或者引用外部存储系统(例如共享文件系统,HDFS,Hbase或者Hadoop InputFormat的任何数据源)中的数据集。

Parallelized Collections

​ 在驱动程序中可以在现有集合中利用SparkContext’s parallelize方法创建并行化集合,复制集合的元素以形成可以并行操作的分布式数据集。下面是创建包含数字1-5的并行化集合的方法。

val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data)

​ 创建之后,分布式数据集(distData)可以并行操作。例如,我们可能会调用disData.reduce((a,b)=>a+b)以添加数组的元素,稍后会描述对分布式数据集的操作。

​ 并行化集合中最重要的参数是能够将数据集切分的分区数***partitions***。Spark将会为集群的每一个分区运行一个Task。通常,Spark将会根据集群自动设置分区数。但是,你也可以通过将分区数作为辅助参数传递给parallelize (e.g. sc.parallelize(data, 10)).代码中的某些位置使用术语切片 slices(分区的同义词)来保持向后兼容性。

External Datasets(外部数据集)

​ Spark可以为Hadoop支持的任何分布式数据源创建分布式数据集datasets,包括本地系统,HDFS,Cassandra,Hbase,AmazonS3等等。Spark支持text文件,SequenceFiles序列化文件,以及Hadoop 输入类型的数据。

​ 可以使用SparkContext的textFile方法创建文件文件RDD。此方法需要一个URI的文件(本地路径的机器上,或一个hdfs://,s3n://等URL),并读取其作为行的集合。下面是一个示例调用:

scala> val distFile = sc.textFile("data.txt")
distFile: org.apache.spark.rdd.RDD[String] = data.txt MapPartitionsRDD[10] at textFile at <console>:26

​ 一旦创建之后,distFile就可以通过dataset 操作对齐进行操作。例如,我们可以使用map和reduce操作将所有行的大小相加。如下所示:

distFile.map(s => s.length).reduce((a, b) => a + b)

​ 下面是使用Spark读取文件的一些注意事项:

​ 1.如果使用到了本地文件系统上的路径,则必须在工作节点上的相同路径访问该文件。或者将文件复制到所有工作服务器,或者使用网络上的共享文件系统。

​ 2.Spark的所有基于文件的输入方法(包括textFile)都在支持目录,压缩文件和通配符上运行。例如,可以使用:

textFile("/my/directory")textFile("/my/directory/*.txt")textFile("/my/directory/*.gz")

​ 3.该textFile方法还采用可选的第二个参数来控制文件的分区数。默认情况下,Spark为文件的每个块创建一个分区(HDFS中的块默认为128MB),但是您也可以通过传递更大的值来请求更大数量的分区。请注意,分区不能少于块。

除了文本文件,Spark的Scala API还支持其他几种数据格式:

​ 4.SparkContext.wholeTextFiles可以读取包含多个小文本的目录,并将每个小文本作为(文件名,内容)对返回。与textFile相比,它会在每个文件的每一行返回一条记录。分区由数据局部性决定,在某些情况下,数据局部性可能会导致分区文件太少。为了解决此类问题,wholeTextFiles会提供一个可选的第二个参数来控制最小数量的分区。

​ 5.对于SequenceFiles,请使用SparkContext的***sequenceFile[K,V]***方法,其中K和V是文件中键和值的类型。这些应该是Hadoop’s Writable 接口的子类,例如IntWritableText.此外,Spark允许为一些常见的Writables指定native types。例如,***sequenceFile[Int, String]将自动读取***IntWritable***和***Text

​ 6.对于其他Hadoop InputFormats,可以使用 SparkContext.hadoopRDD 方法,它采用任意JobConf和输入格式类,键类,值类。可以基于 “new” MapReduce API (org.apache.hadoop.mapreduce).将SparkContext.newAPIHadoopRDD用于InputFormats。

​ 7.RDD.saveAsObjectFile and SparkContext.objectFile 支持以一种包含序列化Java对象的简单格式保存RDD,尽管它不如Avro这样的专用格式有效,但是它提供了一种保存任何RDD的简便方法。

RDD Operations

​ RDD支持两种类型的操作:transformations(根据现有的 dataset 创建一个新的dataset ),以及actions(对 dataset 进行计算之后,将值返回给驱动程序)。例如,一方面,map是一个转换,它通过一个函数传递每个dataset元素。,并返回一个代表结果的新RDD。另一方面,reduce是一个action,它能够使用某些函数聚集RDD中所有的元素并且返回给驱动程序最终的结果(尽管也有并行的reduceByKey 返回分布式数据集)。

​ 在Spark中所有的 transformations 操作都是惰性的,这些 transformations 操作不会立即计算出结果。相反,它们只会记住应用于一些base dataset(e.g. a file).的转换操作。仅当动作要求将结果返回给驱动程序时才会计算转换。这种设计可以使Spark高效的运行。例如我们能够意识到通过map创建的dataset将会用于reduce,并且将reduce之后的结果返回给驱动程序,而不是将较大的映射数据集返回给驱动程序。

​ 在默认情况下,每次在RDD上执行action操作时,可能都会重新计算每个transformed转换后的RDD。然而,还可以使用 persist (or cache)方法在内存中persist RDD。在这种情况下,Spark会将元素保留在集群中,以便于在下次查询时可以更快地进行访问。也可以将持久化的RDD存储在磁盘上,或者在多个节点之间复制。

Basics

为了说明RDD基础知识,请考虑以下简单程序:

val lines = sc.textFile("data.txt")
val lineLengths = lines.map(s => s.length)
val totalLength = lineLengths.reduce((a, b) => a + b)

​ 第一行从外部文件定义基本RDD。该数据集未加载到内存中或没有采取其他行动:lines仅是文件的指针。第二行定义lineLengthsmap转换的结果。由于transformations操作的惰性,lineLengths并不会立即计算。只有在运行 action操作:reduce时,Spark才会将计算分解为在不同机器上运行的任务,并且每台机器都运行 map的部分操作和局部的reduce操作,最后将结果返回给驱动程序。

​ 如果lineLengths以后还需要使用,可以运行:

lineLengths.persist()

​ 从而使得一直到执行reduce操作之前,lineLengths在第一次计算之后将其保存在内存之中。

Passing Functions to Spark

​ Spark’s API 在很大程度上依赖于在驱动程序中的传递函数放在集群上运行。下面是两种推荐的方法:

  • 匿名函数语法,可用于简短的代码段。

  • 全局单例对象中的静态方法。例如,您可以如下定义object MyFunctions并传递MyFunctions.func1

  • object MyFunctions {
      def func1(s: String): String = { ... }
    }
    
    myRdd.map(MyFunctions.func1)
    

    请注意,虽然也可以在类实例中传递对方法的引用(与单例对象相对),但这需要将包含该类的对象与方法一起发送。例如,考虑:

    class MyClass {
      def func1(s: String): String = { ... }
      def doStuff(rdd: RDD[String]): RDD[String] = { rdd.map(func1) }
    }
    

    在这里,如果我们创建一个新的MyClass实例,并调用doStuff方法就可以了。map里面有引用的 func1方法是MyClass实例,所以整个对象需要被发送到群集。它类似于rdd.map(x => this.func1(x))

    以类似的方式,访问外部对象的字段将引用整个对象:

    class MyClass {
      val field = "Hello"
      def doStuff(rdd: RDD[String]): RDD[String] = { rdd.map(x => field + x) }
    }
    

    rdd.map(x => this.field + x)。它引用了所有内容this。为避免此问题,最简单的方法是将其复制field到局部变量中,而不是从外部访问它:

    def doStuff(rdd: RDD[String]): RDD[String] = {
      val field_ = this.field
      rdd.map(x => field_ + x)
    }
    
Understanding closures

​ 关于Spark的难点之一是在跨集群执行代码时了解 variables变量和方法的范围和生命周期。修改超出其 范围的变量的RDD 操作会引起问题。在下面的示例中, 将查看foreach() 用于增加计数器的代码。但是其他操作也会引起类似的问题。

Example

​ 考虑下面的统计naive RDD元素总和的操作,其行为可能会有所不同,具体取决于是否是在同一JVM中执行的。一个常见的示例是在local模式(--master = local[n])中运行Spark 而不是将Spark应用程序部署到集群(例如,通过将spark-submit提交给YARN)时:

var counter = 0
var rdd = sc.parallelize(data)

// Wrong: Don't do this!!
rdd.foreach(x => counter += x)

println("Counter value: " + counter)
Local vs. cluster modes(本地模式与集群模式)

​ 上述代码的行为是未定义的,可能无法按期工作。为了执行作业,Spark将RDD操作的处理分解为任务,每个任务都由应用程序执行。在执行之前,Spark计算任务的 closureclosure是执行者在RDD上执行其计算时必须可见的那些变量和方法(在本例中为foreach())。此 closure会被序列化并发送给每个执行器。

​ 发送给每个执行程序的 closure中的变量现在是副本。因此,在函数中引用foreach函数的计数器,它不再是驱动程序节点上的计数器。驱动程序节点的内存中仍然存在一个计数器,但是执行者将不再看到该计数器!执行者仅从序列化闭包中看到副本。因此,由于对计数器的所有操作都引用了序列化闭包内的值,所以计数器的最终值仍将为零。

​ 在本地模式中,在某些情况下,该foreach函数将与驱动程序相同的JVM中执行,并且引用相同的原始计数器,并且实际上可能会对其进行更新。

​ 为了确保在此类情况下行为明确,应使用Accumulator。Spark中的累加器专门用于提供一种机制,用于在集群中的各个工作节点之间拆分执行时安全地更新变量。本指南的“累加器”部分将详细讨论这些内容。

​ 通常,closures - constructs像循环或局部定义的方法,不应该用于mutate某些全局状态。Spark不定义或保证从闭包外部引用的对象的behavior of mutations 某些执行此操作的代码可能会在本地模式下工作,但这只是偶然的情况,此类代码在分布式模式下将无法正常运行。如果需要一些全局聚合,请使用累加器。

Printing elements of an RDD

​ 另一个常见用法是尝试使用rdd.foreach(println)或打印RDD的元素rdd.map(println)。在单台机器上,这将生成预期的输出并打印所有RDD的元素。但是在cluster模式下,stdout执行者要调用的输出是现在正在写入执行者的输出stdout,而不是驱动程序上的那个stdout驱动程序将不会显示这些!要打印在驱动器的所有元素,可以使用collect()方法。首先使RDD带驱动器节点,从而rdd.collect().foreach(println)。由于collect()操作会将整个RDD提取到一台计算机上,但这会导致驱动程序内存的不足。如果只需要打印RDD的一些元素,则更安全的方法是使用take()rdd.take(100).foreach(println)

Working with Key-Value Pairs(使用键值对)

​ 尽管大多数Spark操作可在包含任何类型的对象的RDD上运行,但一些特殊操作仅可用于键-值对的RDD。最常见的是分布式“shuffle”操作,例如通过键对元素进行分组或聚合。

​ 在Scala中,这些操作在包含Tuple2对象的RDD上自动可用(该语言的内置元组,仅需编写即可创建(a,b))。PairRDDFunctions类中提供键值对操作,该类会自动包装RDD元组。

​ 例如,以下代码reduceByKey对键值对使用运算来计算文件中每一行文本出现的次数。

val lines = sc.textFile("data.txt")
val pairs = lines.map(s => (s, 1))
val counts = pairs.reduceByKey((a, b) => a + b)

​ 例如,我们也可以使用counts.sortByKey()来按字母顺序对进行排序,最后利用 counts.collect()将它们作为对象数组带回到驱动程序中。

**注意:**在键-值对操作中使用自定义对象作为键时,必须确保自定义equals()方法与匹配hashCode()方法一起使用。有关完整的详细信息,请参见Object.hashCode()文档中的概述。

Transformations

​ 下表列出了Spark支持的一些常见的Transformation。有关详细信息,请参考RDD API文档(ScalaJavaPythonR)和RDD函数对doc(ScalaJava)。

TransformationMeaning
map(func)Return a new distributed dataset formed by passing each element of the source through a function func.返回一个新的分布式数据集,
filter(func)Return a new dataset formed by selecting those elements of the source on which funcreturns true.
flatMap(func)Similar to map, but each input item can be mapped to 0 or more output items (so func should return a Seq rather than a single item).
mapPartitions(func)Similar to map, but runs separately on each partition (block) of the RDD, so func must be of type Iterator => Iterator when running on an RDD of type T.
mapPartitionsWithIndex(func)Similar to mapPartitions, but also provides func with an integer value representing the index of the partition, so func must be of type (Int, Iterator) => Iterator when running on an RDD of type T.
sample(withReplacement, fraction, seed)Sample a fraction fraction of the data, with or without replacement, using a given random number generator seed.
union(otherDataset)Return a new dataset that contains the union of the elements in the source dataset and the argument.
intersection(otherDataset)Return a new RDD that contains the intersection of elements in the source dataset and the argument.
distinct([numTasks]))Return a new dataset that contains the distinct elements of the source dataset.
groupByKey([numTasks])When called on a dataset of (K, V) pairs, returns a dataset of (K, Iterable) pairs. Note: If you are grouping in order to perform an aggregation (such as a sum or average) over each key, using reduceByKey or aggregateByKey will yield much better performance. Note: By default, the level of parallelism in the output depends on the number of partitions of the parent RDD. You can pass an optional numTasks argument to set a different number of tasks.
reduceByKey(func, [numTasks])When called on a dataset of (K, V) pairs, returns a dataset of (K, V) pairs where the values for each key are aggregated using the given reduce function func, which must be of type (V,V) => V. Like in groupByKey, the number of reduce tasks is configurable through an optional second argument.
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks])When called on a dataset of (K, V) pairs, returns a dataset of (K, U) pairs where the values for each key are aggregated using the given combine functions and a neutral “zero” value. Allows an aggregated value type that is different than the input value type, while avoiding unnecessary allocations. Like in groupByKey, the number of reduce tasks is configurable through an optional second argument.
sortByKey([ascending], [numTasks])When called on a dataset of (K, V) pairs where K implements Ordered, returns a dataset of (K, V) pairs sorted by keys in ascending or descending order, as specified in the boolean ascending argument.
join(otherDataset, [numTasks])When called on datasets of type (K, V) and (K, W), returns a dataset of (K, (V, W)) pairs with all pairs of elements for each key. Outer joins are supported through leftOuterJoin, rightOuterJoin, and fullOuterJoin.
cogroup(otherDataset, [numTasks])When called on datasets of type (K, V) and (K, W), returns a dataset of (K, (Iterable, Iterable)) tuples. This operation is also called groupWith.
cartesian(otherDataset)When called on datasets of types T and U, returns a dataset of (T, U) pairs (all pairs of elements).
pipe(command, [envVars])Pipe each partition of the RDD through a shell command, e.g. a Perl or bash script. RDD elements are written to the process’s stdin and lines output to its stdout are returned as an RDD of strings.
coalesce(numPartitions)Decrease the number of partitions in the RDD to numPartitions. Useful for running operations more efficiently after filtering down a large dataset.
repartition(numPartitions)Reshuffle the data in the RDD randomly to create either more or fewer partitions and balance it across them. This always shuffles all data over the network.
repartitionAndSortWithinPartitions(partitioner)Repartition the RDD according to the given partitioner and, within each resulting partition, sort records by their keys. This is more efficient than calling repartition and then sorting within each partition because it can push the sorting down into the shuffle machinery.
Actions
ActionMeaning
reduce(func)Aggregate the elements of the dataset using a function func (which takes two arguments and returns one). The function should be commutative and associative so that it can be computed correctly in parallel.
collect()Return all the elements of the dataset as an array at the driver program. This is usually useful after a filter or other operation that returns a sufficiently small subset of the data.
count()Return the number of elements in the dataset.
first()Return the first element of the dataset (similar to take(1)).
take(n)Return an array with the first n elements of the dataset.
takeSample(withReplacement, num, [seed])Return an array with a random sample of num elements of the dataset, with or without replacement, optionally pre-specifying a random number generator seed.
takeOrdered(n, [ordering])Return the first n elements of the RDD using either their natural order or a custom comparator.
saveAsTextFile(path)Write the elements of the dataset as a text file (or set of text files) in a given directory in the local filesystem, HDFS or any other Hadoop-supported file system. Spark will call toString on each element to convert it to a line of text in the file.
saveAsSequenceFile(path) (Java and Scala)Write the elements of the dataset as a Hadoop SequenceFile in a given path in the local filesystem, HDFS or any other Hadoop-supported file system. This is available on RDDs of key-value pairs that implement Hadoop’s Writable interface. In Scala, it is also available on types that are implicitly convertible to Writable (Spark includes conversions for basic types like Int, Double, String, etc).
saveAsObjectFile(path) (Java and Scala)Write the elements of the dataset in a simple format using Java serialization, which can then be loaded usingSparkContext.objectFile().
countByKey()Only available on RDDs of type (K, V). Returns a hashmap of (K, Int) pairs with the count of each key.
foreach(func)Run a function func on each element of the dataset. This is usually done for side effects such as updating an Accumulator or interacting with external storage systems. Note: modifying variables other than Accumulators outside of the foreach() may result in undefined behavior. See Understanding closures for more details.

​ Spark RDD API提供了某些操作异步的版本,例如 foreach的foreachAsync,它会立即返回FutureAction给调用方,而不是在action操作完成之后阻塞。这可用于管理或等待动作的异步执行。

Shuffle operations

​ Spark中的某些操作会触发一个称为 shuffle的事件,shuffle是Spark中的一种用于数据重新分配的机制,以便能够在跨分区对数据进行分组。这通常涉及到跨执行程序以及机器间的数据复制,这也使得 shuffle 操作 成为复杂且昂贵的操作。

Background

​ 要了解shuffle期间的情况,可以考虑 reduceByKey 操作示例。该 reduceByKey 操作将生成一个新的RDD:将单个键的所有值组合成一个tuple - the key,该键关联的所有值执行reduce函数的结果。难点在于,单个键的所有值并不都是位于同一分区,甚至同一台机器上,但是只有将它们放在同一位置才能计算出结果。

​ 在Spark中,对于特殊的操作而言,数据通常不会跨分区分布到必要的位置。单个任务将在单个分区上运行。因此,要组织所有数据reduceByKey以执行单个reduce任务,Spark需要执行所有操作。它必须从所有分区读取以找到所有键的所有值,然后将分区中的值汇总在一起,以计算每个键的最终结果。----这称为shuffle。

​ 如果希望在shuffle之后能够有规律的对数据进行排序,那么可以使用

mapPartitions 使用.sorted对每个分区进行排序

​ repartitionAndSortWithinPartitions 在对分区进行有效排序的同时进行重新分区

sortBy全局排序的RDD

repartition operations 有可能会带有shuffle的操作,例如 repartition and coalesce,‘ByKey操作(除了计数操作) 例如groupByKey and reduceByKey的, join 操作 例如 cogroup and join.

Performance Impact

RDD Persistence (RDD持久化)

​ Spark最重要的功能之一是能跨操作 across operations.将dataset 数据集持久化在内存中。当persist RDD时,每个节点都会将其计算的所有分区存储在内存中,并在该数据集(或从该数据集派生的数据集)上的其他操作中重用它们。这样可以使以后的操作更快(通常快10倍以上)。Caching是用于迭代算法和快速交互使用的关键工具。

​ 您可以使用persist()cache()方法将RDD标记为要保留。第一次在操作中对其进行计算时,RDD将会被保存在节点上的内存中。Spark的缓存是可容错的,如果RDD的任何分区丢失,那么它将使用最初创建它的transformations自动重新计算。

​ 此外,每个持久化的RDD可以使用不同的存储级别进行存储。完整的存储级别的集合是:

Storage LevelMeaning
MEMORY_ONLYStore RDD as deserialized Java objects in the JVM. If the RDD does not fit in memory, some partitions will not be cached and will be recomputed on the fly each time they’re needed. This is the default level.
MEMORY_AND_DISKStore RDD as deserialized Java objects in the JVM. If the RDD does not fit in memory, store the partitions that don’t fit on disk, and read them from there when they’re needed.
MEMORY_ONLY_SER (Java and Scala)Store RDD as serialized Java objects (one byte array per partition). This is generally more space-efficient than deserialized objects, especially when using a fast serializer, but more CPU-intensive to read.
MEMORY_AND_DISK_SER (Java and Scala)Similar to MEMORY_ONLY_SER, but spill partitions that don’t fit in memory to disk instead of recomputing them on the fly each time they’re needed.
DISK_ONLYStore the RDD partitions only on disk.仅将RDD分区存储在磁盘上。
MEMORY_ONLY_2, MEMORY_AND_DISK_2, etc.Same as the levels above, but replicate each partition on two cluster nodes.
OFF_HEAP (experimental)Similar to MEMORY_ONLY_SER, but store the data in off-heap memory. This requires off-heap memory to be enabled.

Python中的可用存储级别包括MEMORY_ONLY,MEMORY_ONLY_2, MEMORY_AND_DISK,MEMORY_AND_DISK_2,DISK_ONLY,和DISK_ONLY_2。

Which Storage Level to Choose?存储级别的选择

1.如果您的RDD与默认的存储级别(MEMORY_ONLY)相称,请保持这种状态。这是CPU效率最高的选项,允许RDD上的操作尽可能快地运行。

2.如果不是,请尝试使用MEMORY_ONLY_SER选择一个快速的序列化库,以使对象的空间效率更高,但仍可以快速访问。(Java和Scala)

3.尽量不要在磁盘上运行。重新计算分区可能与从磁盘中读取分区一样快。

4.如果想要快速恢复故障,请使用复制的存储级别。

Removing Data(移除数据)

Spark会自动的监控每个节点上缓存的使用情况,以 in a least-recently-used (LRU) 的方式丢弃旧的旧的数据分区drops out old data partitions。如果需要手动删除RDD而不是等待它自动从缓存中剥离,请使用RDD.unpersist()方法。

5.Shared Variables(共享变量)

​ 通常,当传递给Spark操作的函数(例如map或reduce)在远程集群节点上执行时,它将对函数中使用的所有变量的单独副本起作用。这些变量将会复制到每台计算机上,并且远程计算机上变量的更新都不会传播回驱动程序。在各个任务之间支持通用的读写共享变量的效率将变得很低。但是,Spark确实提供了两种有限类型的共享变量: broadcast variables and accumulators.

Broadcast Variables

​ Broadcast variables使程序员可以在每台计算机上保留一个只读变量,而不用随着任务一起发送它的副本。例如,可以使用它们以高效的方式为每个节点提供 large input dataset。 Spark还尝试使用有效的广播算法broadcast algorithms分布式广播变量,以降低通信成本。

​ Spark actions是通过一组阶段执行的,这些阶段由分布式的shuffle操作分隔。Spark能够自动broadcasts每个阶段中任务所需要的公用数据。在运行每个任务之前,以这种方式broadcasted 的数据以序列化形式缓存并反序列化。这意味着仅当跨多个阶段的任务需要相同数据或以反序列化形式缓存的数据非常重要时,显示创建的 broadcast variables 才有用。

​ 对于变量 v ,通过调用 SparkContext.broadcast(v)便会创建它的Broadcast variables。可以通过调用value 方法访问其值。下面的代码显示了这一点:

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)

​ 当创建broadcast variable 之后,在任何函数中都应该使用它来代替 value v,以使 v不会多次传送到节点。

​ 另外,对象 v在broadcast之后不应该被修改,以确保所有节点都具有相同的broadcast(否则,当变量稍后被传送到新的节点,就会发生改变)。

Accumulators

​ Accumulators是仅通过associative and commutative操作“添加的变量”,因此可以并行有效地支持。它们可以用于实现counters (as in MapReduce) 或者 sums.。Spark本身支持数字类型的Accumulators,程序员可以自行添加对新类型的支持。

​ 作为用户,您可以创建命名或者未命名的累加器。如下图所示,已命名的累加器(在示例中为计数器)将会在Web UI界面中显示修改此 accumulator的阶段。Spark将会显示由在任务列表里中t的task修改的每个 accumulator的值。在UI中跟踪 accumulators 对于了解运行阶段的进度可能很有用。

​ 可以通过分别调用SparkContext.longAccumulator()SparkContext.doubleAccumulator() 累加Long或Double类型的值来创建数字accumulators。然后,可以使用add方法将在集群上运行的任务添加到集群中。但是,他们无法读取其值。只有驱动程序可以使用其value方法读取accumulators的值。

​ 下面的代码显示了一个accumulators,用于累加一个数组的元素:

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类型的accumulators的内置支持,但程序员也可以通过对AccumulatorV2进行子类化来创建自己的类型。AccumulatorV2抽象类具有几种必须被覆盖的方法:reset将累加器重置为零,add将另一个值添加到累加器,merge将另一个相同类型的累加器合并到该accumulators中 。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类型时,结果类型可能与所添加元素的类型不同。

​ Accumulators不会改变Spark的惰性评估模型。如果在RDD上的操作中对其进行更新,则仅当将RDD计算为action操作的一部分时,才更新它们的值。因此,当在类似的惰性转换中进行Accumulators更新时,不能保证执行更新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.
//accum仍然是0,因为没有执行actions操作。

二、Deploying to a Cluster(部署到集群)

application submission guide给出了如何submit applications到集群的方法。简而言之,一旦将你的application 打包为JAR文件(for Java/Scala)或者pyor.zip文件的集合(for Python)。该bin/spark-submit`脚本便可以将其提交给任何受支持的集群管理器上。

Launching Spark jobs from Java / Scala(从Java / Scala启动Spark作业)

org.apache.spark.launcher包提供了启动Spark jobs的类作为子进程。

Unit Testing(单元测试)

​ Spark非常适合任何流行的单元测试框架进行测试,只需要在SparkContext在测试中创建一个URL,并设置为local,并运行operations,然后调用SparkContext.stop()将其拆解。确保在finally块或测试框架的tearDown方法中停用context。Spark不支持在同一个应用程序中同时运行两个 contexts。

Where to Go from Here

​ 您可以在Spark网站上看到一些示例Spark程序。此外,Spark在examples目录(ScalaJavaPythonR)中包含几个示例。您可以通过将类名称传递给Spark的bin/run-example脚本来运行Java和Scala示例。例如:

./bin/run-example SparkPi

​ 对于Python示例:

./bin/spark-submit examples/src/main/python/pi.py

​ 对于R示例:

./bin/spark-submit examples/src/main/r/dataframe.R

参考:
1.Spark官网

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值