Spark 2.0.2 学习笔记

本篇博文译自Spark2.0.2官方文档,以供自己学习及大家参考,如转载请注明。

Spark Programming Guide

综述

在高层次上,每个Spark应用程序都包含一个驱动程序,该程序运行用户的main函数并在集群上执行各种并行操作。Spark提供的主要抽象是弹性分布式数据集(RDD),它是跨集群节点分区的可以并行操作的元素的集合。 RDD通过从Hadoop文件系统(或任何其他Hadoop支持的文件系统)中的文件或驱动程序中的现有Scala集合并对其进行转换来创建。 用户也可以要求Spark在内存中持久化一个RDD,允许它在并行操作中被有效地重用。 最后,RDD自动从节点故障中恢复。

此页面上的所有示例都使用Spark发行版中包含的示例数据,并且可以在spark-shell中运行。

初始化Spark

Spark程序必须做的第一件事是创建一个SparkContext对象,它告诉Spark如何访问集群。 要创建SparkContext,首先需要构建一个包含有关应用程序信息的SparkConf对象

每个JVM只能有一个SparkContext。 您必须在创建新的SparkContext之前调用stop()。

val conf = new SparkConf().setAppName(appName).setMaster(master)
new SparkContext(conf)
appName参数是应用程序在集群UI上显示的名称。 master是Spark,Mesos或YARN集群URL,或者是以本地模式运行的特殊“local”字符串。 在实践中, 当在集群上运行时,您不想在程序中硬编码主程序,而是使用spark-submit启动应用程序接收它。 但是,对于本地测试和单元测试,您可以传递“local”来运行Spark进程。

使用Spark-shell

在Spark shell中,已经为您创建了一个特殊的解释器感知型SparkContext,该变量名为sc。 使你自己创建的SparkContext不工作。 您可以使用--master参数设置上下文连接到哪个主机,并且可以通过将逗号分隔的列表传递到--jars参数来将JAR添加到类路径。 您还可以通过向--packages参数提供逗号分隔的maven坐标列表,将依赖关系(例如Spark Packages)添加到您的shell会话。 可以将存在依赖关系的任何其他存储库(例如Sonatype)传递到--repositories参数。 例如,要在四个内核上运行bin / spark-shell,请使用:

$ ./bin/spark-shell --master local[4]
或者使用如下方式将code.jar添加到classpath:

$ ./bin/spark-shell --master local[4] --jars code.jar
有关选项的完整列表,请运行spark-shell --help。 在幕后,spark-shell调用更一般的spark-submit脚本。

弹性分布式数据集(RDDs)

Spark围绕着弹性分布式数据集(RDD)的概念展开,这是一个可以并行操作的容错容器集合。 有两种方法来创建RDD:并行化(parallelize方法)驱动程序中的现有集合,或引用外部存储系统中的数据集,例如共享文件系统,HDFS,HBase或提供Hadoop InputFormat的任何数据源。

并行化的集合

通过在驱动程序(Scala Seq)中的现有集合上调用SparkContext的parallelize方法来创建并行集合。 集合的元素被复制以形成可以并行操作的分布式数据集。 例如,下面创建了一个包含数字1到5的并行化集合:

val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data)
一旦创建, 分布式数据集(distData)可以并行操作。 例如,我们可以调用distData.reduce((a,b)=> a + b)将数组的元素相加。 我们稍后描述对分布式数据集的操作。

并行集合的一个重要参数是将数据集切割到的分区数。 Spark将为集群的每个分区运行一个任务。 通常,您需要为集群中的每个CPU分配2-4个分区。 通常,Spark尝试根据您的集群自动设置分区数。 但是,您也可以手动设置它作为第二个参数进行并行化(例如sc.parallelize(data,10))。 注意:代码中的一些地方使用术语slice(分区的同义词)来保持向后兼容性。

外部数据集(Dataset)

Spark可以从Hadoop支持的任何存储源创建分布式数据集,包括本地文件系统,HDFS,Cassandra,HBase,Amazon S3等。Spark支持文本文件,SequenceFiles和任何其他Hadoop InputFormat。

