Spark Programming Guide

Overview

更高的层次上,每个Spark应用由运行main 方法并在集群中执行多种并行操作的driver program组成。Spark主要抽象概念是提供一个 resilient distributed dataset 弹性分布式数据集(RDD)-一个可以并行操作的跨集群节点的元素集合。RDDs从HDFS创建,或在驱动程序中存在的Scala集合,并转化它。用户也可能要求Spark持久一个内存中的RDD,在并行操作中允许有效的重复使用。最后,RDDs自动的恢复失败节点。
Spark的第二种抽象概念是并行操作中使用 shared variables 共享变量。缺省方式,当Spark在不同节点上以一系列任务的方式并行运行一个方法时,它把方法中使用的每个变量的副本传递到每个任务中。有时,一个变量需要在任务中贡献,或在任务间共享,或在驱动程序间。Spark提供两种共享变量类型:broadcast variables(广播变量)-在集群内存中缓存值时使用;accumulators(累加寄存器)-像counters,sums一样只能”added” to的变量。
这个指南展示了每一种Spark支持的语言的特点。很容易跟随Spark的交互shell进行学习-Scala的shell:bin/spark-shell,Python的shell:bin/pyspark


Linking with Spark

Spark 1.3.0使用Scala 2.10。为了使用Scala编写applications,你需要使用兼容的Scala版本(e.g. 2.10.x)。
编写Spark应用,你需要添加Maven支持。Maven Central的Spark支持:

groupId = org.apache.spark
artifactId = spark-core_2.10
version = 1.3.0

另外,如果你想访问HDFS集群,你需要为你的HDFS版本添加一个hadoop-client依赖。一些常见的HDFS版本列表third party distributions

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

最后,你需要在程序中引入一些Spark classes和隐式转换。添加下面这些行:

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

Initializing Spark

Spark程序必须做的第一件事是创建SparkCOntex对象-告诉Spark怎样访问一个集群。要创建SparkContext,首先要建立一个包含你的app信息的SparkConf对象。
每个JVM只能有一个SparkContext。你必须在创建新SparkContext之前,stop()当前活动的SparkContext

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

参数appName是你的app在集群UI中显示的名字。masterSpark, Mesos or YARN cluster URL,或者一个特殊的”local“字符串以本地模式运行。
实际上,当在集群中运行时,你不想在程序中硬编码master,宁可launch the application with spark-submit,然后在那里使用它。然而,针对本地测试和单元测试,你可以传递”local”来在进程内运行Spark。

Using the Shell

Spark Shell中,一个特殊的解释器SparkContext已经为你创建好了,sc变量引用。你自己的SparkContext不会工作。你可以使用--master argument来设置context要连接的master,你也可以添加JARs到classpath,通过传递一个逗号分隔的列表给--jars argument。你也可以添加依赖(e.g. Spark Pacjages)到你的shell session,通过提供一个逗号分隔的maven坐标列表给--packages argument。任何依赖存在附加仓库都可以被传递给--repositories argument。例如,以四核心运行bin/spark-shell,使用:

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

或者,也可以添加code.jar到它的classpath,使用:

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

使用maven坐标引入依赖:

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

运行spark-shell --help,可以得到一个完全选项的列表。在幕后,spark-shell调用更常规的spark-submit script.


Resilient Distributed Datasets (RDDs)

Spark围绕着==resilient distributed dataset (RDD)==概念-一种可以并行操作的容错性的元素集合。有两种创建RDDs的方式:在你的驱动程序中parallelizing一个已存在的集合;或者引用一个外部存储系统(例如:共享文件系统,HDFS,HBase,任何提供Hadoop InputFormat的数据源)的数据集。

Parallelized Collections

并行集合通过在你的驱动程序中已存在的集合(Scala的Seq),调用SparkContext’s parallelize方法创建。从分布式数据集,复制集合的元素-可以并行操作。例如,下面就是怎么创建1 to 5的并行集合:

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

一旦创建,这个分布式的数据集(distData)可以并行的被操作。例如,我们可以调用distData.reduce((a, b) => a + b)合计array中的元素。我们一会儿再描述分布式数据集的上的操作。
并行集合的一个重要的参数是数据集切分成partitions的数量。Spark在集群中对每个分区运行一个任务。典型的是在集群中每个CPU你应该有2-4个分区。通常,Spark会根据你的集群自动设置分区。然而,你也可以手动的设置它-传递第二个参数给parallelize(e.g. sc.parallelize(data, 10))。注意:一些代码的地方使用术语切片(分区的同义词)维持向后兼容。

