spark官方文檔之——Spark programming guide spark編程指南

(相關代碼爲scala版本,其他java和python版自行查閱)

概述

每個spark應用由一個driver program組成,driver program運行用戶main函數並在集羣上執行多種並行操作。spark主要的抽象概念是彈性分佈式數據集(RDD),它是分區在集羣節點上的數據集合,可在其上做並行操作。RDDs可以從一個hadoop文件系統(或者其他任何hadoop支持的文件系統)上的文件或者driver program上存在的scala集合轉換後的結果創建而來。用戶也可以把RDD持久化persist到內存,讓它在並行操作中被有效的重複使用。最後,RDDs會自動恢復失敗的節點(容錯)。
spark的第二個抽象概念是在並行操作中使用的shared variables共享變量。spark默認把函數作爲並行的任務集在不同節點上運行,它傳遞每個函數中使用的變量的拷貝到每個任務。有時,一個變量需要在任務中共享,或者在任務和driver program間共享。spark支持兩種類型的共享變量:廣播變量(緩存到所有節點內存上使用);累加器accumulators(只是用來累加,例如counters和sums)。
文檔展示了spark支持的每種語言的特性。很容易從spark的交互式shell開始,bin/spark-shell(scala shell)或者bin/pyspark(python)。

Linking with Spark

scala(java和python版自行查閱):
spark 1.4.0使用scala 2.10。爲了能夠用scala寫應用,你需要使用一個兼容的scala版本(例如2.10.X)。
爲了寫spark應用,你需要在spark上添加Maven依賴。spark is available through Maven Central at:
groupId = org.apache.spark
artifactId = spark-core_2.10
version = 1.4.0
另外,如果你希望可以使用HDFS集羣,你需要在hadoop-client上爲你HDFS版本增加一個依賴。一些常用的HDFS版本標籤在third party distributions頁列出。
groupId = org.apache.hadoop
artifactId = hadoop-client
version = <your-hdfs-version>
最後,你需要在你的程序中導入一些spark類。如下:
import org.apache.spark.SparkContext
import org.apache.spark.SparkConf
(在spark1.3.0之前,你需要導入import org.apache.spark.SparkContext._來使必要的隱式轉換起作用)

初始化spark

scala(java和python版自行查閱):
spark程序第一件必須做的是創建一個SparkContext對象,它告訴spark怎樣訪問集羣。首先需要創建一個包含你應用信息的SparkConf對象。
每個JVM中只有一個SparkContext對象是活躍的。你在創建一個新的之前必須stop()活躍的SparkContext。
val conf = new SparkConf().setAppName(appName).setMaster(master)
new SparkContext(conf)
appName參數是你應用的名稱,顯示在集羣UI上。master是一個spark,Mesos或YARN集羣的URL,或者“local”字符串表示運行在本地模式。事實上,在一個集羣上運行的時候,你並不想在程序中硬編碼(寫死?)master,而是用spark-submit啓動並接收應用程序。但是,爲了本地測試和單元測試,你可以用“local”在進程中運行spark。

使用shell

scala(python版自行查閱):
spark shell中,SparkContext已經爲你創建好了,就是變量sc。創建你自己的SparkContext將不會工作。你可以用--master參數設置SparkContext連接哪個主機,並且你可以使用--jars參數添加JARs包(逗號分隔的列表)。你也可以使用--packages參數添加依賴(逗號分隔的列表)到你的shell會話(例如spark包)。任何用到的依賴(例如SonaType)都可以傳遞到--repositories參數。例如,四核運行bin/spark-shell:

$ ./bin/spark-shell --master local[4]//master參數,local表示本地模式,4表示四核
或者添加code.jar到路徑中:
$ ./bin/spark-shell --master local[4] --jars code.jar//使用到的jar包
使用maven coordinates include一個依賴:
$ ./bin/spark-shell --master local[4] --packages "org.example:example:0.1"
運行spark-shell --help得到完整的操作列表。更多參考spark-submitscript(http://spark.apache.org/docs/latest/submitting-applications.html)

彈性分佈式數據集(RDDs)

spark圍繞着RDD的概念,它是一個可容錯的數據集,在它之上可以進行並行操作。兩種方法創建RDDs:parallelizing一個driver program中存在的集合,或者指向一個外部存儲系統中的數據集(例如共享的文件系統,HDFS,HBase,或任何提供一個hadoop InputFormat的數據源)。

並行集合

scala(java和python版自行查閱):
並行集合由在driver program上已存在的集合上調用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一般會根據你的集羣自動設置分區數。但是,你也可以用parallelize的第二個參數(例如 sc.parallelize(data,10))自己設置。注意:代碼中有些地方使用term slices(分區的同義詞)來保持向後兼容性。

外部數據集

scala(java和python版自行查閱):
spark可以從任何hadoop支持的存儲源創建分佈式數據集,包括你的本地文件系統,HDFS,Cassandra,HBase,Amazon S3等。spark支持文本文件,序列化文件和任何其他hadoop InputFormat。
使用SparkContext的textFile方法創建文本文件RDDs。此方法用到文件的URL(本地路徑或hdfs;//,s3n://等URL),並讀取這些文件爲文件行的集合。這裏是一個例子:
scala> val distFile = sc.textFile("data.txt")
distFile: RDD[String] = MappedRDD@1d4cee08
一經創建,distFile上可進行數據集操作。例如,我們可以像下面那樣使用map和reduce操作累加所有行的大小:distFile.map(s => s.length).reduce((a,b) => a + b)。
spark在讀取文件時的一些注意點:
*如果在本地文件系統上使用一個路徑,在worker節點上此文件必須有相同的路徑。要麼拷貝此文件到所有workers節點,要麼使用網絡共享文件系統。

*spark所有基於文件的輸入方法,包括textFile,支持在文件夾,壓縮文件,通配符上運行(文件參數可通配符表示,或爲壓縮文件,或爲文件夾)。例如,textFile(“/my/directory”)//文件夾,textFile("/my/directory/*.txt")//通配符,textFile("/my/directory/*.gz")//壓縮文件及通配符。

*textFile方法的第二個可選參數用來控制文件的分區數。默認spark爲文件的每個塊創建一個分區(HDFS默認塊爲64M),但你可以傳遞一個更大的值指定分區大小。注意分區數不能少於塊的數目。

除了文本文件,spark的scala API也支持一些其他的數據形式:
*SparkContext.wholeTextFiles可讀取文件下多個小的文本文件,每個文件以(filename,content)鍵值對形式返回。相比之下,textFile使文件中的每行返回一個記錄。

*對於序列化文件,使用SparkContext的sequenceFile[K,V]方法,K V和文件裏key value的類型相同。且應該是hadoop的Writable接口的子類,像IntWritable和Text一樣。另外,spark允許爲一些常見Writables指定native類型;例如,sequenceFile[Int,String]會自動讀取IntWritables和Texts。

*對於其他hadoop的InputFormats,可以使用SparkContext.hadoopRDD方法,此方法需JobConf和輸入格式類(key類和value類)。像hadoop job那樣設置你的輸入源。基於新的MapReduce API(org.apache.hadoop.mapreduce)的輸入格式,可以使用SparkContext.newAPIHadoopRDD。

*RDD.saveAsObjectFile和SparkContext.objectFile支持以一個組成序列化java objects的簡單格式保存RDD。儘管不是像Avro那樣有效的序列化格式,但它提供一個簡單的方法去保存任何RDD。

RDD操作

RDDs支持兩種類型的操作:transformations(從一個存在的數據集創建一個新的);actions(數據集上運行一個計算後返回到driver program的一個值)。例如,map轉換通過一個函數轉換每個數據集元素,返回一個代表結果的新的RDD。另一方面,reduce是使用相同的函數聚合RDD的所有的元素的一個action,並且返回給driver program最終的結果(儘管也有一個並行的返回一個分佈式數據集的方法reduceByKey)。
spark中的轉換爲惰性的,不會立刻計算他們的結果。相反,它會記住這些應用於一些基本數據集(例如一個文件)上的轉換。這些轉換只有在一個action需要返回結果給driver program的時候被計算。這個設計使spark更有效的運行—例如,通過map創建的一個數據集可以被reduce使用,並只返回reduce的結果給driver,而不是mapped更大的數據集。
默認的,每次運行一個action時,每個RDD轉換會被重複計算。但是,也可以使用persist(或cache)方法持久化一個RDD到內存裏,這樣spark會保持元素在集羣上,提供更快的訪問在下次你查詢它時。也支持在磁盤上持久化RDDs,或者跨多節點複製。

basics

scala:
如下簡單的程序闡明RDD基礎:
val lines = sc.textFile("data.txt")
val lineLengths = lines.map(s => s.length)
val totalLength = lineLengths.reduce((a, b) => a + b)
第一行從一個外部文件定義一個RDD。這個數據集沒有加載到內存:lines只是一個指向文件的指針。第二行定義lineLengths作爲一個map轉換的結果。由於惰性轉換機制,lineLengths不會被立刻計算。最後,運行reduce,它是一個action。在這個時間點(action觸發)spark把計算分到很多任務中, 在不同機器上運行,每個機器運行它那一部分map和本地的reduction,只返回它的結果給driver program。
如果我們想隨後再次使用lineLengths,我們可以加入語句:
lineLengths.persist()
在reduce之前,可使lineLengths一經計算出立刻存到內存中。

向spark傳遞函數

scala:
spark的API很大程度上依賴於在driver program裏傳遞函數來運行在集羣上。兩種推薦的方法:
*Anonymous function syntax 匿名函數語法(http://docs.scala-lang.org/tutorials/tour/anonymous-function-syntax.html),被用於短的片段代碼。
*全局單例對象裏的靜態方法。例如,你可以定義object MyFuntions,然後傳遞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) }
}
這裏,如果你new了一個MyClass,並調用它的doStuff,方法內的map引用MyClass類實例的func1方法,所以整個object需要被髮送到集羣上。類似於這樣寫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)是等價的。爲了避免這個問題,最簡單的方法是拷貝字段到一個局部變量而不是外部訪問它。
def doStuff(rdd: RDD[String]): RDD[String] = {
  val field_ = this.field
  rdd.map(x => field_ + x)
}