文本文件RDD可以使用SparkContext的textFile方法创建。 此方法接受文件的URI(机器上的本地路径或hdfs://,s3n://等URI),并将其读为行的集合。 这里是一个调用示例:

scala> val distFile = sc.textFile("data.txt")
distFile: org.apache.spark.rdd.RDD[String] = data.txt MapPartitionsRDD[10] at textFile at <console>:26
一旦创建,可以在distFile执行数据集操作。 例如,我们可以使用map和reduce操作将所有行的长度相加如下:distFile.map(s => s.length).reduce((a,b)=> a + b)。

有关使用Spark读取文件的一些注意事项:

`如果在本地文件系统上使用路径,则文件也必须在工作节点上的相同路径上可访问。 将文件复制到所有工作站或使用网络安装的共享文件系统。

`Spark的所有基于文件的输入方法(包括textFile)都支持在目录,压缩文件和通配符上运行。 例如,可以使用textFile(“/ my / directory”),textFile(“/ my / directory / *。txt”)和textFile(“/ my / directory / *.gz”)。

`textFile方法还使用可选的第二个参数来控制文件的分区数。 默认情况下,Spark为文件的每个块(如何知道分成多少块)创建一个分区(HDFS默认为64MB),但是你也可以通过传递一个更大的值来请求更多的分区。 请注意,您不能有比块更少的分区(最少为每个块创建一个分区)

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

·SparkContext.wholeTextFiles允许您读取包含多个小文本文件的目录并将其作为(文件名,内容)对返回。 这与textFile相反,textFile将在每个文件中每行返回一条记录。

·对于SequenceFiles,使用SparkContext的sequenceFile [K,V]方法,其中K和V是键和文件中的值的类型。 这些应该是Hadoop的Writable接口的子类,如IntWritable和Text。 此外,Spark允许你为几个常见的Writables指定本地类型; 例如,sequenceFile [Int,String]将自动读取IntWritables和Texts。

·对于其他Hadoop InputFormats,您可以使用SparkContext.hadoopRDD方法,该方法接受任意的JobConf和input format类,键类和值类。使用与使用输入源的Hadoop作业相同的方式进行设置。 您还可以为基于“新的”MapReduce API(org.apache.hadoop.mapreduce)的InputFormats 使用SparkContext.newAPIHadoopRDD。

·RDD.saveAsObjectFile和SparkContext.objectFile支持以包含序列化Java对象的简单格式保存RDD。 虽然这不像像Avro这样的专用格式有效,但它提供了一种保存任何RDD的简单方法。

RDD操作:transformation和action

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

Spark中的所有转换都是懒惰的,因为他们不会马上计算它们的结果。相反,他们只记住(DAG??)应用于一些基础数据集(例如文件)的转换。仅当操作(action)需要将结果返回到驱动程序时(对它们调用action操作),才会计算转换。这种设计使Spark能够更有效地运行。例如,我们可以意识到通过map创建的数据集将在reduce中使用,并且只返回reduce到驱动程序的结果,而不是较大的映射数据集。

默认情况下,每次对其执行操作时,都可以重新计算每个已转换的RDD。但是,您还可以使用persist(或cache)方法在内存中持久化一个RDD,在这种情况下,Spark将保持集群上的元素,以便在下次查询它时获得更快的访问速度。还支持在磁盘上持久存储RDD,或者跨多个节点进行复制。

基础知识

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

val lines = sc.textFile("data.txt")
val lineLengths = lines.map(s => s.length)
val totalLength = lineLengths.reduce((a, b) => a + b)
第一行定义来自外部文件的基本RDD。 此数据集未加载到内存中或以其他action操作: lines仅仅是指向文件的指针。 第二行定义lineLength作为map变换的结果。 同样, 由于懒惰,lineLength不会立即计算。 最后,我们 运行reduce,这是一个action。 在这一点上,Spark将计算分解为在每个单独的机器上运行的任务,每个机器运行它的一部分map和本地reduce,只向驱动程序 返回它的计算结果

如果我们还想以后再次使用lineLengths,我们可以添加:

lineLengths.persist()
这样一来,之前的reduce将导致lineLength在第一次计算后保存在内存中。

向Spark传递函数

Spark的API在很大程度上依赖于在驱动程序中传递函数来在集群上运行。 有两种推荐的方法:

·匿名函数 语法,可用于短代码。

·全局单例对象中的静态方法。 例如,您可以定义对象Functions,然后传递Functions.func1,如下所示:

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

myRdd.map(MyFunctions.func1)
注意,虽然也可以在类实例(而不是单例对象)中传递对方法的引用,但这需要发送包含该类的对象以及方法(objName.funcName)。 例如,考虑:

这里,如果我们创建一个新的MyClass实例并调用doStuff,doStuff()里的map引用了MyClass实例的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。 为了避免这个问题,最简单的方法是将字段复制到方法的本地变量中,而不是从外部访问它:

def doStuff(rdd: RDD[String]): RDD[String] = {
  val field_ = this.field
  rdd.map(x => field_ + x)
}
了解闭包

Spark的一个更难的事情是在跨集群执行代码时理解变量和方法的范围和生命周期 修改其范围之外的变量的RDD操作可能是混淆的常见原因。 在下面的例子中,我们将看看使用foreach()来递增计数器的代码,但是对于其他操作也会出现类似的问题。

示例

考虑下面的单纯RDD元素求和,这可能会根据执行是否发生在同一个JVM中而有所不同。 一个常见的例子是在本地模式下运行Spark(--master = local [n])versus (vs.)将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对集群模式

上述代码的行为是不确定的,可能无法按预期工作。为了执行作业,Spark将RDD操作的处理分解为任务,每个任务由执行器执行。在执行之前,Spark计算任务的闭包。闭包是执行器对RDD(本例为foreach())执行计算时必须可见的那些变量和方法。这个闭包被序列化并发送到每个执行器。

发送给每个执行器的闭包中的变量现在是副本,因此,当在foreach函数中引用counter时,它不再是驱动程序节点上的计数器。驱动程序节点的内存中还有一个counter,但对执行器不再可见。执行器只看到来自序列化闭包的副本。因此,计数器的最终值将仍为零,因为计数器上的所有操作都引用序列化闭包中的值。

在local模式下,在某些情况下,foreach函数实际上将在与驱动程序相同的JVM中执行,并且将引用相同的原始计数器,并且实际上可以更新它。

为了确保在这些情况下良好定义的行为,应该使用累加器(Accumulator)。 Spark中的累加器专门用于提供一种机制,用于在集群中的工作节点上拆分执行(execution)时安全地更新变量。本指南的“累加器”部分将更详细地讨论这些内容。

一般来说,闭包 - 循环或本地定义的方法之类的构造不应该用于改变某些全局状态。 Spark不会定义或保证从闭包外部引用的对象的突变行为。一些代码,这可能工作在本地模式,但这只是偶然,在分布式模式中,这样的代码将不会像预期的那样。如果需要某些全局聚合,请使用累加器

打印RDD元素

另一个常见的习惯是尝试使用rdd.foreach(println)或rdd.map(println)打印出RDD的元素。 在单个机器上,这将生成预期的输出并打印所有RDD的元素。 但是,在集群模式下,执行器调用stdout的输出现在正在写入执行器的stdout,而不是驱动程序上的stdout,因此驱动程序上的stdout不会显示这些!要打印驱动程序上的所有元素,可以使用collect()方法首先将RDD带到驱动程序节点:rdd.collect().foreach(println)。 但是,这可能会导致驱动程序耗尽内存,因为collect()获取整个RDD到单个机器; 如果你只需要打印RDD中的 几个元素,一个更安全的方法是使用take():rdd.take(100).foreach(println)。

使用K-V对

虽然大多数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))--将RDDlines中的元素转换成元组以组成新的RDDparis
val counts = pairs.reduceByKey((a, b) => a + b)---进行求和/计数
我们还可以使用counts.sortByKey(),按字母顺序对对进行排序,最后使用counts.collect()将它们作为对象数组返回到驱动程序。

注意:当在键值对操作中使用自定义对象作为键时,您必须确保自定义equals()方法伴随着匹配的hashCode()方法。 有关完整的详细信息,请参阅Object.hashCode()文档中概述的合同。

Transformations

下表列出了Spark支持的一些常见转换。 有关详细信息,请参阅RDD API文档(Scala,Java,Python,R)和RDD函数doc(Scala,Java)。

Transformation Meaning
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 func returns 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<T> => Iterator<U> 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<T>) => Iterator<U> when running on an RDD of type T.
sample(withReplacementfractionseed)                                                                                                                                                                                                                  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<V>) 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 oftype (V,V) => V. Like in groupByKey, the number of reduce tasks is configurable through an optional second argument.
aggregateByKey(zeroValue)(seqOpcombOp, [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 ascendingargument.
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 leftOuterJoinrightOuterJoin, and fullOuterJoin.
cogroup(otherDataset, [numTasks]) When called on datasets of type (K, V) and (K, W), returns a dataset of (K, (Iterable<V>, Iterable<W>)) 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

The following table lists some of the common actions supported by Spark. Refer to the RDD API doc (ScalaJavaPythonR)

and pair RDD functions doc (ScalaJava) for details.

Action Meaning
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(withReplacementnum, [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.

The Spark RDD API also exposes asynchronous versions of some actions, like foreachAsync for foreach, which immediately return a FutureAction to the caller instead of blocking on completion of the action. This can be used to manage or wait for the asynchronous execution of the action.




Shuffle(洗牌)操作

Spark中的某些操作会触发称为shuffle的事件。 Shuffle是Spark重新分发数据的机制,以便数据跨分区进行不同的分组。 这通常涉及跨执行器和机器复制数据,使得Shuffle是复杂且昂贵的操作

背景

要理解在shuffle期间发生了什么,我们可以考虑reduceByKey操作的示例。 reduceByKey操作生成一个新的RDD,其中单个键的所有值被组合成一个元组——键-值对,与该键相关联的所有值执行reduce函数的结果。 难点是单个key的所有value不一定驻留在同一个分区,甚至是同一台机器上,但是它们必须位于同一位置才能计算结果。

在Spark中,数据通常不会跨分区分布到特定操作所需的位置。 在计算期间,单个任务将在单个分区上操作 - 因此,为了组织单个reduceByKey reduce任务执行所需的数据,Spark需要执行一个all-to-all操作。 它必须从所有分区读取,以查找所有键的所有值,然后跨分区将与单个key对应的所有的值汇总以计算每个key的最终结果 - 这就称为shuffle

虽然新shuffle的数据的每个分区中的元素集将是确定性的,分区本身的排序也是确定性的,但是这些元素的排序不是。 如果希望shuffle之后可预测地排序数据,则可以使用:

  • mapPartitions to sort each partition using, for example, .sorted
  • repartitionAndSortWithinPartitions to efficiently sort partitions while simultaneously repartitioning
  • sortBy to make a globally ordered RDD
可以导致shuffle的操作包括重新分区操作,如重新分区和合并,“ByKey操作(除了计数外),如groupByKey和reduceByKey,以及连接操作,如cogroup和join。

性能影响

Shuffle是一个昂贵的操作,因为它涉及磁盘I / O,数据序列化和网络I / O。为了整理用于shuffle的数据,Spark生成一组任务 - map任务用来组织数据,以及一组reduce任务来聚合数据。这个命名来自MapReduce,并且不直接与Spark的map和reduce操作相关。

在内部,来自单个map任务的结果保存在内存中,直到它们不能适应(cannot fit)。然后,基于目标分区对它们进行排序并写入单个文件。在reduce侧,任务读取相关的排序块。

某些shuffle操作可能消耗大量的堆内存,因为它们使用内存数据结构在传输它们之前或之后组织记录。具体来说,reduceByKey和aggregateByKey在map端创建这些结构,而ByKey操作在reduce端生成这些结构。当数据不适合内存时,Spark会将这些表溢出到磁盘,导致磁盘I / O的额外开销和增加的垃圾回收。

Shuffle还会在磁盘上生成大量中间文件。从Spark 1.3开始,这些文件被保留,直到相应的RDD不再使用并被垃圾收集。这样一来,如果重新计算谱系,则不需要重新创建shuffle文件。如果应用程序保留对这些RDD的引用,或者如果GC不经常引入,则垃圾收集可能只在很长一段时间后才会发生。这意味着长时间运行的Spark作业可能会消耗大量的磁盘空间。在配置Spark上下文时,临时存储目录由spark.local.dir配置参数指定。

Shuffle行为可以通过调整各种配置参数来调整。请参阅Spark配置指南中的“Shuffle行为”部分。

RDD持久化

Spark中最重要的功能之一是通过操作(across 操作)在内存中持久化(或缓存)数据集。当持久化RDD时,每个节点存储它在内存中计算的任何分区,并在该数据集(或从其派生的数据集)上的其他操作中重用它们。这允许未来的action更快(通常超过10倍)执行。缓存是迭代算法和快速交互使用的关键工具。

您可以使用persist()或cache()方法将RDD标记为持久化。第一次在action计算时,它将保存在节点上的内存中。 Spark的缓存是容错的 - 如果RDD的任何分区丢失,它将使用最初创建的变换自动重新计算。

此外,每个持久化的RDD可以使用不同的存储级别存储,例如,允许您将数据集持久保存在磁盘上,持久存储在内存中--但作为序列化的Java对象(以节省空间),跨节点进行复制。这些级别是通过将StorageLevel对象(Scala,Java,Python)传递给persist()来设置的。 cache()方法是使用默认存储级别的简写,即StorageLevel.MEMORY_ONLY(在存储器中存储反序列化的对象)。全套存储级别为:

Storage Level Meaning
MEMORY_ONLY Store 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_DISK Store 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_ONLY Store the RDD partitions only on disk.
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.
Spark还自动在shuffle操作(例如reduceByKey)中持久化一些中间数据,即使没有用户调用persist。 这样做是为了避免在shuffle期间节点故障时重新计算整个输入。 我们仍然建议用户在生成的RDD上调用persist,如果他们计划重用它。

要选择哪个存储级别?

Spark’s storage levels are meant to provide different trade-offs between memory usage and CPU efficiency. We recommend going through the following process to select one:

  • If your RDDs fit comfortably with the default storage level (MEMORY_ONLY), leave them that way. This is the most CPU-efficient option, allowing operations on the RDDs to run as fast as possible.

  • If not, try using MEMORY_ONLY_SER and selecting a fast serialization library to make the objects much more space-efficient, but still reasonably fast to access. (Java and Scala)

  • Don’t spill to disk unless the functions that computed your datasets are expensive, or they filter a large amount of the data. Otherwise, recomputing a partition may be as fast as reading it from disk.

  • Use the replicated storage levels if you want fast fault recovery (e.g. if using Spark to serve requests from a web application). All the storage levels provide full fault tolerance by recomputing lost data, but the replicated ones let you continue running tasks on the RDD without waiting to recompute a lost partition.

删除数据

Spark自动监视每个节点上的缓存使用情况,并以最近最少使用(LRU)的方式删除旧的数据分区。 如果您想手动删除RDD,而不是等待它脱离缓存,请使用RDD.unpersist()方法。

共享变量

通常,当传递给Spark操作(例如map或reduce)的函数在远程集群节点上执行时,它会在函数中使用的所有变量的单独副本上工作。 这些变量将复制到每台机器,并且远程机器上的变量的更新不会传播回驱动程序。 通用支持的、跨任务的可读写共享变量将是低效的。 但是,Spark为两种常见的使用模式提供了两种类型受限的共享变量:广播变量和累加器。

广播变量

广播变量允许程序员保存每个机器上缓存的只读变量,而不是在任务中携带变量的副本。例如,它们(广播变量)可以用于以高效的方式给每个节点一个大的输入数据集的副本。 Spark还尝试使用高效的广播算法来分发广播变量以降低通信成本。

Spark的actions通过一系列阶段执行,由分布式“shuffle”操作分隔。 Spark自动广播每个阶段中任务所需的公共数据。以这种方式广播的数据以串行形式缓存,并在运行每个任务之前反序列化。这意味着显式创建广播变量仅在跨多个阶段的任务需要相同的数据或以反序列化形式缓存数据很重要时才有用

通过调用SparkContext.broadcast(v)从变量v创建广播变量。广播变量是v的一个包装,它的值可以通过调用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)
创建广播变量后,应使用它而不是在集群上运行的任何函数中使用值v,以便v不会多次发送到节点。 另外,对象v在广播之后不应被修改,以便确保所有节点获得广播变量的相同值(例如,如果变量以后被运送到新节点)。

累加器

累积器是仅通过关联和交换( associative and commutative )操作“添加”的变量,因此可以并行地有效地支持。 它们可以用于实现计数器(如在MapReduce中)或求和。 Spark本身支持数值类型的累加器,而程序员可以添加对新类型的支持。

如果累加器使用名称创建,它们将显示在Spark的UI中。 这对理解运行阶段的进度很有用(注意:这在Python中还不支持)。


可以通过调用SparkContext.longAccumulator()或SparkContext.doubleAccumulator()分别累积Long类型或Double类型的值来创建数字累加器。 然后,在集群上运行的任务可以使用add方法添加到它(Accumulator)。 但是,他们不能读取其(Accumulator)的值。 只有驱动程序可以使用其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/重置)用于将累加器复位为零,并且添加用于将累加值添加到累加器中,合并用于将另一相同类型累加器合并到该累加器中。 其他需要重写的方法可以参考scala API文档。 例如,假设我们有一个表示数学向量的MyVector类,我们可以这样写:

object VectorAccumulatorV2 extends AccumulatorV2[MyVector, MyVector] {
  val vec_ : MyVector = MyVector.createZeroVector
  def reset(): MyVector = {
    vec_.reset()
  }
  def add(v1: MyVector, v2: MyVector): MyVector = {
    vec_.add(v2)
  }
  ...
}

// Then, create an Accumulator of this type:
val myVectorAcc = new VectorAccumulatorV2
// Then, register it into spark context:
sc.register(myVectorAcc, "MyVectorAcc1")
注意,当程序员定义自己的AccumulatorV2类型时,结果类型可能与添加的元素不同。

对于仅在actions内执行的累加器更新,Spark保证每个任务对累加器的更新只应用一次,即重新启动的任务不会更新该值。 在transformation中,用户应该注意,如果重新执行任务或作业阶段,每个任务的更新可能会被应用多次。

累加器不改变Spark的延迟评估模型。 如果它们在RDD上的操作内被更新,则仅当RDD作为action的一部分被计算时才更新它们的值。 因此,不能保证在像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.


Submitting Applications

Spark的bin目录中的 spark-submit脚本用于在集群上启动应用程序。 它可以通过统一的接口使用Spark的所有支持的集群管理器,因此您不必为每个集群管理器专门配置您的应用程序。

绑定应用程序的依赖关系

如果您的代码依赖于其他项目,则需要将它们与应用程序一起打包,以便将代码分发到Spark集群。 为此,请创建一个包含你的代码及其依赖关系的assembly jar(或“uber”jar)。 sbt和Maven都有assembly (组装)插件。 创建组件jar时,将Spark和Hadoop列为provided依赖; 这些不需要捆绑,因为它们在运行时由集群管理器提供。 一旦你有一个组装的jar,你可以调用bin / spark-submit脚本,如下所示,同时传递你的jar。

使用spark-submit启动应用程序

一旦为用户应用程序绑定依赖关系后,可以使用bin / spark-submit脚本启动。 此脚本负责使用Spark及其依赖关系设置类路径,并且支持Spark支持的不同集群管理器和部署模式:

./bin/spark-submit \
  --class <main-class> \
  --master <master-url> \
  --deploy-mode <deploy-mode> \
  --conf <key>=<value> \
  ... # other options
  <application-jar> \
  [application-arguments]
一些常用的选项如下:
·--class:应用程序的入口点(例如 : org.apache.spark.examples.SparkPi

·--master:集群的master URL(如:spark://23.195.26.187:7077

·--deploy-mode:是否在工作节点(集群)上部署驱动程序,或者在本地外部客户端(客户端)上部署驱动程序(默认值:客户端)†。

·--conf:key = value格式的任意Spark配置属性。 对于包含空格的值,用引号包裹:“key = value”(如上所示)。

·application-jar:包含您的应用程序和所有依赖关系的绑定jar的路径。 URL必须在集群内部全局可见,例如,所有节点上存在的hdfs://路径或file://路径。

·application-arguments:传递给你的主类的main方法的参数,如果有的话。

常见的部署策略是从与您的工作机器(例如独立EC2群集中的主节点)物理上位于同一地点的网关机器提交应用程序。 在此设置中,客户端模式是适当的。 在客户端模式下,驱动程序直接在spark-submit进程中启动,该进程充当集群的客户端。 应用程序的输入和输出连接到控制台。 因此,此模式特别适合涉及REPL的应用程序(例如Spark shell)。

或者,如果您的应用程序是从远离工作机器的计算机(例如笔记本电脑本地)上提交的,则通常使用集群模式以最小化驱动程序和执行器之间的网络延迟。 目前,独立模式不支持Python应用程序的集群模式。

有几个特定于你所使用的集群管理器的可用选项。 例如,对于具有集群部署模式的Spark独立集群(Spark standalone cluster ),您还可以指定--supervise以确保如果驱动程序以非零退出代码失败,则会自动重新启动。 要枚举spark-submit可用的所有这些选项,请使用--help运行它。 以下是常见选项的几个示例:

 Run application locally on 8 cores
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master local[8] \
  /path/to/examples.jar \
  100

# Run on a Spark standalone cluster in client deploy mode
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master spark://207.184.161.138:7077 \
  --executor-memory 20G \
  --total-executor-cores 100 \
  /path/to/examples.jar \
  1000

# Run on a Spark standalone cluster in cluster deploy mode with supervise
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master spark://207.184.161.138:7077 \
  --deploy-mode cluster \
  --supervise \
  --executor-memory 20G \
  --total-executor-cores 100 \
  /path/to/examples.jar \
  1000

# Run on a YARN cluster
export HADOOP_CONF_DIR=XXX
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master yarn \
  --deploy-mode cluster \  # can be client for client mode
  --executor-memory 20G \
  --num-executors 50 \
  /path/to/examples.jar \
  1000

# Run a Python application on a Spark standalone cluster
./bin/spark-submit \
  --master spark://207.184.161.138:7077 \
  examples/src/main/python/pi.py \
  1000

# Run on a Mesos cluster in cluster deploy mode with supervise
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master mesos://207.184.161.138:7077 \
  --deploy-mode cluster \
  --supervise \
  --executor-memory 20G \
  --total-executor-cores 100 \
  http://path/to/examples.jar \
  1000

Master URLs

传递给Spark的master URI有以下格式:

Master URL Meaning
local                  Run Spark locally with one worker thread (i.e. no parallelism at all).
local[K]                 Run Spark locally with K worker threads (ideally, set this to the number of cores on your machine).
local[*] Run Spark locally with as many worker threads as logical cores on your machine.
spark://HOST:PORT                       Connect to the given Spark standalone cluster master. The port must be whichever one your master is configured to use, which is 7077 by default.
mesos://HOST:PORT Connect to the given Mesos cluster. The port must be whichever one your is configured to use, which is 5050 by default. Or, for a Mesos cluster using ZooKeeper, use mesos://zk://.... To submit with --deploy-mode cluster, the HOST:PORT should be configured to connect to the MesosClusterDispatcher.
yarn Connect to a YARN cluster in client or cluster mode depending on the value of --deploy-mode. The cluster location will be found based on the HADOOP_CONF_DIR or YARN_CONF_DIR variable.
从文件中加载配置信息

spark-submit脚本可以从属性文件中加载默认的Spark配置值,并将它们传递给你的应用程序。 默认情况下,它将从Spark目录中的conf / spark-defaults.conf中读取。 有关详细信息,请参阅加载默认配置一节。

以这种方式加载默认Spark配置可以避免进行spark-submit所需要某些标志。 例如,如果设置了spark.master属性,则可以安全地忽略spark提交中的--master标志。 一般来说,在SparkConf上显式设置的配置值采用最高优先级,然后是传递到spark-submit的标志,最后是默认文件中的值。

如果您不清楚配置选项来自哪里,您可以通过使用--verbose选项运行spark-submit打印出细粒度的调试信息。

高级依赖管理

使用spark-submit时,应用程序jar以及包含在--jars选项中的任何jar将自动传输到集群。 --jars之后提供的URL必须用逗号分隔。 该列表包含在驱动程序和执行程序类路径中。 目录扩展不能与--jars一起使用。

Spark使用以下URL方案来允许使用不同的策略传播jar:

  • file: -绝对路径和file:/ URI由驱动程序的HTTP文件服务器提供,每个执行器从驱动程序的HTTP服务器拉取文件。
  • hdfs:http:https:ftp: - 这些格式从指定的URI拉取文件和JAR。
  • local: -以local:/开头的URI应该是存在于每个工作节点上的本地文件。 这意味着不会产生网络IO,并且适用于被推送到每个工作线程或通过NFS,GlusterFS等共享的大的file/或JAR。
请注意,JAR和文件会被复制到执行器节点上每个SparkContext的工作目录。 这可能会占用大量的空间,随着时间的推移,需要对它们进行清理。 使用YARN的话,清理工作会自动处理;使用Spark standalone的话,可以使用spark.worker.cleanup.appDataTtl属性配置自动清除。

用户还可以通过用--packages提供逗号分隔的maven坐标列表来包括任何其他依赖项。 使用此命令时将处理所有传递依赖性。 可以使用标志--repositories以逗号分隔的方式添加其他存储库(或SBT中的解析器)。 这些命令可以与pyspark,spark-shell和spark-submit一起使用,以便包括Spark Packages。

More Information

部署应用程序后,cluster mode overview描述了分布式执行中涉及的组件以及如何监视和调试应用程序。


Cluster Mode Overview

本文简要概述Spark如何在集群上运行,以便更容易地了解所涉及的组件。 阅读submitting applications 指南,了解在群集上启动应用程序。

Components/组件

Spark应用程序作为集群上独立的进程集运行,由主程序中的SparkContext对象(称为驱动程序)协调。

具体来说,要在集群上运行,SparkContext可以连接到几种类型的集群管理器(Spark自己的独立集群管理器/standalone cluster,Mesos或YARN),它们可以跨应用程序分配资源。 连接后,Spark会在集群中的节点上获取executors,这些进程是为应用程序运行计算和存储数据的进程。 接下来,它将应用程序代码(被传递给SparkContext的、由JAR或Python文件定义的文件)发送给executors。 最后,SparkContext将任务/tasks发送给executors来运行。

注:Spark action会导致一个由多个tasks组成的并行计算产生。



下面几点有助于理解该架构:

1.每个application都有自己的执行程序(executors)进程,它们在整个应用程序的持续时间内运行,并在多个线程中运行任务。 这有利于在调度端(每个驱动程序(driver)调度其自己的任务)和执行器端(来自不同应用程序的任务在不同的JVM中运行)将应用程序彼此隔离。 但是,这也意味着数据不能在不将其写入外部存储系统的情况下跨不同的Spark应用程序(SparkContext实例)共享。

2.Spark对底层的集群管理器是不可知的。 只要它可以获得执行器进程,并且这些进程彼此间能通信,即使在同样支持其他应用程序(例如Mesos / YARN)的集群管理器上,它也是相对容易运行的。

3.驱动程序必须在其生命周期内监听并接受来自其执行器的传入连接(例如,请参阅network config节中的spark.driver.port)。 因此,对工作节点而言,驱动程序必须是网络可寻址的。

4.因为驱动程序在群集上调度任务,所以它应该靠近工作节点运行,最好在同一个局域网上运行。 如果你想远程发送请求到集群,最好开放一个RPC给驱动程序,并从附近提交操作,以运行远离工作节点的驱动程序

集群管理器的类型

Spark目前支持3种集群管理器:

·Standalone-一个包含在Spark内部的简单集群管理器,用它可以容易地建立集群;

·Apache Mesos-一个通用的集群管理器,也可以运行Hadoop MapReduce和服务应用程序。

·Hadoop YARN-Hadoop 2里面的资源管理器。

提交应用程序

通过spark-submit脚本可以容易地将应用程序提交到各种类型的集群中。详情见Submitting Applications。

监视

每个驱动程序都有一个Web UI,通常在端口4040上,该页面显示有关正在运行的任务,执行程序和存储使用的信息。 只需在Web浏览器中访问http:// <driver-node>:4040即可访问此UI。Monitoring指南还介绍了其他监控选项。

作业调度

Spark给予跨应用程序(在集群管理器级别)和应用程序(如果多个计算在同一SparkContext上进行)上的资源分配的控制。Job Scheduling
概述进行了更详细地描述

术语

下表总结了用来引用群集概念的术语:

Term Meaning
Application 在Spark上构建的用户程序,由集群上的driver program 和 executors 组成。
Application jar A jar containing the user's Spark application. In some cases users will want to create an "uber jar" containing their application along with its dependencies. The user's jar should never include Hadoop or Spark libraries, however, these will be added at runtime.
Driver program 运行应用程序main()函数、创建SparkContext的进程
Cluster manager 一个用来获取集群上的资源的外部服务 (e.g. standalone manager, Mesos, YARN)
Deploy mode 区分driver进程在哪运行。在“集群”模式下,框架会启动集群中的驱动程序。 在“客户端”模式下,提交器在集群外启动驱动程序。
Worker node 集群中可以运行应用程序代码的任何节点。
Executor 为工作节点上的应用程序启动的进程,该进程运行任务并将数据保存在内存或磁盘存储中。 每个应用程序都有自己的executors。
Task 发送给某个executor的工作单元
Job 由多个任务组成的并行计算,响应Spark操作(例如savecollect)而生成; 您将在驱动程序log中看到此术语
Stage 每个作业被分成更小的任务集---相互依赖的阶段(类似于MapReduce中的映射和缩减阶段); 您将在驱动程序日志中看到此术语。
作业调度

综述

Spark具有用于在计算之间调度资源的若干设施。 首先,回想一下,如Cluster Mode概述中所述,每个Spark应用程序(即SparkContext的实例)都运行一组独立的执行器进程(executors)。 Spark运行其上的集群管理器提供用于跨应用程序调度的工具。 其次,在每个Spark应用程序中,如果多个“作业”(Spark actions)由不同的线程提交,它们可能会同时/并发运行。 如果您的应用程序服务来自于网络提供请求,这是很常见的。 Spark有一个公平的(Fair)调度程序来调度每个SparkContext中的资源。

跨应用程序调度

当在集群上运行时,每个Spark应用程序都会获得一组独立的执行器JVM,这些JVM仅运行任务并存储该应用程序的数据。 如果多个用户需要共享您的集群,则有不同的选项来管理分配,具体取决于集群管理器。

静态分区

在所有集群管理器上可用的最简单的选项是资源的静态分区(static partitioning。 使用这种方法,每个应用程序被给予它可以使用的最大限度的资源,并且在其整个持续时间内保持它们(静态分区)。 这是在Spark的独立和YARN模式中使用的方法,以及粗粒度Mesos模式。 可以根据集群类型配置资源分配,如下所示:

·Standalone mode:默认情况下,提交到独立模式集群的应用程序将以FIFO(先进先出)顺序运行每个应用程序将尝试使用所有可用节点。您可以通过在其中设置spark.cores.max配置属性来限制应用程序使用的节点数,或者通过spark.deploy.defaultCores为应用程序指定默认值。最后,除了控制内核占有(亦使用的节点数)情况外,每个应用程序的spark.executor.memory设置控制其内存使用

·Mesos模式:要在Mesos上使用静态分区,请将spark.mesos.coarse配置属性设置为true,并可选择设置spark.cores.max以限制每个应用程序的资源共享,就像在独立模式下一样。你还应该设置spark.executor.memory来控制执行器内存。

·YARN:Spark YARN客户端的--num-executors选项控制它将在集群上分配多少个执行器(spark.executor.instances作为配置属性),而--executor-memory(spark.executor.memory配置属性)和--executor-cores(spark.executor.cores配置属性)控制每个执行器的资源。有关更多信息,请参阅YARN Spark属性。

在Mesos上可用的第二个选项是CPU核心的动态共享。 在这种模式下,每个Spark应用程序仍然具有固定和独立的内存分配(由spark.executor.memory设置),但是当应用程序不在机器上运行任务时,其他应用程序可能会在这些内核上运行任务。 当您期望大量未过度活动的应用程序(例如来自单独用户的shell会话)时,此模式非常有用。 但是,它具有较少可预测的延迟的风险,因为应用程序可能需要一段时间来获得一个节点上的后备核心,当它有工作要做。 要使用此模式,只需使用mesos:// URL并将spark.mesos.coarse设置为false。


请注意,目前没有一种模式支持跨应用程序的内存共享。 如果您希望以这种方式(内存)共享数据,我们建议运行单个服务器应用程序,通过查询相同的RDD来提供多个请求

动态资源分配--默认是禁用的(在1.6.0版本,仅支持standalone模式)

Spark提供了一种根据工作负载动态调整应用程序占用的资源的机制。 这意味着,如果不再使用资源,您的应用程序可能会将资源返回给集群,并在有需求时再次请求它们。 如果多个应用程序在Spark集群中共享资源,此功能特别有用。

默认情况下禁用此功能,并且可在所有粗粒度集群管理器上使用此功能,即独立模式,YARN模式和Mesos粗粒度模式。

配置

使用此功能有两个要求。首先,您的应用程序必须将spark.dynamicAllocation.enabled设置为true。其次,您必须在同一集群中的每个工作节点上设置外部shuffle服务,并在应用程序中将spark.shuffle.service.enabled设置为true。外部shuffle服务的目的是允许删除执行器而不删除它们编写的shuffle文件(下面将更详细地描述)。设置此服务的方法因群集管理器的不同而异:

独立模式下,只需启动您的workers,并将spark.shuffle.service.enabled设置为true

在Mesos粗粒度模式下,在spark.shuffle.service.enabled设置为true的所有从节点上运行$ SPARK_HOME / sbin / start-mesos-shuffle-service.sh。例如,你可以通过Marathon来这样做。

在YARN模式下,按照此处的说明操作。

所有其他相关配置都是可选的,位于spark.dynamicAllocation.*和spark.shuffle.service.*命名空间下。有关更多详细信息,请参阅配置页。

资源分配策略

在高级别,Spark应该在不再使用executors时放弃它们,并在需要时获得执行程序。 由于没有确定的方法来预测即将被删除的执行器是否会在不久的将来运行任务,或者要添加的新执行器是否是空闲的,我们需要一组启发式来确定何时删除和请求executors

请求策略--轮询executors

当启用了动态分配的Spark应用程序在具有等待调度的挂起任务时会请求其他执行器。该条件必然意味着现有的执行器集合不足以同时饱和已经提交但尚未完成的所有任务。

Spark会以轮询的方式请求执行器。当已经有spark.dynamicAllocation.schedulerBacklogTimeout秒的挂起任务时,才会触发实际请求,然后每隔spark.dynamicAllocation.sustainedSchedulerBacklogTimeout秒再次触发请求,如果挂起任务的队列仍然存在。此外,每轮中请求的执行器数量与上一轮相比呈指数增长。例如,应用程序将在第一轮中添加1个执行程序,然后在后续轮中添加2,4,8等等执行程序。

指数增长政策的动机是双重的。首先,应用程序应该在开始时谨慎地请求执行器,以防只要少数额外的执行器就可以满足要求。这回应了TCP慢启动的理由。第二,应用程序应能够及时提高其资源使用率,以防万一确实需要许多executors。

移除策略

删除执行程序的策略要简单得多。 Spark应用程序在executor的空闲时间超过spark.dynamicAllocation.executorIdleTimeout秒时删除就该executor。 注意,在大多数情况下,该条件与请求条件互斥,因为如果仍有待调度的挂起任务,执行器不应该是空闲的。

executors的优美退出

在动态分配之前,Spark执行器会在失败或关联的应用程序退出时退出。在这两种情况下,与执行器相关联的所有状态不再需要,并且可以安全地丢弃。然而,使用动态分配,当显式删除执行器时,应用程序仍在运行。如果应用程序尝试访问存储在执行程序中或由执行程序写入的状态,则它将必须执行重新计算状态。因此,Spark需要一种机制,通过在删除执行器之前保持其状态来正常停止执行器。

这个要求对于shuffle特别重要。在shuffle期间,Spark执行程序首先将其自己的映射输出写入本地磁盘,然后在其他执行程序尝试读取这些文件时充当这些文件的服务器。如果stragglers是运行时间比它们的同伴长得多的任务,动态分配可以在shuffle完成之前移除执行器,在这种情况下,由该执行器写入的shuffle文件必须被不必要地重新计算。

保留shuffle文件的解决方案是使用外部shuffle服务,在Spark 1.2中引入。此服务指的是一个长时间运行的进程,在集群的每个节点上运行,与Spark应用程序及其执行程序无关。如果服务已启用,Spark执行程序将从服务而不是彼此获取shuffle文件。这意味着executor写入的任何shuffle状态可以继续在该executor的生命周期之外被服务。

除了写入shuffle文件之外,executors还可以在磁盘或内存中缓存数据。但是,当删除该executor后,所有缓存的数据将无法再访问。为了减轻这种情况,默认情况下,包含缓存数据的executor不会被删除。您可以使用spark.dynamicAllocation.cachedExecutorIdleTimeout配置此行为。在将来的版本中,缓存的数据可以通过堆外存储来保存,类似于如何通过外部shuffle服务保留shuffle文件。

在应用程序内部进行调度

在给定的Spark应用程序(SparkContext实例)中,如果多个并行作业是从不同的线程提交的,它们可以同时运行。在本节中,“作业”是指Spark操作/actions(例如save,collect)以及需要运行以评估该操作的任何任务。 Spark的调度器是完全线程安全的,并支持这种用例来启用服务多个请求的应用程序(例如对多个用户的查询)。

默认情况下,Spark的调度器以FIFO方式运行作业。每个作业被分为“阶段”(例如,映射和缩减阶段),并且第一个作业在所有可用资源上获得优先级,而其阶段具有要启动的任务,然后第二个作业获得优先级等。如果在队列头部的作业不需要使用整个集群,稍后的作业则可以立即开始运行,但是如果队列头部的作业很大,则稍后的作业可能会显着延迟。

从Spark 0.8开始,也可以配置作业之间的公平共享。在公平共享下,Spark以“循环”方式在作业之间分配任务,以便所有作业获得大致相等的群集资源份额。这意味着,在长作业正在运行时提交的短作业可以立即开始接收资源,并且仍然获得良好的响应时间,而无需等待长作业完成。此模式最适合多用户设置。

要启用公平调度器,只需在配置SparkContext时将spark.scheduler.mode属性设置为FAIR:

val conf = new SparkConf().setMaster(...).setAppName(...)
conf.set("spark.scheduler.mode", "FAIR")
val sc = new SparkContext(conf)
Fair调度器池

公平调度器还支持将作业分组为池,并且为每个池设置不同的调度选项(例如权重)。 这对于为更重要的作业创建“高优先级”池或者将每个用户的作业分组在一起并给予所有用户平等的份额是有益的(不管他们具有多少并发作业,不按作业给予相等份额)。 这种方法是依据Hadoop Fair Scheduler建模的。

公平调度器还支持将作业分组为池,并且为每个池设置不同的调度选项(例如权重)。 这对于为更重要的作业创建“高优先级”池或者将每个用户的作业分组在一起并给予所有用户平等的份额是有益的(不管他们具有多少并发作业,不按作业给予相等份额)。 这种方法是依据Hadoop Fair Scheduler建模的。

没有任何干预的情况下,新提交的作业将进入默认池,但可以通过将spark.scheduler.pool“local属性”添加到提交它们的线程中的SparkContext 来设置作业池。 做法如下:

// Assuming sc is your SparkContext variable
sc.setLocalProperty("spark.scheduler.pool", "pool1")
设置此local属性后,在该线程内提交的所有作业(通过此线程调用的RDD.save,count,collect等)将使用此池名称。 该设置是每线程的,以便于线程代表同一用户运行多个作业。 如果你想清除 线程关联的池,只需调用:

sc.setLocalProperty("spark.scheduler.pool", null)
池的默认行为

默认情况下,每个池获得集群的相等份额(默认池中的每个作业的份额也相等),但在每个池内部,作业按FIFO顺序运行。 例如,如果您为每个用户创建一个池,这意味着每个用户将获得集群的相等份额,并且每个用户的查询将按顺序运行,而不是稍后的查询获取该用户的较早查询得到的资源。

配置池属性

特定池的属性也可以通过配置文件进行修改。每个池支持三个属性:

`schedulingMode:这可以是FIFO或FAIR,以控制池中的作业是否在彼此之后排队(默认情况,FIFO)或公平地共享池的资源。
`weight/权重:这控制池相对于其他池的共享。默认情况下,所有池的权重都为1.如果您给某个特定池的权重为2,那么它将获得相对于其他活动池2倍的资源。设置诸如1000的高权重也使得可以在池之间实现优先级 - 实质上,当weight-1000池具有作业活动时,它将总是首先启动任务。
`minShare:除了总体权重,每个池可以获取管理员希望拥有的最小份额(作为CPU核数)。在根据权重重新分配额外资源之前,fair调度器总是尝试满足所有活动池的最小份额。因此,minShare属性可以是另一种确保池可以总是快速获得一定数量的资源(例如10个核心)的方式,而不必为集群的其余部分提供高优先级。默认情况下,每个池的minShare为0。
可以通过创建一个XML文件来设置池属性,类似于conf / fairscheduler.xml.template,并在SparkConf中设置spark.scheduler.allocation.file属性。

conf.set("spark.scheduler.allocation.file", "/path/to/file")
XML文件的格式只是每个池对应一个<pool>元素,其中包含用于各种设置的不同元素。 例如:

<?xml version="1.0"?>
<allocations>
  <pool name="production">
    <schedulingMode>FAIR</schedulingMode>
    <weight>1</weight>
    <minShare>2</minShare>
  </pool>
  <pool name="test">
    <schedulingMode>FIFO</schedulingMode>
    <weight>2</weight>
    <minShare>3</minShare>
  </pool>
</allocations>
完整的例子也可以在conf / fairscheduler.xml.template中获得。 请注意,未在XML文件中配置的任何池将简单地获得所有设置(调度模式默认为FIFO,权重为1和minShare为0)的默认值。

















Spark Streaming 编程指南(基于Scala语言)

综述

Spark Streaming是核心Spark API的扩展,能够对实时数据流进行可扩展,高吞吐量,容错的流处理。 数据可以从诸如Kafka,Flume,Kinesis或TCP套接字的许多源中提取,并且可以使用由诸如map、reduce、join和window等高级高级函数表示的复杂算法来处理。 最后,处理后的数据可以推送到文件系统,数据库和live dashboards。 事实上,你可以对数据流应用Spark的机器学习和图形处理算法。


在内部,它的工作原理如下。 Spark Streaming接收实时输入数据流,并将数据分成批次,然后由Spark引擎处理以批量生成最终结果流。


Spark Streaming提供了一种称为离散流或DStream的高级抽象,用来表示连续的数据流。 DStreams可以从数据源(例如Kafka,Flume和Kinesis)的输入数据流创建,也可以通过对其他DStreams应用高级操作来创建。 在内部,DStream表示为RDD序列。

一个快速了解例子

先看看Spark Streaming程序的大概样子。假设我们要计算从侦听TCP套接字的数据服务器接收的文本数据中的字数。 所有你需要做的是如下。  Skip


Spark SQL, DataFrames and Datasets Guide

综述

Spark SQL是用于结构化数据处理的Spark模块。 与基本Spark RDD API不同,Spark SQL提供的接口为Spark提供了有关数据结果和正在执行的计算的结构的更多信息。 在内部,Spark SQL使用这些额外的信息来执行额外的优化。 有几种方法与Spark SQL(包括SQL和Dataset API)进行交互。 当计算结果时,使用相同的执行引擎,独立于用来表达计算的API /语言。 这种统一意味着开发人员可以容易地在不同的API之间来回切换,基于这些API提供了表达给定变换(transformation)的最自然的方式。

所有示例都使用Spark发行版中包含的示例数据,并且可以在spark-shell,pyspark shell或sparkR shell中运行。

SQL

Spark SQL的一个用途是执行SQL查询。 Spark SQL还可以用于从现有Hive中读取数据。 有关如何配置此功能的更多信息,请参阅HiveTables部分。当在另一种编程语言中运行SQL时,结果将作为Dataset / DataFrame返回。 您还可以使用命令行或通过JDBC / ODBC与SQL界面交互。

Datasets and DataFrames

数据集/Dataset是数据的分布式集合。 数据集是在Spark 1.6中添加的一个新接口 ,它提供了RDD的优点(强类型化,使用强大的lambda函数的能力)以及Spark SQL优化的执行引擎的优点。 数据集可以通过JVM对象构建 ,然后使用函数转换(map,flatMap,filter等)对其操作。 Scala和Java中提供了Dataset API。 Python没有对Dataset API的支持。 但是由于Python的动态特性,Dataset API的许多优点已经可用(即,您可以通过名称自然地访问行的字段row.columnName)。 R的情况是类似的。

DataFrame是组织成命名列的Dataset。 它在概念上等同于关系数据库中的表或R / Python中的数据框架,但具有更丰富的优化。DataFrames可以从各种来源构建,例如:结构化数据文件,Hive中的表,外部数据库或现有RDD。 DataFrame API可用于Scala,Java,Python和R中。在Scala和Java中,DataFrame由Rows类型的Dataset表示。 在Scala API中,DataFrame只是Dataset [Row]的类型别名。 而在Java API中,用户需要使用Dataset <Row>来表示DataFrame。

我们经常会用DataFrames来指Scala / Java数据集的行(Rows of Dataset)

Getting Started

Starting Point: SparkSession

Spark中所有功能的入口点是SparkSession类。 要创建基本的SparkSession,只需使用SparkSession.builder():

import org.apache.spark.sql.SparkSession

val spark = SparkSession
  .builder()
  .appName("Spark SQL basic example")
  .config("spark.some.config.option", "some-value")
  .getOrCreate()

// For implicit conversions like converting RDDs to DataFrames
import spark.implicits._
Spark 2.0中的SparkSession为Hive功能提供了内置支持,包括使用HiveQL编写查询,访问Hive UDF以及从Hive表中读取数据的能力。 要使用这些功能,您不需要具有现成的Hive。

创建 DataFrames

使用SparkSession,应用程序可以从现有RDD,Hive表或Spark数据源来创建DataFrames。

例如,下面根据JSON文件的内容创建DataFrame:

val df = spark.read.json("examples/src/main/resources/people.json")//如果程序报找不到文件错误,可以指定全路径,该文件在Spark发布包下。

// Displays the content of the DataFrame to stdout
df.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+
完整的代码在Spark安装目录下的 examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala

非类型化Dataset操作(也就是DataFrame操作)

DataFrames为Scala,Java,Python和R中的结构化数据操作提供了一个领域特定的语言。

如上所述,在Spark 2.0中,DataFrames只是Scala和Java API中Rows的Dataset(Dataset[Rows]) 。 这些操作也称为“非类型转换”,与带有强类型化Scala / Java数据集的“类型转换”相反。

这里我们包括使用数据集的结构化数据处理的一些基本示例:

// This import is needed to use the $-notation
import spark.implicits._
// Print the schema in a tree format
df.printSchema()
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)