External Datasets

Spark可以从任何Hadoop支持的存储源创建分布式数据集,包括你的本地文件系统,HDFS,Cassandra, HBase, Amazon S3等等。Spark支持文本文件,SequenceFiles,和其它任何Hadoop InputFormat
文本文件RDDs可以调用SparkContext’s textFile方法来创建。这个方法接受一个文件URI(机器上的local path,或在hdfs://s3n://,等等其它URI),然后像lines集合读取它。下面是调用示例:

scala> val distFile = sc.textFile("data.txt")
distFile: RDD[String] = MappedRDD@1d4cee08

一旦创建,distFile可以通过数据集操作起作用(被执行)。例如,我们可以使用mapreduce操作合计lines的sizes:distFile.map(s => s.length).reduce((a,b) => a + b)
一些使用Spark读取文件的注意事项:

  • 如果使用本地文件路径,这个文件在工作节点上必须也可以以相同的路径被访问。
  • 所有基于文件的输入方法,包括textFile,支持目录,压缩文件,通配符。例如,你可以使用textFile("/my/directory"),textFile("/my/directory/*.txt"), textFile("/my/directory/*.gz")
  • textFile方法也接受一个可选的第二参数来控制文件切片数。缺省的,Spark针对每个文件block(HDFS中blocks的默认大小是64M)创建一个切片,但是你也可以传递一个更大的值取得一个更高的切片值。注意,切片数不能少于blocks数。

除了文本文件,Spark的Scala API也至此一些其它数据格式:

  • SparkContext.wholeTextFiles让你可以读取包含多个小文本文件的目录,然后以(filename, content)对的格式返回它们。与textFile对比,后者会返回每个文件每行的记录。
  • SequenceFiles,使用SparkContext的sequenceFile[K, V]方法,kv是文件中key和valus的类型。这些应该是Hadoop Writable接口的子类, 例如 IntWritableText。另外,Spark也允许你为一些普通Writables指定本地类型;例如,sequenceFile[Int, String]会自动读取IntWritbles 和 Texts。
  • 对于其它Hadoop InputFormats,你可以使用SparkContext.hadoopRDD方法-接受一个专用的JobConf和input format class,key class,value class。使用你的输入源用相同的方法设置Hadoop Job。你也可以对Inputformats使用基于新MapReduce API(org.apache.hadoop.mapreduce)的SparkContext.newAPIHadoopRDD方法。
  • RDD.saveAsObjectFileSparkContext.objectFile支持把RDD保存在由序列化Java对象组成的一个简单格式中。然而这不像专门的格式如Avro那样有效,Avro提供一个简单的方式来保存任意RDD。

RDD Operations

RDDs支持两种操作类型:transformations-从存在的数据集创建一个新数据集;actions-在数据集上运行一个计算后返回一个值到驱动程序。例如,map是一个transformations-把数据集中每个元素传递到方法中,返回代表结果的新RDD。另一方面,reduce是一个action-使用一些方法合计所有RDD的元素,然后返回最后的结果到驱动程序(尽管还有个并行的reduceByKey返回一个分布式数据集)。
Spark中所有transformations都是lazy,因此它们不能立即计算出结果。它们只是记录应用在一些基本数据集(e.g. file)上的transformations。Transformations只有当action要求一个结果返回到驱动程序时才被计算。这种设计使Spark运行的更有效率-例如,我们可以这样理解:通过map创建的一个数据集会被使用在一个reduce中,然后只是返回reduce的结果到驱动,好过一个巨大的映射数据集。
缺省的,每次在一个transformed RDD运行一个action时,都会重新计算一次。然而,你也可以使用persist(或cache)方法persist一个RDD到内存,在这种情况下Spark会把元素存储在集群上,这样下次查询就可以更快的被访问。也支持把RDDs持久到disk,或者复制到多个节点。

Basics

为了举例说明RDD基础,考虑下面这个简单程序:

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

第一行从外部文件定义一个基本RDD。这个数据集不会加载到内存或者有其他行为:lines仅仅是一个文件指针。第二行将map转换的结果定义为linesLengths。此外,lineLengths不是立即计算,应该是延迟的。最后,我们运行reduce,一个action。此时,Spark把计算分解到任务,在不同的机器运行,然后每个机器运行它的map部分和本地reduce,返回只是它的给驱动程序的答复。
如果我们一会儿还要再次使用lineLengths,可以添加:

lineLengths.persist()

使用在reduce之前,lineLengths会在第一次计算后被保存在内存中。

Passing Functions to Spark

Spark的API很倚重在集群中的驱动程序中传递函数。有两种推荐方式:

  • Anonymous function syntax-可以被使用在短代码段中。

  • 全局单例对象中的静态方法。例如,你可以定义object MyFunctions,然后传递MyFunctions.func1,就像下面:

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

myRdd.map(MyFunctions.func1)

注意:也可以传递引用到类(对应一个单例object)实例的方法中,这需要跟随方法一起发送这个类的对象。例如,考虑下面:

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

这里,如果我们创建MyClass,然后调用doStuff,这个MyClass实例中的map引用了这个实例中的func1方法,那么就需要发送整个对象到集群中。等同于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)
}