理解集羣

理解變量及方法的作用域和生命週期當在集羣執行代碼時是spark一個難點。作用域外修改變量的RDD操作是導致困惑的來源。在下面的例子中我們會看到使用foreach()增加計數的代碼,但是其他別的操作也可能會發生類似的問題。

例子

根據執行是否發生在相同的JVM,下面naive RDD element sum 表現行爲完全不同。一個常見的例子是local模式(--master = local[n])下運行spark(相對於集羣上部署應用(例如通過spark-submit部署到YARN))時:

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

// 錯誤: 不要這樣做!!(不同運行模式下表現行爲不同,local模式在一個JVM中,集羣模式卻不是)
rdd.foreach(x => counter += x)

println("Counter value: " + counter)

本地VS集羣模式

上面代碼的行爲是不明確的。本地模式下有一個JVM,上面代碼會在RDD內累加values並存儲在變量counter中。這是因爲RDD和變量counter都在driver節點相同的內存空間中。
但是,在集羣模式下,行爲就更復雜,上面的代碼可能不會像預期的那樣工作。爲了執行工作,spark會把RDD操作的過程變成多個任務—每個在一個executor上執行。在執行之前,spark計算閉包。爲了executor在RDD上執行計算,這些變量和方法必須是可見的,這些變量和方法就是閉包。閉包被序列化併發送到每個executor。在本地模式下,只有一個executors,所以全部都共享相同的閉包。但在其他模式下,情況就不是這樣的了,executors運行在不同的worker節點上,每個節點有它們自己的閉包拷貝。
送到每個executor的閉包裏的變量是備份,因此在foreach函數裏引用counter時,就不再是driver節點上的counter了。在driver節點內存裏仍然有一個counter,但是對於executors是不可見的!executors只能看到序列化閉包的拷貝。因此,counter最後的值仍然會是0,因爲在counter上所有的操作都指向序列化的閉包。
爲了確保場景中明確定義的行爲,應該使用一個accumulator累加器。當執行被分散到集羣worker節點上時,spark中的accumulators被用來提供一種安全更新變量的機制。本指南的accumulators部分更詳細討論了這些。
總之,閉包—像loops或本地定義方法的結構,不應該用來改變一些全局狀態。spark不會定義或保證引用自閉包外的對象的變化行爲。這樣做的一些代碼可能會在本地模式下起作用,但那是偶然的並且這樣的代碼分佈式模式下並不會表現出預期的行爲。如果一些全局的聚合需要,使用accumulator。