// Select only the "name" column
df.select("name").show()
// +-------+
// |   name|
// +-------+
// |Michael|
// |   Andy|
// | Justin|
// +-------+

// Select everybody, but increment the age by 1
df.select($"name", $"age" + 1).show()
// +-------+---------+
// |   name|(age + 1)|
// +-------+---------+
// |Michael|     null|
// |   Andy|       31|
// | Justin|       20|
// +-------+---------+

// Select people older than 21
df.filter($"age" > 21).show()
// +---+----+
// |age|name|
// +---+----+
// | 30|Andy|
// +---+----+

// Count people by age
df.groupBy("age").count().show()
// +----+-----+
// | age|count|
// +----+-----+
// |  19|    1|
// |null|    1|
// |  30|    1|
// +----+-----+
有关可以对数据集执行的操作类型的完整列表,请参阅 API文档

除了简单的列引用和表达式数据集还具有丰富的函数库,包括字符串操作,日期算术,常用数学运算等。 完整的列表在DataFrame函数参考中提供。

以编程方式运行SQL查询

SparkSession上的sql函数使应用程序以编程方式运行SQL查询,并将结果作为DataFrame返回。

// Register the DataFrame as a SQL temporary view
df.createOrReplaceTempView("people")

val sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+
创建Datasets