==Important==
1. Issue:访问外部域时,会引用到外部域所在的整个对象
2. Avoid:复制field到本地变量

Working with Key-Value Pairs

虽然工作在RDDs上大部分Spark操作包含了任意类型的对象,但一些特殊操作只在Key-Value对格式的RDDs起作用。最常见的操作是分布式的”洗牌”操作,像通过key分组或聚集。
在Scala中,这些操作会自动在RDDs上起作用,包含Tuple2 objects(语言中内建的tuples,通过简单编写(a, b)创建),一旦你importorg.apache.spark.SparkContext._到你的程序中,就会启用Spark的隐式转换。Key-value对儿操作起作用在http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.rdd.PairRDDFunctions“>PairRDDFunctions类-如果你引入这个转换将自动包装一个元组RDD。
例如,下面代码使用reduceByKey操作key-value对儿计算文件中每行文本出现的次数:

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

我们也可以使用counts.sortByKey(),例如,按字母排序对儿,最后使用counts.collect()把他们以数组对象形式传回驱动程序。
注意:当你在key-value对儿操作中使用自定义对象做key时,你必须保证一个自定义equals()方法和一个相匹配的hashCode()方法。有关详情,请见Object.hashCode() documentation

==Important==
1. reduceBykey()sortByKey()等操作是key-value格式RDD特有的操作
2. reduceBykey()sortByKey()操作是transformation,不是action
3. 需要再做collect()之类的action操作,执行并返回真实数据集。

Transformations

下面列表列举了Spark中一些常见的transformations。参考RDD API doc(Scala, Java, Python)和PairRDDFunctions doc(Scala, Java)。

TransformationMeaning
map(func)返回一个新的分布式数据集-由通过把源数据中每个元素都传递给func方法后组成.
filter(func)返回一个新数据集-由源数据中func方法会返回true的元素组成
flatMap(func)与map相似,但是每个输入元素会被映射到0或者更多输出元素(所以func会返回一个Seq而不是一个单一的元素)
mapPartitions(func)与map相似,但是独自运行在RDD的每个分区(block),所以当运行在一个T类型的RDD上时,func类型必须是Iterator<T> => Iterator<U>类型的
mapPartitionsWithIndex(func)与mapPartitions相似,但是也会提供一个表示分区索引整数给func,所以当运行在一个T类型的RDD上时,func的类型必须是(Int, Iterator<T> => Iterator<U>)
sample(withReplacement, fraction, seed)使用一个给出的随机数生成器种子,替换或者不替换的,抽样数据的一个分数fraction
union(otherDataset)返回一个包含入参的元素和源数据集的元素的聚集的新数据集
intersection(otherDataset)返回一个包含入参的元素和源数据集的元素的交集的新数据集
distinct([numTasks])返回一个包含源数据集distinct元素的新数据集
groupByKey([numTasks])当被(K, V)数据集调用,会返回一个(K, Iterable<V>)数据集. 注意:如果你在每个key上分组为了执行聚合(sum或average),使用reduceByKey()aggregateByKey()会有更好的性能. 注意:缺省的,输入的并行等级依赖于父RDD的分区数量。你可以传递一个可选的numTasks参数设置一个不同的任务数量
reduceByKey(func, [numTasks])当被一个(K, V)数据集调用,会返回一个(K, V)-使用给出的reduce方法func(类型是(V, V) => V), 聚集每个key对应的值。像groupByKey()一样,通过一个可选的第二参数配置reduce的任务数量
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks])当被一个(K, V)数据集调用,返回一个(K, U)数据集-使用给出的合并方法和独立的’zero’值,聚集每个key对应的value。允许聚集值的类型不同于输入值的类型,为了不必要的内存分配。像groupByKey()一样,通过一个可选的第二参数配置reduce的任务数量
sortByKey([ascending], [numTasks])当一个K implements Ordered的(K, V)数据集被调用,返回一个以Keys递增或递减顺序排序的(K, V)数据集,由Boolean参数ascending指定
join(otherDataset, [numTasks])(K, V)(K, W)类型数据集调用时,返回一个(K, (V, W))类型数据集。通过leftOuterJoin()rightOuterJoin()fullOuterJoin()支持Outer joins
cogroup(otherDataset, [numTasks])当被(K, V)(K, W)类型数据集调用时,返回一个tuple(K, (Iterable<V>, Iterable<W>))类型的数据集。这个操作也可以是groupWith()
cartesian(otherDataset)当一个T或U类型的数据集调用时,返回一个(T, U)类型的数据集
pipe(command, [envVars])通过管道把RDD分区输送到shell控制台,例如一个Perl或bash脚本。RDD元素被写入进程的标准输入和行输出到它的标准输出,以字符串RDD形式返回。
coalesce(numPartitions)把RDD的分区数量减少到numPartitions。 对于过滤出一个巨大的数据集后,更高效的执行操作很有用。
repartition(numPartitions)随机的重新洗牌RDD中的数据,创建不多也不少的分区,在他们之间平衡。这总会重新洗牌网络上所有的数据。
repartitionAndSortWithinPartitions(partitioner)根据给出的partitioner重新分配RDD,在每个结果分区里面,用它们的keys进行排序。这比调用repartition更高效,而且因为它可以把排序也放入每个洗牌机器,所以在每个分区中排序