打印RDD的元素

另一個常見的操作是試圖使用rdd.foreach(println)或rdd.map(println)打印出RDD的元素。在一個單獨的機器上,會生成期望的輸出並打印出RDD所有的元素。但是,在集羣模式下,executors會調用輸出到stdout,這樣返回會輸出到executors的stdout,而不是driver上的,所以driver上的stdout不會顯示那些元素。爲了在driver上打印所有的元素,可以使用collect()方法首先把RDD收集到driver節點上,接着rdd.collect().foreach(println)。即使這樣會導致driver內存溢出,因爲collect()獲取整個RDD到一個單獨的機器上;如果你只是需要打印RDD的少許元素,一個更安全的方法是使用take():
rdd.take(100).foreach(println)。

使用鍵值對

scala:
儘管大多數spark操作可工作在包含任何類型對象的RDDs上,仍然有一些特定的操作只適用於鍵值對形式的RDDs。最常見的是分佈式“shuffle”操作,例如根據key分類或聚合元素。
在scala中,在包含Tuple2(http://www.scala-lang.org/api/2.10.4/index.html#scala.Tuple2)對象(語言內置元組,通過簡單的寫(a,b)創建)的RDDs上這些操作會自動使用。在類PairRDDFunctions(http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.rdd.PairRDDFunctions)中鍵值對操作是可用的。
例如,如下代碼在鍵值對上使用reduceByKey操作去計算文件中每行文本出現的次數。
val lines = sc.textFile("data.txt")
val pairs = lines.map(s => (s, 1))
val counts = pairs.reduceByKey((a, b) => a + b)
例如,我們可以使用counts.sortByKey()按照字母順序排序鍵值對,並且可以最後counts.collect()送回driver program作爲一個對象數組。
注意:當在鍵值對操作中使用自定義對象作爲key時,你必須確保自定義equals()方法伴隨着一個匹配的hashCode()方法。更多細節,Object.hashCode() documentation(http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode())

轉換

下面表格列出了spark支持的常用轉換。詳細參考RDD API(scala(http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.rdd.RDD),java(http://spark.apache.org/docs/latest/api/java/index.html?org/apache/spark/api/java/JavaRDD.html),python(http://spark.apache.org/docs/latest/api/python/pyspark.html#pyspark.RDD),R(http://spark.apache.org/docs/latest/api/R/index.html))文檔和pair RDD(scala(http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.rdd.PairRDDFunctions),java(http://spark.apache.org/docs/latest/api/java/index.html?org/apache/spark/api/java/JavaPairRDD.html))方法文檔。

轉換                                            含義
map(func)                                     func轉換每個元素形成的一個新的分佈式數據集

filter(func)                                  過濾出func返回true的元素形成的新的數據集

flatMap(func)                                 類似於map,但是每個輸入元素可被映射爲0或多個輸出元素(所以func返回一個序列而不是單個的元素)

mapPartitions(func)                           類似於map,但是RDD的每個分區(塊)分別運行,所以當在類型T的RDD上運行時,func必須爲類型Iterator<T> =>                                                       Iterator<U>

mapPartitionsWithIndex(func)                  類似於mapPartitions,但可提供一個整型值給func代表分區的索引,所以當在類型T的RDD上運行時,func必須爲類型                                                   (Int,Iterator<T>) =>  Iterator<U>

sample(withReplacement,fraction,seed)       可替換也可不替換,使用一個給定的隨機數生成器seed,來採樣部分數據的樣品。

union(otherDataset)                            返回一個新的RDD,爲源數據集和參數集合的並集 

distinct([numTasks])                           返回一個新的RDD,爲源數據集中不重複的元素集合

intersection(otherDataset)                     返回一個新的RDD,爲源數據集和參數數據集的交集

groupByKey([numTasks])                         當在(K,V)鍵值對數據集上調用時,返回(K,Iterable<V>)鍵值對數據集。注意:如果你爲了在每個key上執行一個聚                                                  合(例如sum或average)而分類,使用reduceByKey或aggregateByKey會更好。注意:默認的,並行輸出的水平取決於父                                                  RDD的分區數。你可以傳遞可選參數numTasks設置不同的tasks數。  

reduceByKey(func,[numTasks])                    在(K,V)鍵值對數據集上調用,返回一個新的(K,V)鍵值對數據集,它使用給定的reduce函數func對每個key的values                                                  進行聚合操作,必須是類型(V,V)=> V(例如源數據集爲(key,(value,1)),reduce函數功能爲加操作,則對源                                                  數據集每個元素的1進行加操作).就像groupByKey,reduce任務的數目是可用第二個參數配置的。  

aggregateByKey(zeroValue)(seqOp,combOp,     在(K,V)鍵值對數據集上調用,返回一個新的(K,U)鍵值對數據集,使用combine函數聚合每個key的values值和一個
[numTasks])                                     “zero”值。爲了避免不必要的分配,允許聚合的value類型不同於輸入value類型。就像groupByKey,reduce任務的數                                                  目是可用第二個參數配置的。

sortByKey([ascending],[numTasks])              在要實現K排序的(K,V)鍵值對數據集上調用,返回以keys降序或升序排序的(K,V)鍵值對數據集。ascending控制降                                                  序或升序。

join(otherDataset,[numTasks])                 在(K,V)和(K,W)類型數據集上調用,返回(K,(V,W))(相同key的聚集在一起)類型數據集。letfOuterJoin,                                                  rightOuterJoin和fullOuterJoin支持outer joins。  

cogroup(otherDataset,[numTasks])              在(K,V)和(K,W)類型數據集上調用,返回(K,(Iterable<V>,Iterable<W>))元組數據集。這種操作也可以調用                                                    groupWith。

cartesian(otherDataset)                        當在T和U類型上調用時,返回(T,U)鍵值對(所有元素的鍵值對)數據集。   

pipe(command,[envVars])                       通過shell命令連通RDD每個分區,例如,一個perl或bash腳本。RDD元素寫入到stdin,輸出到stdout的行作爲一個字符                                                  RDD返回

coalesce(numPartitions)                        減少RDD分區到numPartitions。對於一個filter處理後的大的數據集進行操作時,是很有用的。

repartition(numPartitions)                     隨機的重新洗牌RDD中的數據,去創建更多或更少的分區。會通過網絡洗牌所有數據。

repartitionAndSortWithPartitions(partitioner)  根據給定的partitioner重新分區RDD,在每個結果分區內,根據它們的keys排序記錄。因爲它能夠把排序交給shuffle                                                    machinery,這比調用repartition,並且之後在每個分區內排序更有效。                        

Actions算子

下面列出常用的一些spark支持的actions。詳細參考RDD API(scala(http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.rdd.RDD),java(http://spark.apache.org/docs/latest/api/java/index.html?org/apache/spark/api/java/JavaRDD.html),python(http://spark.apache.org/docs/latest/api/python/pyspark.html#pyspark.RDD),R(http://spark.apache.org/docs/latest/api/R/index.html))文檔和pair RDD(scala(http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.rdd.PairRDDFunctions),java(http://spark.apache.org/docs/latest/api/java/index.html?org/apache/spark/api/java/JavaPairRDD.html))方法文檔。

action                                                   含義

reduce(func)                                           使用func函數聚合數據集的元素(變兩個參數變爲一個)。爲了保證正確的並行計算,函數應該                                                                      是commutative and associative(這裏不理解)

collect()                                              作爲一個數組返回數據集所有的元素到driver program。通常在filter或其他返回數據一個充分小子集操作之                                                          後,collect是很有用的。

count()                                                返回數據集元素的數目

first()                                                返回數據集的第一個元素(類似於take(1))

take(n)                                                返回數據集前n個元素數組

takeSample(withReplacement)         

未完待續···········                            
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章