数据集类似于RDD,但是,不是使用Java序列化或Kryo,他们使用专门的编码器Encoder(如Seq)来序列化对象以通过网络进行处理或传输。 虽然编码器和标准序列化都负责将对象转换为字节,但是编码器是动态生成的代码,并且使用允许Spark执行许多操作(如过滤,排序和散列),不用将字节反序列化为对象的格式

// Note: Case classes in Scala 2.10 can support only up to 22 fields. To work around(解决) this limit,
// you can use custom classes that implement the Product interface
case class Person(name: String, age: Long)--定义了一个样本类(Case class)

// Encoders are created for case classes
val caseClassDS = Seq(Person("Andy", 32)).toDS()
caseClassDS.show()
// +----+---+
// |name|age|
// +----+---+
// |Andy| 32|
// +----+---+

// Encoders for most common types are automatically provided by importing spark.implicits._
val primitiveDS = Seq(1, 2, 3).toDS()
primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4)

// DataFrames can be converted to a Dataset by providing a class. Mapping will be done by name
val path = "examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

全局临时视图

Spark SQL中的临时视图是会话范围的,如果创建它的会话终止,它将消失。 如果您希望有一个临时视图在所有会话之间共享,并保持活动,直到Spark应用程序终止,您可以创建一个全局临时视图全局临时视图绑定到系统保留的数据库global_temp,我们必须使用限定名称来引用它。 例如:SELECT * FROM global_temp.view1。

// Register the DataFrame as a global temporary view
df.createGlobalTempView("people")