Actions

下面是的列表列出了Spark支持的常用actions。参考了RDD API doc(Scala, Java, Python) 和 PairRDDFunctions doc(Scala, Java)

ActionMeaning
reduce(func)使用func方法(获取2个参数返回1个)聚集数据集的元素。这个方法应该是可交换的和可合并的,那么它就可以正确的并行计算
collect()在驱动程序中以数组形式返回数据集的所有元素。这通常很有用,在filter以后或者其它会返回数据的一个十分小的子集的操作以后。
count()返回数据集中元素的数量
first()返回数据集的第一个元素(类似take(1))
take(n)以数组形式返回数据集的前n个元素
takeSample(withReplace, num, [seed])以数组形式返回数据集num个元素的随机样本,带或者不带replacement,可选的提前指定一个随机数生成器seed
takeOrdered(n, [ordering])返回数据集前n个元素,使用自然排序或自定义comparator
saveAsTextFile(path)以文本文件(或一系列文本文件)形式,把数据集中元素写入到一个给出的本地文件系统目录、HDFS或者其它任何Hadoop支持的文件系统。Spark会调用toString()将每个元素转化成文件中的一行文本
saveAsSequenceFile(path)(Java and Scala)以Hadoop SequenceFile格式,把数据集元素写入到一个给出的本地文件系统、HDFS或其他任何Hadoop支持的文件系统。这在实现了Hadoop的Writable Interface的key-value格式RDDs有效。在Scala中,对可以隐含转化成Writable的类型(Spark对基本类型像Int, Double, String等待包含了转换),同样有效
savaAsObjectFile(path)把数据集元素写入一个使用Java序列化的简单格式-以后可以使用SparkContext.objectFile()加载
countByKey()只对(K, V)类型RDDs有效。返回一个(K, Int)格式的hashmap-计算每个key的个数
foreach(func)返回针对每个数据集元素的方法func()后的结果。这通常会有副作用-像更新一个寄存器变量(见下文)或与外部存储系统交互

RDD Persistence

Spark一个重要能力是persisting(或caching)一个数据集到内存,在一系列操作中使用。当你持久一个RDD,每个节点存储RDD任意一个分区,在内存中计算,然后再在这个数据集(或衍生出的数据集)的actions中重新使用。这会让后面的actions更快(一般快10倍)。Caching是迭代算法和交互使用的关键工具。
你可以使用persis()cache()方法使一个RDD持久化。第一次在action中计算后,它就会被保持在节点的内存中。Spark的缓存是可容错的-如果任意一个RDD分区丢失了,它会使用当初创建它的transformations自动重新计算。
另外,允许对每个持久的RDD使用不同的storage level存储,例如:持久化数据集到磁盘,持久化它到内存-但要以序列化Java对象形式,在节点间复制它,或存储它off-heapTachyon。这些级别可以通过传递一个StorageLevel(Scala, Java, Python)对象给persis()方法来设置。cache()方法使用默认的存储级别-StorageLevel.MEMORY_ONLY(存储非序列化对象到内存)。完整的存储基本如下:

Storage LevelMeaning
MEMORY_ONLY以非序列化Java对象形式存储在JVM中。如果RDD不适合在内存中,一些分区将不会被缓存,从而在每次需要它们时都会被重新计算。默认级别
MEMORY_AND_DISK以非序列化Java对象形式存储在JVM中。如果RDD不适合在内存中,存储不适合分区到磁盘,当需要时从那里被读取
MEMORY_ONLY_SER以序列化Java对象形式存储在JVM中(每个分区一个byte数组)。这通常比非序列化对象更节省空间,特别是当使用fast serializer时,但是读取时会更耗CPU
MEMORY_AND_DISK_SER类似于MEMORY_ONLY_SER,但是会把不适合内存的分区存储到磁盘,而不是在每次需要它们时再计算
DISK_ONLY只存储RDD分区到磁盘
MEMORY_ONLY_2, MEMORY_AND_DISK_2, etc.和上面的级别一样,但是复制每个分区到2个集群节点
OFF_HEAP (experimental)以序列化格式存储RDD到Tachyon。相对于MEMORY_ONLY_SER,OFF_HEAP减少GC的花销,并且允许executor变的更小而且共享内存池,让它在大内存或多并发应用的环境中更有吸引力。此外,因为RDDs驻留在Tachyon,executor的崩溃不会导致丢失内存中的缓存。以这种模式,Tachyon中内存是可废弃的。因此,Tachyon不会试图去重建逐出内存的block

注意:在Python中,存储的对象都是被Pickle库序列化的,因此不需要关心你是否选择了一个序列化级别。
Spark也会自动持久化一些shuffle操作(如reduceByKey)的中间数据,即使用户没有调用persis()方法。这样做是为了避免,如果在shuffle的期间一个节点出错,再重新计算全部输入。我们仍然建议用户对结果RDD调用persis()方法,如果他们准备再次使用。

Which Storage Level to Choose?

Spark存储打算提供内存利用率和CPU利用率之间不同的权衡。我们建议通过下面的过程选择一个合适的:
- 如果你的RDD很适合默认存储级别(MEMORY_ONLY),那就选择默认。这是CPU利用率最高的,让RDDs上的操作尽可能的快。
- 如果不适合默认级别,试着使用MEMORY_ONLY_SER,然后选择a fast serialization库提高对象的空间使用率,但是仍相当快的访问
- 除非计算数据集的函数花费很大,或者要过滤大量数据,不然不要存储到磁盘。否则,重新计算一个分区可能会和从磁盘中读取它一样慢
- 如果你需要更快的错误恢复(例如:如果使用Spark处理一个web app的请求),使用重复存储级别。所有的存储级别都可以使用重新计算丢失的数据提供完全容错,但是重复存储级别让你不必等待重新计算一个丢失的分区,就可以在RDD上继续运行任务。
- 在大内存或多应用的环境中,实验性的OFF_HEAP模式有几个优势:
- 它允许运行多个executors来共享Tachyon中同一个内存池
- 它明显的减少GC的开销
- 如果单个executors崩溃,缓存数据不会丢失

Removing Data

Spark自动监控每个节点的使用情况,然后使用最少使用原则(least-recently-used)LRU删除老数据分区。如果你想手动删除一个RDD,来替代等待它被缓存放弃,使用RDD.unpersis()方法。


Shared Variables

通常,当一个传递给Spark操作(例如mapreduce)的函数在远程集群节点上运行的时候,Spark操作工作在 函数中使用变量的独立副本。这些变量被复制到每台机器上,而且远程机器上变量的更新不会传递回驱动程序。通常,在任务间读-写分享变量是低效的。然而,Spark还是为两个常见的使用模式提供了两种有限的分享变量类型:广播变量broadcast variable 和 累加器accumulators

Broadcast Variables

广播变量允许程序员在每个机器缓存一个只读的变量,而不是随着任务一起传递它的拷贝。例如,利用广播变量,以一种高效的方式将一个大输入数据集的拷贝传递到每个节点。Spark也试图使用高效的广播算法来分发广播变量,为了减少通信成本。
使用SparkContext.broadcast(v)从变量v创建广播变量。广播变量是v的一个包装,它的值可以通过调用value()方法访问。下面的代码说明了这些:

scala> val broadcastVar = sc.broadcast(Array(1, 2, 3))
broadcastVar: org.apache.spark.broadcast.Broadcast[Array[Int]]

scala> broadcastVar.value
res0: Array[Int] = Array(1, 2, 3)

广播变量创建后,我们可以在集群上的函数中使用广播变量来替代v值,不用再传递变量v到每个节点。另外,对象v在广播后不能被修改,为了确保所有节点获得相同的广播变量的值(例如,如果后来这个变量传递到一个新节点)。

Accumulators

累加器是一种只能通过关联操作进行”加”操作,因此它能高效的应用在并行操作中。它们能用来实现counters(像在MapReduce)或sums。Spark原生支持数值类型的累加器,而且程序员可以添加新类型的支持。如果创建有名字的累加器,它们可以显示在Spark的UI。这有利于理解运行阶段的过程(注意:Python中还没支持)。
可以通过调用SparkContext.accumulator(v)从初始值v创建一个累加器。运行在集群上的任务就可以添加值给它,通过使用add方法或+=操作符(在Scala和Python中)。然而,它们不能读取它的值。只有驱动程序才能读取累加器的值,使用它的value()方法。
下面的代码,展示了如何利用累加器合计一个数组中的元素:

scala> val accum = sc.accumulator(0, "My Accumulator")
accum: spark.Accumulator[Int] = 0

scala> sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum += x)
...
10/09/29 18:41:08 INFO SparkContext: Tasks finished in 0.317106 s

scala> accum.value
res2: Int = 10

虽然这段代码使用了Int类型累加器的内建支持,但是程序员也可以使用子类化AccumulatorParam创建他们自己的累加器类型。AccumulatorParam接口有两个方法:zero为你的数据类型提供一个”0值”;addInPlace方法把两个值相加。例如,假设我们有一个vectorclass代表数学上的向量,我们可以这样写:

object VectorAccumulatorParam extends AccumulatorParam[Vector] {
  def zero(initialValue: Vector): Vector = {
    Vector.zeros(initialValue.size)
  }
  def addInPlace(v1: Vector, v2: Vector): Vector = {
  v1 += v2
  }
}

// Then, create an Accumulator of this type:
val vecAccum = sc.accumulator(new Vector(...))(VectorAccumulatorParam)

在Scala中,Spark也支持使用更通用的Accumulable接口来累积数据,结果类型不同于累加的元素类型(例如:聚集元素建立一个列表),而且SparkContext.accumulableCollection方法累加常见的Scala集合类型。
因为累加器的更新只能在action中执行(action only),Spark保证每个任务对累加器的更新只能应用一次,即重启的任务不会更新这个值。在transformations中,用户会意识到如果tasks或jobs阶段被重新执行,每个任务的更新会应用多次。
累加器不会改变Spark的延迟计算模型。如果他们被一个在RDD上操作更新,它们的值只更新一次,作为一个action的一部分RDD被计算。因此,当使用一个延迟transformation-像map(),累加器的更新不能保证被执行 。下面的代码片段演示了这个性质:

val acc = sc.accumulator(0)
data.map(x => acc += x; f(x))
// Here, acc is still 0 because no actions hava cause the 'map' to be computed.

==Important:==
- Accumulators的值只有在action后才会被更改,符合Spark的设计哲学-lazy evaluation


Deploying to a Cluster

Application submission guide描述了怎样提交应用到集群中。简而言之,一旦你打包你的应用成JAR(Java/Scala)或一系列.py.zip文件,bin/spark-submit脚本让你可以提交它到任何集群管理。


Unit Testing

Spark对使用任何流行的单元测试框架进去单元测试都是友好的。在你的测试中,简单的使用master URL设置到local来创建一个SparkContext,运行你的操作,然后调用SparkContext.stop()撕掉它。为了确保你停止了context,使用finally代码块或者测试框架的tearDown方法,因为Spark不支持两个context在同一个程序中并行运行。


Migrating from pre-1.0 Versions of Spark


Where to Go from Here

可以阅读example Spark programs。另外,Spark包含几个样例在examples目录(Scala, Java, Python)。你可以运行Java和Scala样例,通过传递class namebin/run-example脚本;实例:

./bin/run-example SparkPi

Python实例,使用spark-submit替代:

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

为帮助程序优化,configurationtuning指南提供了最好的实践信息。它们对确保你的数据以有效的格式存储在内存特别的重要。为了帮助部署,cluster mode overview描述分布操作相关的组件和支持的集群管理。
最后,完整的可用API文档Scala, Java, Python

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值