// Global temporary view is tied to a system preserved database `global_temp`
spark.sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

// Global temporary view is cross-session
spark.newSession().sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

与RDD互操作

Spark SQL支持两种不同的方法将现有RDD转换为数据集。 第一种方法使用反射来推断包含特定类型的对象(如case类Person)的RDD的模式(schema)。 这种基于反射的方法在你编写Spark应用程序时已经知道了模式,从而获得更简洁的代码、效果更好。
创建Datasets的第二种方法是通过编程接口,它允许您构造一个模式,然后将其应用到现有的RDD。 虽然此方法更冗长,但它允许您在列和其类型在运行时之前未知时构造Datasets。

1.使用反射来推断模式

Spark SQL的Scala接口支持将包含样本类(case类)的RDD自动转换为DataFrame。 case类定义了表的模式。case类的参数名称使用反射读取,并成为列的名称。 case类也可以嵌套或包含复杂类型,如Seqs或Arrays。 此RDD可以隐式转换为DataFrame,然后注册为表。 这些表可以在后续的SQL语句中使用。

import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder
import org.apache.spark.sql.Encoder

// For implicit conversions from RDDs to DataFrames
import spark.implicits._

// Create an RDD of Person objects from a text file, convert it to a Dataframe
val peopleDF = spark.sparkContext
  .textFile("examples/src/main/resources/people.txt")
  .map(_.split(","))
  .map(attributes => Person(attributes(0), attributes(1).trim.toInt))--包含样本类的RDD,
  .toDF() --将RDD转为DF
// Register the DataFrame as a temporary view
peopleDF.createOrReplaceTempView("people")

// SQL statements can be run by using the sql methods provided by Spark
val teenagersDF = spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND 19")

// The columns of a row in the result can be accessed by field index
teenagersDF.map(teenager => "Name: " + teenager(0)).show()
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+

// or by field name
teenagersDF.map(teenager => "Name: " + teenager.getAs[String]("name")).show()
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+

// No pre-defined encoders for Dataset[Map[K,V]], define explicitly
implicit val mapEncoder = org.apache.spark.sql.Encoders.kryo[Map[String, Any]]
// Primitive types and case classes can be also defined as
// implicit val stringIntMapEncoder: Encoder[Map[String, Any]] = ExpressionEncoder()

// row.getValuesMap[T] retrieves multiple columns at once into a Map[String, T]
teenagersDF.map(teenager => teenager.getValuesMap[Any](List("name", "age"))).collect()
// Array(Map("name" -> "Justin", "age" -> 19))
2.通过编程方式指定Schema

当case类不能提前定义时(例如,记录的结构被编码在一个字符串中,或者在一个需要解析的文本数据集并且字段会被不同的用户进行投影),可以用以下三个步骤创建DataFrame 。

1.从原始的RDD创建RDD of Rows;

2.创建由与第1步中创建的RDD中的Rows结构匹配的、StructType表示的模式

3.通过SparkSession提供的createDataFrame方法将模式应用于RDD of Rows。

示例如下:

import org.apache.spark.sql.types._

// Create an RDD
val peopleRDD = spark.sparkContext.textFile("examples/src/main/resources/people.txt")

// The schema is encoded in a string
val schemaString = "name age"

// 2:Generate the schema based on the string of schema
val fields = schemaString.split(" ")
  .map(fieldName => StructField(fieldName, StringType, nullable = true))
val schema = StructType(fields)

// Convert records of the RDD (people) to Rows
val rowRDD = peopleRDD
  .map(_.split(","))
  .map(attributes => Row(attributes(0), attributes(1).trim))

// Apply the schema to the RDD
val peopleDF = spark.createDataFrame(rowRDD, schema)

// Creates a temporary view using the DataFrame
peopleDF.createOrReplaceTempView("people")

// SQL can be run over a temporary view created using DataFrames
val results = spark.sql("SELECT name FROM people")//DF

// The results of SQL queries are DataFrames and support all the normal RDD operations
// The columns of a row in the result can be accessed by field index or by field name
results.map(attributes => "Name: " + attributes(0)).show()
// +-------------+
// |        value|
// +-------------+
// |Name: Michael|
// |   Name: Andy|
// | Name: Justin|
// +-------------+

-------------------------------------------------------

数据源

Spark SQL支持通过DataFrame接口对各种数据源进行操作。 DataFrame可以使用关系型变换操作,也可以用于创建临时视图(view)。 将DataFrame注册为临时视图允许您对其数据运行SQL查询。 本节介绍使用Spark数据源加载和保存数据的一般方法,然后介绍可用于内置数据源的特定选项

通用的加载/保存函数

在最简单的形式中,默认数据源(parquet,除非由spark.sql.sources.default另行配置)将用于所有操作。

val usersDF = spark.read.load("examples/src/main/resources/users.parquet")
usersDF.select("name", "favorite_color").write.save("namesAndFavColors.parquet")
手动指定选项

您还可以手动指定与您要传递给数据源的任何其他选项一起使用的数据源。 数据源由其完全限定名(如,org.apache.spark.sql.parquet)指定,但对于内置源,您还可以使用其短名称(json,parquet,jdbc,orc,libsvm,csv,text )。 从任何数据源类型加载的DataFrames可以使用此语法转换为其他类型

val peopleDF = spark.read.format("json").load("examples/src/main/resources/people.json")
peopleDF.select("name", "age").write.format("parquet").save("namesAndAges.parquet")
直接对文件运行SQL

除了使用read API将文件加载到数据框架并查询它,您还可以直接使用SQL查询该文件

val sqlDF = spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`")
保存模式  通过mode函数

保存操作可以选择采用SaveMode,指定如何处理现有数据(如果存在)。 重要的是要意识到这些保存模式不使用任何锁定,并且不是原子的。 此外,执行覆盖(Overwrite)时,原数据将在写出新数据之前被删除

Scala/Java Any Language Meaning
SaveMode.ErrorIfExists(default)                                                                                                                 "error"(default)                                                             When saving a DataFrame to a data source, if data already exists, an exception is expected to be thrown.
SaveMode.Append "append" When saving a DataFrame to a data source, if data/table already exists, contents of the DataFrame are expected to be appended to existing data.
SaveMode.Overwrite "overwrite" Overwrite mode means that when saving a DataFrame to a data source, if data/table already exists, existing data is expected to be overwritten by the contents of the DataFrame.
SaveMode.Ignore                           "ignore" Ignore mode means that when saving a DataFrame to a data source, if data already exists, the save operation is expected to not save the contents of the DataFrame and to not change the existing data. This is similar to a CREATE TABLE IF NOT EXISTS in SQL.
保存到持久表

DataFrames也可以使用saveAsTable命令作为持久表保存到Hive元数据仓(metastore)中。 注意,使用此功能不需要现有的Hive部署。 Spark将为您创建一个默认的本地Hive元数据仓库(使用Derby)。 与createOrReplaceTempView命令不同,saveAsTable将实现DataFrame的内容并创建指向Hive Metastore中数据的指针。 即使在Spark程序重新启动后,持久表仍将存在,只要您保持与同一个存储区的连接。 可以通过使用表的名称调用SparkSession上的表方法来创建永久表的DataFrame。

默认情况下,saveAsTable将创建一个“托管表”,这意味着数据的位置将由元数据仓(metastore)控制。 托管表也将在删除表时自动删除其数据。

Parquet文件

Parquet是一种由许多其他数据处理系统支持的柱形格式。 Spark SQL提供对读取和写入Parquet文件的支持,这些文件自动保留原始数据的模式。在写Parquet文件时,出于兼容性原因,所有列都会自动转换为可空

以编程方式加载数据

使用上面例子中的数据:

// Encoders for most common types are automatically provided by importing spark.implicits._
import spark.implicits._

val peopleDF = spark.read.json("examples/src/main/resources/people.json")

// DataFrames can be saved as Parquet files, maintaining the schema information
peopleDF.write.parquet("people.parquet")

// Read in the parquet file created above
// Parquet files are self-describing so the schema is preserved
// The result of loading a Parquet file is also a DataFrame
val parquetFileDF = spark.read.parquet("people.parquet")

// Parquet files can also be used to create a temporary view and then used in SQL statements
parquetFileDF.createOrReplaceTempView("parquetFile")
val namesDF = spark.sql("SELECT name FROM parquetFile WHERE age BETWEEN 13 AND 19")
namesDF.map(attributes => "Name: " + attributes(0)).show()
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+
Partition Discovery/分区发现

表分区是(如Hive这样的)系统中常用的优化方法。 在分区表中,数据通常存储在不同的目录中,分区列值编码在每个分区目录的路径中。这样一来, Parquet数据源能够自动发现和推断分区信息。 例如,我们可以使用以下目录结构将所有先前使用的人口数据存储到分区表中,其中包含两个额外的列gender和country作为分区列:

path
└── to
    └── table
        ├── gender=male
        │   ├── ...
        │   │
        │   ├── country=US
        │   │   └── data.parquet
        │   ├── country=CN
        │   │   └── data.parquet
        │   └── ...
        └── gender=female
            ├── ...
            │
            ├── country=US
            │   └── data.parquet
            ├── country=CN
            │   └── data.parquet
            └── ...
通过将path / to / table传递给SparkSession.read.parquet或SparkSession.read.load,Spark SQL将自动从路径中提取分区信息。 现在,返回的DataFrame的模式变为:

root
|-- name: string (nullable = true)
|-- age: long (nullable = true)
|-- gender: string (nullable = true)
|-- country: string (nullable = true)
请注意,分区列的数据类型是自动推断的。 目前,支持数字数据类型和字符串类型。 有时用户可能不想自动推断分区列的数据类型。 对于这些用例,自动类型推断可以由spark.sql.sources.partitionColumnTypeInference.enabled配置,默认为true。 当禁用类型推断时,字符串类型将用于分区列。

从Spark 1.6.0开始,默认情况下分区发现仅查找给定路径下的分区。 对于上面的示例,如果用户将path / to / table / gender = male传递给SparkSession.read.parquet或SparkSession.read.load,则性别将不会被视为分区列。 如果用户需要指定分区发现应该从哪个基本路径开始,他们可以在数据源选项中设置basePath。 例如,当path / to / table / gender = male是数据的路径并且用户将basePath设置为path / to / table /时,gender将是一个分区列。

模式合并

与ProtocolBuffer,Avro和Thrift一样,Parquet也支持模式演进。 用户可以从一个简单的模式开始,并根据需要逐渐向模式添加更多列。 以这种方式,用户可能最终得到具有不同但相互兼容的模式的多个Parquet文件。 Parquet数据源现在能够自动检测这种情况并合并所有这些文件的模式。

由于模式合并是一个相对昂贵的操作,在大多数情况下不是必需的,因此默认情况下将其从1.5.0开始关闭。 您可以通过以下方式启用它:

1.在阅读Parquet文件时将数据源选项mergeSchema设置为true(如下面的示例所示)或

2.将全局SQL选项spark.sql.parquet.mergeSchema设置为true。

// This is used to implicitly convert an RDD to a DataFrame.
import spark.implicits._

// Create a simple DataFrame, store into a partition directory
val squaresDF = spark.sparkContext.makeRDD(1 to 5).map(i => (i, i * i)).toDF("value", "square")
squaresDF.write.parquet("data/test_table/key=1")

// Create another DataFrame in a new partition directory,
// adding a new column and dropping an existing column
val cubesDF = spark.sparkContext.makeRDD(6 to 10).map(i => (i, i * i * i)).toDF("value", "cube")
cubesDF.write.parquet("data/test_table/key=2")

// Read the partitioned table
val mergedDF = spark.read.option("mergeSchema", "true").parquet("data/test_table")
mergedDF.printSchema()

// The final schema consists of all 3 columns in the Parquet files together
// with the partitioning column appeared in the partition directory paths
// root
//  |-- value: int (nullable = true)
//  |-- square: int (nullable = true)
//  |-- cube: int (nullable = true)
//  |-- key: int (nullable = true)
Hive Metastore Parquet表转换

当读取和写入Hive metastore Parquet表时,Spark SQL将尝试使用自己的Parquet支持,而不是Hive SerDe,以获得更好的性能。 此行为由spark.sql.hive.convertMetastoreParquet配置控制,默认情况下处于打开状态。

Hive / Parquet模式对照

表模式处理的角度来看,Hive和Parquet之间有两个主要区别

1.Hive不区分大小写,而Parquet不是。

2.Hive将所有列视为可空,而Parquet中的可空性很重要。

由于这个原因,当将Hive Metastore Parquet表转换为Spark SQL Parquet表时,我们必须调整Hive Metastore模式与Parquet模式。 对照规则为:

1.在两个模式中具有相同名称的字段必须具有相同的数据类型,而不管可否为空。 已对帐字段应具有Parquet侧的数据类型,以便遵守可空性。

2.已协调模式恰好包含Hive metastore模式中定义的那些字段。

·在已对帐模式中,仅在Parquet模式中的出现任何字段都会被删除。

·只有出现在Hive Metastore模式中的任何字段才会在已调节模式中添加为可空字段。

元数据刷新

Spark SQL缓存Parquet元数据以提高性能。 启用Hive metastore Parquet表转换时,这些转换表的元数据也会被缓存。 如果这些表是由Hive或其他外部工具更新的,则需要手动刷新它们以确保元数据的一致性

// spark is an existing SparkSession
spark.catalog.refreshTable("my_table")
配置

Parquet的配置可以使用SparkSession上的setConf方法或通过使用SQL运行SET key = value命令来完成。

Property Name Default Meaning
spark.sql.parquet.binaryAsString                                                  false Some other Parquet-producing systems, in particular Impala, Hive, and older versions of Spark SQL, do not differentiate between binary data and strings when writing out the Parquet schema. This flag tells Spark SQL to interpret binary data as a string to provide compatibility with these systems.
spark.sql.parquet.int96AsTimestamp true Some Parquet-producing systems, in particular Impala and Hive, store Timestamp into INT96. This flag tells Spark SQL to interpret INT96 data as a timestamp to provide compatibility with these systems.
spark.sql.parquet.cacheMetadata true Turns on caching of Parquet schema metadata. Can speed up querying of static data.
spark.sql.parquet.compression.codec snappy                                  Sets the compression codec use when writing Parquet files. Acceptable values include: uncompressed, snappy, gzip, lzo.
spark.sql.parquet.filterPushdown true Enables Parquet filter push-down optimization when set to true.
spark.sql.hive.convertMetastoreParquet                                                                        true When set to false, Spark SQL will use the Hive SerDe for parquet tables instead of the built in support.
spark.sql.parquet.mergeSchema false

When true, the Parquet data source merges schemas collected from all data files, otherwise the schema is picked from the summary file or a random data file if no summary file is available.

spark.sql.optimizer.metadataOnly true

When true, enable the metadata-only query optimization that use the table's metadata to produce the partition columns instead of table scans. It applies when all the columns scanned are partition columns and the query has an aggregate operator that satisfies distinct semantics.

JSON Datasets

Spark SQL可以自动推断JSON数据集的模式,并将其作为Dataset [Row]加载。 此转换可以使用SparkSession.read.json()对String的RDD或JSON文件进行。

请注意,作为json文件提供的文件不是典型的JSON文件。 每行必须包含一个单独的,自包含的有效JSON对象。 有关详细信息,请参阅JSON行文本格式,也称为换行分隔的JSON。 因此,常规的多行JSON文件通常会失败。

// A JSON dataset is pointed to by path.
// The path can be either a single text file or a directory storing text files
val path = "examples/src/main/resources/people.json"
val peopleDF = spark.read.json(path)

// The inferred schema can be visualized using the printSchema() method
peopleDF.printSchema()
// root
//  |-- age: long (nullable = true)
//  |-- name: string (nullable = true)

// Creates a temporary view using the DataFrame
peopleDF.createOrReplaceTempView("people")

// SQL statements can be run by using the sql methods provided by spark
val teenagerNamesDF = spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19")
teenagerNamesDF.show()
// +------+
// |  name|
// +------+
// |Justin|
// +------+

// Alternatively, a DataFrame can be created for a JSON dataset represented by
// an RDD[String] storing one JSON object per string
val otherPeopleRDD = spark.sparkContext.makeRDD(
  """{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil)
val otherPeople = spark.read.json(otherPeopleRDD)
otherPeople.show()
// +---------------+----+
// |        address|name|
// +---------------+----+
// |[Columbus,Ohio]| Yin|
// +---------------+----+
Hive 表--SKIP(本节的例子位于 examples/src/main/scala/org/apache/spark/examples/sql/hive/SparkHiveExample.scala

Spark SQL还支持读取和写入存储在Apache Hive中的数据。 但是,由于Hive有大量的依赖关系,这些依赖关系不包括在默认的Spark分发包中。 如果Hive依赖项可以在类路径中找到,Spark将自动加载它们。 注意,这些Hive依赖项必须也存在于所有的工作节点上,因为它们需要访问Hive序列化和反序列化库(SerDes),以访问存储在Hive中的数据。

通过将您的hive-site.xml,core-site.xml(用于安全配置)和hdfs-site.xml(用于HDFS配置)文件放在conf /中来完成Hive的配置。

当使用Hive时,必须使用支持的Hive的实例化SparkSession,包括与持久Hive Metastore的连接,Hive serdes的支持以及Hive用户定义的函数。 没有现有Hive部署的用户仍然可以启用Hive支持。 当未由hive-site.xml配置时,上下文将在当前目录中自动创建metastore_db,并创建由spark.sql.warehouse.dir配置的目录,该目录默认为启动Spark应用程序的目录中的spark-warehouse目录 。 请注意,hive-site.xml中的hive.metastore.warehouse.dir属性自Spark 2.0.0以来已弃用。 相反,使用spark.sql.warehouse.dir来指定仓库中数据库的默认位置。 您可能需要向启动Spark应用程序的用户授予写入权限。

import org.apache.spark.sql.Row
import org.apache.spark.sql.SparkSession

case class Record(key: Int, value: String)

// warehouseLocation points to the default location for managed databases and tables
val warehouseLocation = "spark-warehouse"

val spark = SparkSession
  .builder()
  .appName("Spark Hive Example")
  .config("spark.sql.warehouse.dir", warehouseLocation)
  .enableHiveSupport()
  .getOrCreate()

import spark.implicits._
import spark.sql

sql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING)")
sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src")

// Queries are expressed in HiveQL
sql("SELECT * FROM src").show()
// +---+-------+
// |key|  value|
// +---+-------+
// |238|val_238|
// | 86| val_86|
// |311|val_311|
// ...

// Aggregation queries are also supported.
sql("SELECT COUNT(*) FROM src").show()
// +--------+
// |count(1)|
// +--------+
// |    500 |
// +--------+

// The results of SQL queries are themselves DataFrames and support all normal functions.
val sqlDF = sql("SELECT key, value FROM src WHERE key < 10 ORDER BY key")

// The items in DaraFrames are of type Row, which allows you to access each column by ordinal.
val stringsDS = sqlDF.map {
  case Row(key: Int, value: String) => s"Key: $key, Value: $value"
}
stringsDS.show()
// +--------------------+
// |               value|
// +--------------------+
// |Key: 0, Value: val_0|
// |Key: 0, Value: val_0|
// |Key: 0, Value: val_0|
// ...

// You can also use DataFrames to create temporary views within a SparkSession.
val recordsDF = spark.createDataFrame((1 to 100).map(i => Record(i, s"val_$i")))
recordsDF.createOrReplaceTempView("records")

// Queries can then join DataFrame data with data stored in Hive.
sql("SELECT * FROM records r JOIN src s ON r.key = s.key").show()
// +---+------+---+------+
// |key| value|key| value|
// +---+------+---+------+
// |  2| val_2|  2| val_2|
// |  4| val_4|  4| val_4|
// |  5| val_5|  5| val_5|
// ...
与不同版本的Hive Metastore交互

Spark SQL的Hive支持的最重要的部分之一是与Hive metastore的交互,这使Spark SQL能够访问Hive表的元数据。 从Spark 1.4.0开始,使用下面描述的配置,可以使用Spark SQL的单个二进制构建来查询不同版本的Hive Metastore。 请注意,独立于用于与Metastore通信的Hive版本,Spark SQL内部将根据Hive 1.2.1进行编译,并将这些类用于内部执行(serdes,UDF,UDAF等)。

以下选项可配置用于检索元数据的Hive版本:

Property Name Default Meaning
spark.sql.hive.metastore.version 1.2.1 Version of the Hive metastore. Available options are 0.12.0 through 1.2.1.
spark.sql.hive.metastore.jars builtin Location of the jars that should be used to instantiate the HiveMetastoreClient. This property can be one of three options:
  1. builtinUse Hive 1.2.1, which is bundled with the Spark assembly when -Phiveis enabled. When this option is chosen,spark.sql.hive.metastore.version must be either 1.2.1 or not defined.
  2. mavenUse Hive jars of specified version downloaded from Maven repositories. This configuration is not generally recommended for production deployments.
  3. A classpath in the standard format for the JVM. This classpath must include all of Hive and its dependencies, including the correct version of Hadoop. These jars only need to be present on the driver, but if you are running in yarn cluster mode then you must ensure they are packaged with your application.
spark.sql.hive.metastore.sharedPrefixes com.mysql.jdbc,
org.postgresql,
com.microsoft.sqlserver,
oracle.jdbc

A comma separated list of class prefixes that should be loaded using the classloader that is shared between Spark SQL and a specific version of Hive. An example of classes that should be shared is JDBC drivers that are needed to talk to the metastore. Other classes that need to be shared are those that interact with classes that are already shared. For example, custom appenders that are used by log4j.

spark.sql.hive.metastore.barrierPrefixes (empty)

A comma separated list of class prefixes that should explicitly be reloaded for each version of Hive that Spark SQL is communicating with. For example, Hive UDFs that are declared in a prefix that typically would be shared (i.e.org.apache.spark.*).

JDBC To Other Databases

Spark SQL还包括可以 使用JDBC从其他数据库读取数据的数据源。 此功能应优先于JdbcRDD。 这是因为结果作为DataFrame返回,并且可以很容易地在Spark SQL中处理或与其他数据源结合。 JDBC数据源也更容易在Java或Python中使用,因为它不需要用户提供ClassTag。 (请注意,这与Spark SQL JDBC服务器不同,后者允许其他应用程序使用Spark SQL运行查询)。

作为开始,您需要在spark类路径中包含特定数据库的JDBC驱动程序。 例如,要从Spark Shell连接到postgres,您将运行以下命令:

bin/spark-shell --driver-class-path postgresql-9.4.1207.jar --jars postgresql-9.4.1207.jar
远程数据库中的表可以使用Data Sources API加载为DataFrame或Spark SQL临时视图 。 用户可以在数据源选项中指定JDBC连接属性。 用户和密码通常作为用于登录数据源的连接属性提供。 除了连接属性外,Spark还支持以下区分大小写的选项:

Property Name Meaning
url The JDBC URL to connect to. The source-specific connection properties may be specified in the URL. e.g.,jdbc:postgresql://localhost/test?user=fred&password=secret
dbtable The JDBC table that should be read. Note that anything that is valid in a FROM clause of a SQL query can be used. For example, instead of a full table you could also use a subquery in parentheses.
driver The class name of the JDBC driver to use to connect to this URL.
partitionColumn, lowerBound, upperBound, numPartitions      These options must all be specified if any of them is specified. They describe how to partition the table when reading in parallel from multiple workers. partitionColumn must be a numeric column from the table in question. Notice that lowerBound and upperBound are just used to decide the partition stride, not for filtering the rows in table. So all rows in the table will be partitioned and returned. This option applies only to reading.
fetchsize The JDBC fetch size, which determines how many rows to fetch per round trip. This can help performance on JDBC drivers which default to low fetch size (eg. Oracle with 10 rows). This option applies only to reading.
batchsize The JDBC batch size, which determines how many rows to insert per round trip. This can help performance on JDBC drivers. This option applies only to writing. It defaults to 1000.
isolationLevel The transaction isolation level, which applies to current connection. It can be one of NONEREAD_COMMITTEDREAD_UNCOMMITTED,REPEATABLE_READ, or SERIALIZABLE, corresponding to standard transaction isolation levels defined by JDBC's Connection object, with default of READ_UNCOMMITTED. This option applies only to writing. Please refer the documentation in java.sql.Connection.
truncate This is a JDBC writer related option. When SaveMode.Overwrite is enabled, this option causes Spark to truncate an existing table instead of dropping and recreating it. This can be more efficient, and prevents the table metadata (e.g., indices) from being removed. However, it will not work in some cases, such as when the new data has a different schema. It defaults to false. This option applies only to writing.
createTableOptions This is a JDBC writer related option. If specified, this option allows setting of database-specific table and partition options when creating a table (e.g., CREATE TABLE t (name string) ENGINE=InnoDB.). This option applies only to writing.

// Note: JDBC loading and saving can be achieved via either the load/save or jdbc methods
// Loading data from a JDBC source
val jdbcDF = spark.read
  .format("jdbc")
  .option("url", "jdbc:postgresql:dbserver")
  .option("dbtable", "schema.tablename")
  .option("user", "username")
  .option("password", "password")
  .load()

val connectionProperties = new Properties()
connectionProperties.put("user", "username")
connectionProperties.put("password", "password")
val jdbcDF2 = spark.read
  .jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)

// Saving data to a JDBC source
jdbcDF.write
  .format("jdbc")
  .option("url", "jdbc:postgresql:dbserver")
  .option("dbtable", "schema.tablename")
  .option("user", "username")
  .option("password", "password")
  .save()

jdbcDF2.write
  .jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)
注:完整代码位于 examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala。

Troubleshooting(故障排除)

·JDBC驱动程序类对客户端会话和所有执行程序上的原始类装入器必须可见。 这是因为Java的DriverManager类进行了一个安全检查,导致它在打开一个连接时忽略对原始类加载器不可见的所有驱动程序。 执行此操作的一个方便的方法是在所有工作线程节点上修改compute_classpath.sh以包含驱动程序JAR。

·一些数据库,如H2,将所有名称转换为大写。 在Spark SQL中,您需要使用大写来引用这些名称。

性能调优

对于某些工作负载,可以通过在内存中高速缓存数据或通过启用一些实验选项来提高性能。

1)在内存中缓存数据

Spark SQL可以通过调用spark.cacheTable(“tableName”)或dataFrame.cache(),使用内存中列的格式来缓存表。 然后Spark SQL将仅扫描所需的列,并将自动调整压缩以最小化内存使用和GC压力。 您可以调用spark.uncacheTable(“tableName”)从内存中删除该表。

内存缓存的配置可以使用SparkSession上的setConf方法或通过使用SQL运行SET key = value命令来完成。

Property Name Default Meaning
spark.sql.inMemoryColumnarStorage.compressed true When set to true Spark SQL will automatically select a compression codec for each column based on statistics of the data.
spark.sql.inMemoryColumnarStorage.batchSize 10000 Controls the size of batches for columnar caching. Larger batch sizes can improve memory utilization and compression, but risk OOMs when caching data.
其他配置选项

以下选项也可用于调整查询执行的性能。 这些选项可能会在将来的版本中被弃用,因为会自动执行更多优化。

Property Name Default Meaning
spark.sql.files.maxPartitionBytes 134217728 (128 MB) The maximum number of bytes to pack into a single partition when reading files.
spark.sql.files.openCostInBytes 4194304 (4 MB) The estimated cost to open a file, measured by the number of bytes could be scanned in the same time. This is used when putting multiple files into a partition. It is better to over estimated, then the partitions with small files will be faster than partitions with bigger files (which is scheduled first).
spark.sql.broadcastTimeout 300

Timeout in seconds for the broadcast wait time in broadcast joins

spark.sql.autoBroadcastJoinThreshold                                                                                                      10485760 (10 MB)                                                                                             Configures the maximum size in bytes for a table that will be broadcast to all worker nodes when performing a join. By setting this value to -1 broadcasting can be disabled. Note that currently statistics are only supported for Hive Metastore tables where the command ANALYZE TABLE <tableName> COMPUTE STATISTICS noscan has been run.
spark.sql.shuffle.partitions 200 Configures the number of partitions to use when shuffling data for joins or aggregations.
分布式SQL引擎

通过使用其JDBC / ODBC或命令行界面,Spark SQL还可以充当分布式查询引擎。 在此模式下,终端用户或应用程序可以与Spark SQL直接交互以运行SQL查询,而无需编写任何代码。

运行Thrift JDBC / ODBC服务器

这里实现的Thrift JDBC / ODBC服务器对应于Hive 1.2.1中的HiveServer2。您可以使用Spark或Hive 1.2.1附带的beeline脚本测试JDBC服务器。

要启动JDBC / ODBC服务器,请在Spark目录中运行以下命令:

./sbin/start-thriftserver.sh
此脚本接受所有bin / spark-submit命令行选项,以及--hiveconf选项以指定Hive属性。 您可以运行./sbin/start-thriftserver.sh --help获取所有可用选项的完整列表。 默认情况下,服务器侦听localhost:10000。 您可以通过环境变量覆盖此行为,例如:

export HIVE_SERVER2_THRIFT_PORT=<listening-port>
export HIVE_SERVER2_THRIFT_BIND_HOST=<listening-host>
./sbin/start-thriftserver.sh \
  --master <master-uri> \
  ...
或通过系统属性:

./sbin/start-thriftserver.sh \
  --hiveconf hive.server2.thrift.port=<listening-port> \
  --hiveconf hive.server2.thrift.bind.host=<listening-host> \
  --master <master-uri>
  ...
现在你可以使用beeline来测试Thrift JDBC / ODBC服务器:

./bin/beeline
使用以下命令连接到JDBC / ODBC服务器:

beeline> !connect jdbc:hive2://localhost:10000
Beeline将要求您提供用户名和密码。 在非安全模式下,只需在计算机上输入用户名和空白密码即可。 对于安全模式,请按照beeline文档中的说明进行操作。

通过将hive-site.xml,core-site.xml和hdfs-site.xml文件放在conf /中来配置Hive。

您还可以使用Hive附带的beeline脚本。

Thrift JDBC服务器还支持通过HTTP传输发送thrift RPC消息。 使用以下设置以启用HTTP模式作为系统属性或在conf /中的hive-site.xml文件中:

hive.server2.transport.mode - Set this to value: http
hive.server2.thrift.http.port - HTTP port number fo listen on; default is 10001
hive.server2.http.endpoint - HTTP endpoint; default is cliservice
为了测试,请使用beeline在http模式下使用以下命令连接到JDBC / ODBC服务器:

beeline> !connect jdbc:hive2://<host>:<port>/<database>?hive.server2.transport.mode=http;hive.server2.thrift.http.path=<http_endpoint>
运行Spark SQL CLI

Spark SQL CLI是一种方便的工具,可以在本地模式下运行Hive Metastore服务,并从命令行执行查询输入。 请注意,Spark SQL CLI无法与Thrift JDBC服务器通信。

要启动Spark SQL CLI,请在Spark目录中运行以下命令:

./bin/spark-sql
通过将hive-site.xml,core-site.xml和hdfs-site.xml文件放在conf /中来配置Hive。 您可以运行./bin/spark-sql --help获取所有可用选项的完整列表。

迁移指南(略)


Running Spark on YARN

在Spark版本0.6.0中添加了对在YARN(Hadoop NextGen)集群上运行的支持,并在后续版本中得到改进。

在YARN上启动Spark

确保HADOOP_CONF_DIR或YARN_CONF_DIR指向包含Hadoop集群的(客户端)配置文件的目录。这些配置用于写入HDFS并连接到YARN ResourceManager。此目录中包含的配置将分发到YARN群集,以便应用程序使用的所有容器都使用相同的配置。如果配置引用了Java系统属性或未由YARN管理的环境变量,则还应在Spark应用程序的配置(驱动程序,executors和在客户端模式下运行时的AM--Application Master)中设置它们。

两种部署模式可用于在YARN上启动Spark应用程序。在集群模式(cluster mode)下,Spark驱动程序(driver)在集群上由YARN管理的Application Master进程内运行,客户端可以在启动应用程序后离开。在客户端模式(client mode)下,驱动程序(driver)在客户端进程中运行,应用程序主服务器(AM)仅用于向YARN请求资源

与Spark独立模式和Mesos模式不同(它们的主节点地址在--master参数中指定);在YARN模式下,ResourceManager的地址从Hadoop配置中选取。因此,-master参数是yarn。

在集群模式下启动Spark应用程序:

$ ./bin/spark-submit --class path.to.your.Class --master yarn --deploy-mode cluster [options] <app jar> [app options]
例如:

$ ./bin/spark-submit --class org.apache.spark.examples.SparkPi \
    --master yarn \
    --deploy-mode cluster \
    --driver-memory 4g \
    --executor-memory 2g \
    --executor-cores 1 \
    --queue thequeue \
    lib/spark-examples*.jar \
    10
以上启动一个YARN客户端程序,该程序启动了默认的应用程序主服务器(AM)。 然后SparkPi将作为Application Master的子线程运行。 客户端将定期轮询AM以获取状态更新,并在控制台中显示它们。 应用程序完成运行后,客户端将退出。 请参阅下面的“调试应用程序--Debugging your Application”部分,了解如何查看驱动程序和执行程序日志。 

要在客户端模式(client mode)下启动Spark应用程序,请执行相同操作,但用客户端替换集群(cluster-->client)。 以下显示了如何在客户端模式(client mode)下运行spark-shell:

$ ./bin/spark-shell --master yarn --deploy-mode client
添加其他JAR包

在集群模式下,驱动程序在与客户端不同的计算机上运行,因此SparkContext.addJar将无法使用客户端本地的文件。 要使客户端上的文件可用于SparkContext.addJar,请在启动命令中使用--jars选项来包含这些文件。

$ ./bin/spark-submit --class my.main.Class \
    --master yarn \
    --deploy-mode cluster \
    --jars my-other-jar.jar,my-other-other-jar.jar \
    my-main-jar.jar \
    app_arg1 app_arg2
准备工作

在YARN上运行Spark需要使用YARN支持构建的Spark的二进制分发包。 二进制分发包可以从Spark网站的下载页面下载。 要自己构建Spark,请参考Building Spark。

要使Spark运行时jar可以从YARN端访问,可以指定spark.yarn.archive或spark.yarn.jars。 有关详细信息,请参阅Spark属性。 如果既没有指定spark.yarn.archive也没有指定spark.yarn.jars,Spark将使用$ SPARK_HOME / jars下的所有jars创建一个zip文件,并将其上传到分布式缓存中。

配置

对于Spark on YARN和其他部署模式,大多数配置相同。 有关这些的更多信息,请参阅配置页。 这些是特定于YARN上的Spark的配置。

调试应用程序

在YARN术语中,executors和Application Masters在“容器”中运行。 YARN有两种模式用于在应用程序完成后处理容器日志。

1、  如果启用日志聚合(使用yarn.log-aggregation-enable配置),容器日志将复制到HDFS并在本地计算机上删除。 可以使用yarn logs命令从集群中的任何位置查看这些日志。

yarn logs -applicationId <app ID>
执行该命令后,将打印出来自给定应用程序的所有容器的所有日志文件的内容。您还可以使用HDFS shell或API直接在HDFS中查看容器日志文件。可以通过查看您的YARN配置(yarn.nodemanager.remote-app-log-dir和yarn.nodemanager.remote-app-log-dir-suffix)找到它们所在的目录。日志还可以在Spark Web UI的“Executors Tab”下找到。您需要同时运行Spark历史记录服务器和MapReduce历史记录服务器,并在yarn-site.xml中正确配置yarn.log.server.url。 Spark历史记录服务器UI上的日志URL将重定向您到MapReduce历史记录服务器以显示聚合日志。

2、当未打开日志聚合时,日志将本地保留在每台计算机上的YARN_APP_LOGS_DIR下,通常配置为/ tmp / logs或$ HADOOP_HOME / logs / userlogs,具体取决于Hadoop版本和安装。查看容器的日志需要转到包含它们的主机并在此目录中查找。子目录根据应用程序ID和容器ID组织日志文件。日志还可以在Spark Web UI的“Executors Tap”下找到,并且不需要运行MapReduce历史记录服务器。

要查看每个容器的启动环境,请将yarn.nodemanager.delete.debug-delay-sec增加到一个较大的值(例如36000),然后通过启动容器的节点中的yarn.nodemanager.local-dirs访问应用程序缓存。此目录包含启动脚本,JAR和用于启动每个容器的所有环境变量。这个过程对于调试类路径问题特别有用。 (请注意,启用此功能需要群集设置和所有节点管理器重新启动的管理员权限。因此,这不适用于托管集群)。

要为AM或executors使用自定义log4j配置,请选择以下选项:

·通过将spark-submit添加到要与应用程序一起上传的文件的--files列表中,上传一个自定义的log4j.properties。

`添加-Dlog4j.configuration = <配置文件的位置>到spark.driver.extraJavaOptions(for driver)或spark.executor.extraJavaOptions(for executors)。 请注意,如果使用文件,则应显式提供file:协议,并且该文件需要在所有节点上本地存在。

`更新$ SPARK_CONF_DIR / log4j.properties文件,它将与其他配置一起自动上传。 请注意,如果指定了多个选项,上面2个选项的优先级高于此选项

注意,对于上面的第一个选项,executors和AM将共享相同的log4j配置,当它们在同一节点上运行时(例如,尝试写入同一个日志文件),可能会导致问题。

如果需要引用正确的位置用以将日志文件放在YARN中,以便YARN可以正确显示和聚合它们,请在log4j.properties中使用spark.yarn.app.container.log.dir。 例如,log4j.appender.file_appender.File = $ {spark.yarn.app.container.log.dir} /spark.log。 对于流应用程序,配置RollingFileAppender并将文件位置设置为YARN的日志目录将避免由大日志文件导致的磁盘溢出,并且可以使用YARN的日志工具访问日志。

要为AM和executors使用自定义的metrics.properties,请更新$ SPARK_CONF_DIR / metrics.properties文件。 它将自动与其他配置一起上传,因此您不需要使用--files手动指定它。

Spark 属性
Property Name Default Meaning
spark.yarn.am.memory 512m Amount of memory to use for the YARN Application Master in client mode, in the same format as JVM memory strings (e.g. 512m2g). In cluster mode, use spark.driver.memoryinstead.

Use lower-case suffixes, e.g. kmgt, and p, for kibi-, mebi-, gibi-, tebi-, and pebibytes, respectively.

spark.yarn.am.cores 1 Number of cores to use for the YARN Application Master in client mode. In cluster mode, usespark.driver.cores instead.
spark.yarn.am.waitTime 100s In cluster mode, time for the YARN Application Master to wait for the SparkContext to be initialized. In client mode, time for the YARN Application Master to wait for the driver to connect to it.
spark.yarn.submit.file.replication                                                                                                                                                                                      The default HDFS replication (usually)                                                                                                                                                                                                                                                                                                                                                 HDFS replication level for the files uploaded into HDFS for the application. These include things like the Spark jar, the app jar, and any distributed cache files/archives.
spark.yarn.stagingDir Current user's home directory in the filesystem                                                                                                                                                    Staging directory used while submitting applications.
spark.yarn.preserve.staging.files false Set to true to preserve the staged files (Spark jar, app jar, distributed cache files) at the end of the job rather than delete them.
spark.yarn.scheduler.heartbeat.interval-ms 3000 The interval in ms in which the Spark application master heartbeats into the YARN ResourceManager. The value is capped at half the value of YARN's configuration for the expiry interval, i.e. yarn.am.liveness-monitor.expiry-interval-ms.
spark.yarn.scheduler.initial-allocation.interval                                                                                                                                                                                                                                                                                                             200ms The initial interval in which the Spark application master eagerly heartbeats to the YARN ResourceManager when there are pending container allocation requests. It should be no larger than spark.yarn.scheduler.heartbeat.interval-ms. The allocation interval will doubled on successive eager heartbeats if pending containers still exist, untilspark.yarn.scheduler.heartbeat.interval-ms is reached.
spark.yarn.max.executor.failures numExecutors * 2, with minimum of 3                                                                                         The maximum number of executor failures before failing the application.
spark.yarn.historyServer.address (none) The address of the Spark history server, e.g. host.com:18080. The address should not contain a scheme (http://). Defaults to not being set since the history server is an optional service. This address is given to the YARN ResourceManager when the Spark application finishes to link the application from the ResourceManager UI to the Spark history server UI. For this property, YARN properties can be used as variables, and these are substituted by Spark at runtime. For example, if the Spark history server runs on the same node as the YARN ResourceManager, it can be set to ${hadoopconf-yarn.resourcemanager.hostname}:18080.
spark.yarn.dist.archives (none) Comma separated list of archives to be extracted into the working directory of each executor.
spark.yarn.dist.files (none) Comma-separated list of files to be placed in the working directory of each executor.
spark.yarn.dist.jars (none) Comma-separated list of jars to be placed in the working directory of each executor.
spark.executor.instances 2 The number of executors for static allocation. With spark.dynamicAllocation.enabled, the initial set of executors will be at least this large.
spark.yarn.executor.memoryOverhead executorMemory * 0.10, with minimum of 384 The amount of off-heap memory (in megabytes) to be allocated per executor. This is memory that accounts for things like VM overheads, interned strings, other native overheads, etc. This tends to grow with the executor size (typically 6-10%).
spark.yarn.driver.memoryOverhead driverMemory * 0.10, with minimum of 384 The amount of off-heap memory (in megabytes) to be allocated per driver in cluster mode. This is memory that accounts for things like VM overheads, interned strings, other native overheads, etc. This tends to grow with the container size (typically 6-10%).
spark.yarn.am.memoryOverhead AM memory * 0.10, with minimum of 384 Same as spark.yarn.driver.memoryOverhead, but for the YARN Application Master in client mode.
spark.yarn.am.port (random) Port for the YARN Application Master to listen on. In YARN client mode, this is used to communicate between the Spark driver running on a gateway and the YARN Application Master running on YARN. In YARN cluster mode, this is used for the dynamic executor feature, where it handles the kill from the scheduler backend.
spark.yarn.queue default The name of the YARN queue to which the application is submitted.
spark.yarn.jars (none) List of libraries containing Spark code to distribute to YARN containers. By default, Spark on YARN will use Spark jars installed locally, but the Spark jars can also be in a world-readable location on HDFS. This allows YARN to cache it on nodes so that it doesn't need to be distributed each time an application runs. To point to jars on HDFS, for example, set this configuration to hdfs:///some/path. Globs are allowed.
spark.yarn.archive (none) An archive containing needed Spark jars for distribution to the YARN cache. If set, this configuration replaces spark.yarn.jars and the archive is used in all the application's containers. The archive should contain jar files in its root directory. Like with the previous option, the archive can also be hosted on HDFS to speed up file distribution.
spark.yarn.access.namenodes (none) A comma-separated list of secure HDFS namenodes your Spark application is going to access. For example,spark.yarn.access.namenodes=hdfs://nn1.com:8032,hdfs://nn2.com:8032, webhdfs://nn3.com:50070. The Spark application must have access to the namenodes listed and Kerberos must be properly configured to be able to access them (either in the same realm or in a trusted realm). Spark acquires security tokens for each of the namenodes so that the Spark application can access those remote HDFS clusters.
spark.yarn.appMasterEnv.[EnvironmentVariableName] (none) Add the environment variable specified by EnvironmentVariableName to the Application Master process launched on YARN. The user can specify multiple of these and to set multiple environment variables. In cluster mode this controls the environment of the Spark driver and in client mode it only controls the environment of the executor launcher.
spark.yarn.containerLauncherMaxThreads 25 The maximum number of threads to use in the YARN Application Master for launching executor containers.
spark.yarn.am.extraJavaOptions (none) A string of extra JVM options to pass to the YARN Application Master in client mode. In cluster mode, use spark.driver.extraJavaOptions instead. Note that it is illegal to set maximum heap size (-Xmx) settings with this option. Maximum heap size settings can be set with spark.yarn.am.memory
spark.yarn.am.extraLibraryPath (none) Set a special library path to use when launching the YARN Application Master in client mode.
spark.yarn.maxAppAttempts yarn.resourcemanager.am.max-attempts in YARN The maximum number of attempts that will be made to submit the application. It should be no larger than the global number of max attempts in the YARN configuration.
spark.yarn.am.attemptFailuresValidityInterval (none) Defines the validity interval for AM failure tracking. If the AM has been running for at least the defined interval, the AM failure count will be reset. This feature is not enabled if not configured, and only supported in Hadoop 2.6+.
spark.yarn.executor.failuresValidityInterval (none) Defines the validity interval for executor failure tracking. Executor failures which are older than the validity interval will be ignored.
spark.yarn.submit.waitAppCompletion true In YARN cluster mode, controls whether the client waits to exit until the application completes. If set to true, the client process will stay alive reporting the application's status. Otherwise, the client process will exit after submission.
spark.yarn.am.nodeLabelExpression (none) A YARN node label expression that restricts the set of nodes AM will be scheduled on. Only versions of YARN greater than or equal to 2.6 support node label expressions, so when running against earlier versions, this property will be ignored.
spark.yarn.executor.nodeLabelExpression (none) A YARN node label expression that restricts the set of nodes executors will be scheduled on. Only versions of YARN greater than or equal to 2.6 support node label expressions, so when running against earlier versions, this property will be ignored.
spark.yarn.tags (none) Comma-separated list of strings to pass through as YARN application tags appearing in YARN ApplicationReports, which can be used for filtering when querying YARN apps.
spark.yarn.keytab (none) The full path to the file that contains the keytab for the principal specified above. This keytab will be copied to the node running the YARN Application Master via the Secure Distributed Cache, for renewing the login tickets and the delegation tokens periodically. (Works also with the "local" master)
spark.yarn.principal (none) Principal to be used to login to KDC, while running on secure HDFS. (Works also with the "local" master)
spark.yarn.config.gatewayPath (none) A path that is valid on the gateway host (the host where a Spark application is started) but may differ for paths for the same resource in other nodes in the cluster. Coupled withspark.yarn.config.replacementPath, this is used to support clusters with heterogeneous configurations, so that Spark can correctly launch remote processes.

The replacement path normally will contain a reference to some environment variable exported by YARN (and, thus, visible to Spark containers).

For example, if the gateway node has Hadoop libraries installed on /disk1/hadoop, and the location of the Hadoop install is exported by YARN as the HADOOP_HOME environment variable, setting this value to /disk1/hadoop and the replacement path to $HADOOP_HOME will make sure that paths used to launch remote processes properly reference the local YARN configuration.

spark.yarn.config.replacementPath (none) See spark.yarn.config.gatewayPath.
spark.yarn.security.credentials.${service}.enabled true Controls whether to obtain credentials for services when security is enabled. By default, credentials for all supported services are retrieved when those services are configured, but it's possible to disable that behavior if it somehow conflicts with the application being run. For further details please see [Running in a Secure Cluster](running-on-yarn.html#running-in-a-secure-cluster)
spark.yarn.rolledLog.includePattern (none) Java Regex to filter the log files which match the defined include pattern and those log files will be aggregated in a rolling fashion. This will be used with YARN's rolling log aggregation, to enable this feature in YARN side yarn.nodemanager.log-aggregation.roll-monitoring-interval-seconds should be configured in yarn-site.xml. This feature can only be used with Hadoop 2.6.1+. The Spark log4j appender needs be changed to use FileAppender or another appender that can handle the files being removed while its running. Based on the file name configured in the log4j configuration (like spark.log), the user should set the regex (spark*) to include all the log files that need to be aggregated.
spark.yarn.rolledLog.excludePattern (none) Java Regex to filter the log files which match the defined exclude pattern and those log files will not be aggregated in a rolling fashion. If the log file name matches both the include and the exclude pattern, this file will be excluded eventually.
注意事项

1、核心请求是否在调度决策中得到执行取决于使用的调度程序及其配置方式。
2、在集群模式下,Spark执行器和Spark驱动程序使用的本地目录将是为YARN(Hadoop YARN配置yarn.nodemanager.local-dirs)配置的本地目录。如果用户指定spark.local.dir,它将被忽略。在客户端模式下,Sparkexecutor将使用为YARN配置的本地目录,而Spark驱动程序将使用spark.local.dir中定义的目录。这是因为在客户端模式下Spark驱动程序不在YARN群集上运行,只有Sparkexecutors运行在集群上。
3、--files和--archives选项支持使用类似于Hadoop的#指定文件名。例如,您可以指定:--files localtest.txt#appSees.txt,这将会将您在本地名为localtest.txt的文件上传到HDFS,但这将通过名称appSees.txt链接到你本地的localhost.txt文件。当运行在集群上时,您的应用程序应使用名为appSees.txt引用它。
4、--jars选项允许SparkContext.addJar函数在您使用本地文件并在集群模式下运行时工作。如果您使用HDFS,HTTP,HTTPS或FTP文件,则不需要使用它。

在安全集群中运行

如在安全性/Security中所述,Kerberos在安全的Hadoop集群中用于验证与服务和客户端相关联的主体。这允许客户端请求这些已验证的服务;向授权的主体授予权利的服务。

Hadoop服务发出hadoop令牌以授予对服务和数据的访问权限。客户端必须首先获取它们将要访问的服务的令牌,并将它们与它们的应用程序一起传递,当它在YARN集群中启动时。

对于与HDFS,HBase和Hive进行交互的Spark应用程序,它必须使用启动应用程序的用户的Kerberos凭据获取相关令牌,即主体的身份(id)将成为已启动的Spark应用程序的id。

这通常在启动时完成:在安全集群中,Spark将自动获取集群的HDFS文件系统的令牌,并可能为HBase和Hive获取。

类似地,如果Hive在类路径上,其配置包括“hive.metastore.uris中的元数据存储的URI,并且spark.yarn.security.credentials.hive.enabled未设置为false,则将获得Hive令牌。

如果应用程序需要与其他安全HDFS集群交互,则在启动时必须显式请求访问这些集群所需的令牌。这是通过将它们列在spark.yarn.access.namenodes属性中来实现的。

spark.yarn.access.namenodes hdfs://ireland.example.org:8020/,hdfs://frankfurt.example.org:8020/
Spark通过Java服务机制(参见java.util.ServiceLoader)支持与其他安全感知服务集成。 为此,Spark应该可以通过在jar的META-INF / services目录中的相应文件中列出org.apache.spark.deploy.yarn.security.ServiceCredentialProvider的实现,以便可以访问该实现。 可以通过将spark.yarn.security.tokens.{service} .enabled设置为false来禁用这些插件,其中{service}是凭据提供程序的名称。

配置外部Shuffle服务

要在YARN集群中的每个NodeManager上启动Spark Shuffle服务,请按照以下说明操作:

1、使用YARN配置文件构建Spark。 如果使用预打包分发,请跳过此步骤。
2、找到spark- <version> -yarn-shuffle.jar。 如果你自己构建Spark,该jar应该在$ SPARK_HOME / common / network-yarn / target / scala- <version>下;如果你正在使用发行包,它应该在yarn下。
3、将此jar添加到集群中所有NodeManager的类路径。
4、在每个节点上的yarn-site.xml中,将spark_shuffle添加到yarn.nodemanager.aux-services,然后将yarn.nodemanager.aux-services.spark_shuffle.class设置为org.apache.spark.network.yarn.YarnShuffleService。
5、通过在etc / hadoop / yarn-env.sh中设置YARN_HEAPSIZE(默认为1000)来增加NodeManager的堆大小,以避免shuffle过程中的垃圾回收问题。
6、重新启动集群中的所有NodeManager。

当在YARN上运行shuffle服务时,以下额外配置选项可用:

Property NameDefaultMeaningspark.yarn.shuffle.stopOnFailurefalseWhether to stop the NodeManager when there's a failure in the Spark Shuffle Service's initialization. This prevents application failures caused by running containers on NodeManagers where the Spark Shuffle Service is not running.

Launching your application with Apache Oozie

Apache Oozie可以作为工作流的一部分启动Spark应用程序。在安全集群中,启动的应用程序将需要相关的令牌来访问集群的服务。如果Spark使用keytab启动,这(获取相关令牌)是自动的。但是,如果Spark将在没有keytab的情况下启动,则设置安全性的责任必须移交给Oozie。

有关配置Oozie以获取安全集群和获取作业凭据的详细信息,请参阅Oozie网站上特定版本文档的“身份验证”部分。

对于Spark应用程序,必须设置Oozie工作流以使Oozie请求应用程序需要的所有令牌,包括:

1、YARN资源管理器。
2、本地HDFS文件系统。
3、任何用作I / O的源或目标的远程HDFS文件系统。
4、Hive -如使用。
5、HBase - 如果使用。
6、YARN时间轴服务器,如果应用程序与此交互。
为了避免Spark尝试获取Hive,HBase和远程HDFS令牌的过程中失败,必须将Spark配置设置为禁用这些服务的令牌收集(不收集这些服务的令牌)。
Spark配置必须包含以下行:

spark.yarn.security.tokens.hive.enabled   false
spark.yarn.security.tokens.hbase.enabled  false
必须取消设置配置选项spark.yarn.access.namenodes。

Troubleshooting Kerberos

调试Hadoop / Kerberos问题可能是“困难的”。 一个有用的技术是通过设置HADOOP_JAAS_DEBUG环境变量在Hadoop中启用对Kerberos操作的额外记录。

export HADOOP_JAAS_DEBUG=true
JDK类可以配置为通过系统属性sun.security.krb5.debug和sun.security.spnego.debug = true启用对Kerberos和SPNEGO / REST认证的额外日志记录。

-Dsun.security.krb5.debug=true -Dsun.security.spnego.debug=true
所有这些选项都可以在Application Master中启用:

spark.yarn.appMasterEnv.HADOOP_JAAS_DEBUG true
spark.yarn.am.extraJavaOptions -Dsun.security.krb5.debug=true -Dsun.security.spnego.debug=true
最后,如果org.apache.spark.deploy.yarn.Client的日志级别设置为DEBUG,日志将包含获得的所有令牌的列表,以及关于它们到期的详细信息。

























































































































































  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值