Geekhoo 關注 html
2017.09.20 13:55* 字數 2062 閱讀 13評論 0喜歡 1java
使用 Spark Shell 進行交互式分析python
基礎mysql
緩存程序員
獨立的應用es6
快速跳轉github
本教程提供瞭如何使用 Spark 的快速入門介紹。首先經過運行 Spark 交互式的 shell(在 Python 或 Scala 中)來介紹 API, 而後展現如何使用 Java , Scala 和 Python 來編寫應用程序。web
爲了繼續閱讀本指南, 首先從Spark 官網下載 Spark 的發行包。由於咱們將不使用 HDFS, 因此你能夠下載一個任何 Hadoop 版本的軟件包。
請注意, 在 Spark 2.0 以前, Spark 的主要編程接口是彈性分佈式數據集(RDD)。 在 Spark 2.0 以後, RDD 被 Dataset 替換, 它是像RDD 同樣的 strongly-typed(強類型), 可是在引擎蓋下更加優化。 RDD 接口仍然受支持, 您能夠在RDD 編程指南中得到更完整的參考。 可是, 咱們強烈建議您切換到使用 Dataset(數據集), 其性能要更優於 RDD。 請參閱SQL 編程指南獲取更多有關 Dataset 的信息。
使用 Spark Shell 進行交互式分析
基礎
Spark shell 提供了一種來學習該 API 比較簡單的方式, 以及一個強大的來分析數據交互的工具。在 Scala(運行於 Java 虛擬機之上, 並能很好的調用已存在的 Java 類庫)或者 Python 中它是可用的。經過在 Spark 目錄中運行如下的命令來啓動它:
./bin/spark-shell
Spark 的主要抽象是一個稱爲 Dataset 的分佈式的 item 集合。Datasets 能夠從 Hadoop 的 InputFormats(例如 HDFS文件)或者經過其它的 Datasets 轉換來建立。讓咱們從 Spark 源目錄中的 README 文件來建立一個新的 Dataset:
scala>valtextFile=spark.read.textFile("README.md")textFile:org.apache.spark.sql.Dataset[String]=[value:string]
您能夠直接從 Dataset 中獲取 values(值), 經過調用一些 actions(動做), 或者 transform(轉換)Dataset 以得到一個新的。更多細節, 請參閱API doc。
scala>textFile.count()// Number of items in this Datasetres0:Long=126// May be different from yours as README.md will change over time, similar to other outputsscala>textFile.first()// First item in this Datasetres1:String=#ApacheSpark
如今讓咱們 transform 這個 Dataset 以得到一個新的。咱們調用filter以返回一個新的 Dataset, 它是文件中的 items 的一個子集。
scala>vallinesWithSpark=textFile.filter(line=>line.contains("Spark"))linesWithSpark:org.apache.spark.sql.Dataset[String]=[value:string]
咱們能夠鏈式操做 transformation(轉換)和 action(動做):
scala>textFile.filter(line=>line.contains("Spark")).count()// How many lines contain "Spark"?res3:Long=15
Dataset 上的更多操做
Dataset actions(操做)和 transformations(轉換)能夠用於更復雜的計算。例如, 統計出現次數最多的單詞 :
scala>textFile.map(line=>line.split(" ").size).reduce((a,b)=>if(a>b)aelseb)res4:Long=15
第一個 map 操做建立一個新的 Dataset, 將一行數據 map 爲一個整型值。在 Dataset 上調用reduce來找到最大的行計數。參數map與reduce是 Scala 函數(closures), 而且能夠使用 Scala/Java 庫的任何語言特性。例如, 咱們能夠很容易地調用函數聲明, 咱們將定義一個 max 函數來使代碼更易於理解 :
scala>importjava.lang.Mathimportjava.lang.Mathscala>textFile.map(line=>line.split(" ").size).reduce((a,b)=>Math.max(a,b))res5:Int=15
一種常見的數據流模式是被 Hadoop 所推廣的 MapReduce。Spark 能夠很容易實現 MapReduce:
scala>valwordCounts=textFile.flatMap(line=>line.split(" ")).groupByKey(identity).count()wordCounts:org.apache.spark.sql.Dataset[(String,Long)]=[value:string,count(1):bigint]
在這裏, 咱們調用了flatMap以 transform 一個 lines 的 Dataset 爲一個 words 的 Dataset, 而後結合groupByKey和count來計算文件中每一個單詞的 counts 做爲一個 (String, Long) 的 Dataset pairs。要在 shell 中收集 word counts, 咱們能夠調用collect:
scala>wordCounts.collect()res6:Array[(String,Int)]=Array((means,1),(under,2),(this,3),(Because,1),(Python,2),(agree,1),(cluster.,1),...)
緩存
Spark 還支持 Pulling(拉取)數據集到一個羣集範圍的內存緩存中。例如當查詢一個小的 「hot」 數據集或運行一個像 PageRANK 這樣的迭代算法時, 在數據被重複訪問時是很是高效的。舉一個簡單的例子, 讓咱們標記咱們的linesWithSpark數據集到緩存中:
scala>linesWithSpark.cache()res7:linesWithSpark.type=[value:string]scala>linesWithSpark.count()res8:Long=15scala>linesWithSpark.count()res9:Long=15
使用 Spark 來探索和緩存一個 100 行的文本文件看起來比較愚蠢。有趣的是, 即便在他們跨越幾十或者幾百個節點時, 這些相同的函數也能夠用於很是大的數據集。您也能夠像編程指南. 中描述的同樣經過鏈接bin/spark-shell到集羣中, 使用交互式的方式來作這件事情。
獨立的應用
假設咱們但願使用 Spark API 來建立一個獨立的應用程序。咱們在 Scala(SBT), Java(Maven)和 Python 中練習一個簡單應用程序。
咱們將在 Scala 中建立一個很是簡單的 Spark 應用程序 - 很簡單的, 事實上, 它名爲SimpleApp.scala:
/* SimpleApp.scala */importorg.apache.spark.sql.SparkSessionobjectSimpleApp{defmain(args:Array[String]){vallogFile="YOUR_SPARK_HOME/README.md"// Should be some file on your systemvalspark=SparkSession.builder.appName("Simple Application").getOrCreate()vallogData=spark.read.textFile(logFile).cache()valnumAs=logData.filter(line=>line.contains("a")).count()valnumBs=logData.filter(line=>line.contains("b")).count()println(s"Lines with a:$numAs, Lines with b:$numBs")spark.stop()}}
注意, 這個應用程序咱們應該定義一個main()方法而不是去擴展scala.App。使用scala.App的子類可能不會正常運行。
該程序僅僅統計了 Spark README 文件中每一行包含 ‘a’ 的數量和包含 ‘b’ 的數量。注意, 您須要將 YOUR_SPARK_HOME 替換爲您 Spark 安裝的位置。不像先前使用 spark shell 操做的示例, 它們初始化了它們本身的 SparkContext, 咱們初始化了一個 SparkContext 做爲應用程序的一部分。
咱們調用SparkSession.builder以構造一個 [[SparkSession]], 而後設置 application name(應用名稱), 最終調用getOrCreate以得到 [[SparkSession]] 實例。
咱們的應用依賴了 Spark API, 因此咱們將包含一個名爲build.sbt的 sbt 配置文件, 它描述了 Spark 的依賴。該文件也會添加一個 Spark 依賴的 repository:
name:="Simple Project"version:="1.0"scalaVersion:="2.11.8"libraryDependencies+="org.apache.spark"%%"spark-sql"%"2.2.0"
爲了讓 sbt 正常的運行, 咱們須要根據經典的目錄結構來佈局SimpleApp.scala和build.sbt文件。在成功後, 咱們能夠建立一個包含應用程序代碼的 JAR 包, 而後使用spark-submit腳原本運行咱們的程序。
# Your directory layout should look like this$ find .../build.sbt./src./src/main./src/main/scala./src/main/scala/SimpleApp.scala# Package a jar containing your application$ sbt package...[info]Packaging{..}/{..}/target/scala-2.11/simple-project_2.11-1.0.jar# Use spark-submit to run your application$ YOUR_SPARK_HOME/bin/spark-submit\--class"SimpleApp"\--master local[4]\target/scala-2.11/simple-project_2.11-1.0.jar...Lines with a:46, Lines with b:23
快速跳轉
恭喜您成功的運行了您的第一個 Spark 應用程序!
更多 API 的深刻概述, 從RDD programming guide和SQL programming guide這裏開始, 或者看看 「編程指南」 菜單中的其它組件。
爲了在集羣上運行應用程序, 請前往deployment overview.
最後, 在 Spark 的examples目錄中包含了一些 (Scala,Java,Python,R) 示例。您能夠按照以下方式來運行它們:
# 針對 Scala 和 Java, 使用 run-example:./bin/run-example SparkPi# 針對 Python 示例, 直接使用 spark-submit:./bin/spark-submit examples/src/main/python/pi.py# 針對 R 示例, 直接使用 spark-submit:./bin/spark-submit examples/src/main/r/dataframe.R
原文地址: http://spark.apachecn.org/docs/cn/2.2.0/quick-start.html
網頁地址: http://spark.apachecn.org/
github: https://github.com/apachecn/spark-doc-zh(以爲不錯麻煩給個 Star,謝謝!~)
Geekhoo 關注
2017.09.21 16:23* 字數 11326 閱讀 127評論 0喜歡 5
概述
在一個較高的概念上來講,每個 Spark 應用程序由一個在集羣上運行着用戶的main函數和執行各類並行操做的driver program(驅動程序)組成。Spark 提供的主要抽象是一個彈性分佈式數據集(RDD),它是能夠執行並行操做且跨集羣節點的元素的集合。RDD 能夠從一個 Hadoop 文件系統(或者任何其它 Hadoop 支持的文件系統),或者一個在 driver program(驅動程序)中已存在的 Scala 集合,以及經過 transforming(轉換)來建立一個 RDD。用戶爲了讓它在整個並行操做中更高效的重用,也許會讓 Spark persist(持久化)一個 RDD 到內存中。最後,RDD 會自動的從節點故障中恢復。
在 Spark 中的第二個抽象是可以用於並行操做的shared variables(共享變量),默認狀況下,當 Spark 的一個函數做爲一組不一樣節點上的任務運行時,它將每個變量的副本應用到每個任務的函數中去。有時候,一個變量須要在整個任務中,或者在任務和 driver program(驅動程序)之間來共享。Spark 支持兩種類型的共享變量 :broadcast variables(廣播變量),它能夠用於在全部節點上緩存一個值,和accumulators(累加器),他是一個只能被 「added(增長)」 的變量,例如 counters 和 sums。
本指南介紹了每一種 Spark 所支持的語言的特性。若是您啓動 Spark 的交互式 shell - 針對 Scala shell 使用bin/spark-shell或者針對 Python 使用bin/pyspark是很容易來學習的。
Spark 依賴
Spark 2.2.0 默認使用 Scala 2.11 來構建和發佈直到運行。(固然,Spark 也能夠與其它的 Scala 版本一塊兒運行)。爲了使用 Scala 編寫應用程序,您須要使用可兼容的 Scala 版本(例如,2.11.X)。
要編寫一個 Spark 的應用程序,您須要在 Spark 上添加一個 Maven 依賴。Spark 能夠經過 Maven 中央倉庫獲取:
groupId = org.apache.spark
artifactId = spark-core_2.11
version = 2.2.0
此外,若是您想訪問一個 HDFS 集羣,則須要針對您的 HDFS 版本添加一個hadoop-client(hadoop 客戶端)依賴。
groupId = org.apache.hadoop
artifactId = hadoop-client
version =
最後,您須要導入一些 Spark classes(類)到您的程序中去。添加下面幾行:
importorg.apache.spark.SparkContextimportorg.apache.spark.SparkConf
(在 Spark 1.3.0 以前,您須要明確導入org.apache.spark.SparkContext._來啓用必要的的隱式轉換。)
初始化 Spark
Spark 程序必須作的第一件事情是建立一個SparkContext對象,它會告訴 Spark 如何訪問集羣。要建立一個SparkContext,首先須要構建一個包含應用程序的信息的SparkConf對象。
每個 JVM 可能只能激活一個 SparkContext 對象。在創新一個新的對象以前,必須調用stop()該方法中止活躍的 SparkContext。
valconf=newSparkConf().setAppName(appName).setMaster(master)newSparkContext(conf)
這個appName參數是一個在集羣 UI 上展現應用程序的名稱。master是一個Spark, Mesos 或 YARN 的 cluster URL,或者指定爲在 local mode(本地模式)中運行的 「local」 字符串。在實際工做中,當在集羣上運行時,您不但願在程序中將 master 給硬編碼,而是用使用spark-submit啓動應用而且接收它。然而,對於本地測試和單元測試,您能夠經過 「local」 來運行 Spark 進程。
使用 Shell
在 Spark Shell 中,一個特殊的 interpreter-aware(可用的解析器)SparkContext 已經爲您建立好了,稱之爲sc的變量。建立您本身的 SparkContext 將不起做用。您能夠使用--master參數設置這個 SparkContext 鏈接到哪個 master 上,而且您能夠經過--jars參數傳遞一個逗號分隔的列表來添加 JARs 到 classpath 中。也能夠經過--packages參數應用一個用逗號分隔的 maven coordinates(maven 座標)方式來添加依賴(例如,Spark 包)到您的 shell session 中去。任何額外存在且依賴的倉庫(例如 Sonatype)能夠傳遞到--repositories參數。例如,要明確使用四個核(CPU)來運行bin/spark-shell,使用:
$ ./bin/spark-shell --master local[4]
或者, 也能夠添加code.jar到它的 classpath 中去, 使用:
$ ./bin/spark-shell --master local[4]--jars code.jar
爲了包含一個依賴,使用 Maven 座標:
$ ./bin/spark-shell --master local[4]--packages"org.example:example:0.1"
有關選項的完整列表, 請運行spark-shell --help. 在幕後,spark-shell調用了經常使用的spark-submit腳本.
彈性分佈式數據集 (RDDs)
Spark 主要以一個彈性分佈式數據集(RDD)的概念爲中心,它是一個容錯且能夠執行並行操做的元素的集合。有兩種方法能夠建立 RDD : 在你的 driver program(驅動程序)中parallelizing一個已存在的集合,或者在外部存儲系統中引用一個數據集,例如,一個共享文件系統,HDFS,HBase,或者提供 Hadoop InputFormat 的任何數據源。
並行集合
能夠在您的 driver program (a ScalaSeq) 中已存在的集合上經過調用SparkContext的parallelize方法來建立並行集合。該集合的元素從一個能夠並行操做的 distributed dataset(分佈式數據集)中複製到另外一個 dataset(數據集)中去。例如,這裏是一個如何去建立一個保存數字 1 ~ 5 的並行集合。
valdata=Array(1,2,3,4,5)valdistData=sc.parallelize(data)
在建立後,該 distributed dataset(分佈式數據集)(distData)能夠並行的執行操做。例如,咱們能夠調用distData.reduce((a, b) => a + b) 來合計數組中的元素。後面咱們將介紹 distributed dataset(分佈式數據集)上的操做。
並行集合中一個很重要參數是partitions(分區)的數量,它可用來切割 dataset(數據集)。Spark 將在集羣中的每個分區上運行一個任務。一般您但願羣集中的每個 CPU 計算 2-4 個分區。通常狀況下,Spark 會嘗試根據您的羣集狀況來自動的設置的分區的數量。固然,您也能夠將分區數做爲第二個參數傳遞到parallelize(e.g.sc.parallelize(data, 10)) 方法中來手動的設置它。注意: 代碼中的一些地方會使用 term slices (a synonym for partitions) 以保持向後兼容.
外部 Datasets(數據集)
Spark 能夠從 Hadoop 所支持的任何存儲源中建立 distributed dataset(分佈式數據集),包括本地文件系統,HDFS,Cassandra,HBase,Amazon S3等等。 Spark 支持文本文件,SequenceFiles,以及任何其它的 HadoopInputFormat。
能夠使用SparkContext的textFile方法來建立文本文件的 RDD。此方法須要一個文件的 URI(計算機上的本地路徑 ,hdfs://,s3n://等等的 URI),而且讀取它們做爲一個 lines(行)的集合。下面是一個調用示例:
scala>valdistFile=sc.textFile("data.txt")distFile:org.apache.spark.rdd.RDD[String]=data.txtMapPartitionsRDD[10]attextFileat:26
在建立後,distFile能夠使用 dataset(數據集)的操做。例如,咱們能夠使用下面的 map 和 reduce 操做來合計全部行的數量:distFile.map(s => s.length).reduce((a, b) => a + b)。
使用 Spark 讀取文件時須要注意:
若是使用本地文件系統的路徑,所工做節點的相同訪問路徑下該文件必須能夠訪問。複製文件到全部工做節點上,或着使用共享的網絡掛載文件系統。
全部 Spark 基於文件的 input 方法, 包括textFile, 支持在目錄上運行, 壓縮文件, 和通配符. 例如, 您能夠使用textFile("/my/directory"),textFile("/my/directory/*.txt"), andtextFile("/my/directory/*.gz").
textFile方法也能夠經過第二個可選的參數來控制該文件的分區數量. 默認狀況下, Spark 爲文件的每個 block(塊)建立的一 個 partition 分區(HDFS 中塊大小默認是 128MB),固然你也能夠經過傳遞一個較大的值來要求一個較高的分區數量。請注意,分區的數量不可以小於塊的數量。
除了文本文件以外,Spark 的 Scala API 也支持一些其它的數據格式:
SparkContext.wholeTextFiles能夠讀取包含多個小文本文件的目錄, 而且將它們做爲一個 (filename, content) pairs 來返回. 這與textFile相比, 它的每個文件中的每一行將返回一個記錄. 分區由數據量來肯定, 某些狀況下, 可能致使分區太少. 針對這些狀況,wholeTextFiles在第二個位置提供了一個可選的參數用戶控制分區的最小數量.
針對SequenceFiles, 使用 SparkContext 的sequenceFile[K, V]方法,其中K和V指的是文件中 key 和 values 的類型. 這些應該是 Hadoop 的Writable接口的子類, 像IntWritableandText. 此外, Spark 可讓您爲一些常見的 Writables 指定原生類型; 例如,sequenceFile[Int, String]會自動讀取 IntWritables 和 Texts.
針對其它的 Hadoop InputFormats, 您能夠使用SparkContext.hadoopRDD方法, 它接受一個任意的JobConf和 input format class, key class 和 value class. 經過相同的方法你能夠設置你的 input source(輸入源). 你還能夠針對 InputFormats 使用基於 「new」 MapReduce API (org.apache.hadoop.mapreduce) 的SparkContext.newAPIHadoopRDD.
RDD.saveAsObjectFile和SparkContext.objectFile支持使用簡單的序列化的 Java objects 來保存 RDD. 雖然這不像 Avro 這種專用的格式同樣高效,但其提供了一種更簡單的方式來保存任何的 RDD。.
RDD 操做
RDDs support 兩種類型的操做:transformations(轉換), 它會在一個已存在的 dataset 上建立一個新的 dataset, 和actions(動做), 將在 dataset 上運行的計算後返回到 driver 程序. 例如,map是一個經過讓每一個數據集元素都執行一個函數,並返回的新 RDD 結果的 transformation,reducereduce 經過執行一些函數,聚合 RDD 中全部元素,並將最終結果給返回驅動程序(雖然也有一個並行reduceByKey返回一個分佈式數據集)的 action.
Spark 中全部的 transformations 都是lazy(懶加載的), 所以它不會馬上計算出結果. 相反, 他們只記得應用於一些基本數據集的轉換 (例如. 文件). 只有當須要返回結果給驅動程序時,transformations 纔開始計算. 這種設計使 Spark 的運行更高效. 例如, 咱們能夠了解到,map所建立的數據集將被用在reduce中,而且只有reduce的計算結果返回給驅動程序,而不是映射一個更大的數據集.
默認狀況下,每次你在 RDD 運行一個 action 的時, 每一個 transformed RDD 都會被從新計算。可是,您也可用persist(或cache) 方法將 RDD persist(持久化)到內存中;在這種狀況下,Spark 爲了下次查詢時能夠更快地訪問,會把數據保存在集羣上。此外,還支持持續持久化 RDDs 到磁盤,或複製到多個結點。
基礎
爲了說明 RDD 基礎,請思考下面這個的簡單程序:
vallines=sc.textFile("data.txt")vallineLengths=lines.map(s=>s.length)valtotalLength=lineLengths.reduce((a,b)=>a+b)
第一行從外部文件中定義了一個基本的 RDD,但這個數據集並未加載到內存中或即將被行動:line僅僅是一個相似指針的東西,指向該文件. 第二行定義了lineLengths做爲maptransformation 的結果。請注意,因爲laziness(延遲加載)lineLengths不會被當即計算. 最後,咱們運行reduce,這是一個 action。此時,Spark 分發計算任務到不一樣的機器上運行,每臺機器都運行在 map 的一部分並本地運行 reduce,僅僅返回它聚合後的結果給驅動程序.
若是咱們也但願之後再次使用lineLengths,咱們還能夠添加:
lineLengths.persist()
在reduce以前,這將致使lineLengths在第一次計算以後就被保存在 memory 中。
傳遞 Functions(函數)給 Spark
當 driver 程序在集羣上運行時,Spark 的 API 在很大程度上依賴於傳遞函數。有 2 種推薦的方式來作到這一點:
Anonymous function syntax(匿名函數語法), 它能夠用於短的代碼片段.
在全局單例對象中的靜態方法. 例如, 您能夠定義object MyFunctions而後傳遞MyFunctions.func1, 以下:
objectMyFunctions{deffunc1(s:String):String={...}}myRdd.map(MyFunctions.func1)
請注意,雖然也有可能傳遞一個類的實例(與單例對象相反)的方法的引用,這須要發送整個對象,包括類中其它方法。例如,考慮:
classMyClass{deffunc1(s:String):String={...}defdoStuff(rdd:RDD[String]):RDD[String]={rdd.map(func1)}}
這裏,若是咱們建立一個MyClass的實例,並調用doStuff,在map內有MyClass實例的func1方法的引用,因此整個對象須要被髮送到集羣的。它相似於rdd.map(x => this.func1(x))
相似的方式,訪問外部對象的字段將引用整個對象:
classMyClass{valfield="Hello"defdoStuff(rdd:RDD[String]):RDD[String]={rdd.map(x=>field+x)}}
至關於寫rdd.map(x => this.field + x), 它引用this全部的東西. 爲了不這個問題, 最簡單的方式是複製field到一個本地變量,而不是外部訪問它:
defdoStuff(rdd:RDD[String]):RDD[String]={valfield_=this.fieldrdd.map(x=>field_+x)}
理解閉包
在集羣中執行代碼時,一個關於 Spark 更難的事情是理解變量和方法的範圍和生命週期. 修改其範圍以外的變量 RDD 操做能夠混淆的常見緣由。在下面的例子中,咱們將看一下使用的foreach()代碼遞增累加計數器,但相似的問題,也可能會出現其餘操做上.
示例
考慮一個簡單的 RDD 元素求和,如下行爲可能不一樣,具體取決因而否在同一個 JVM 中執行. 一個常見的例子是當 Spark 運行在local本地模式(--master = local[n])時,與部署 Spark 應用到羣集(例如,經過 spark-submit 到 YARN):
varcounter=0varrdd=sc.parallelize(data)// Wrong: Don't do this!!rdd.foreach(x=>counter+=x)println("Counter value: "+counter)
Local(本地)vs. cluster(集羣)模式
上面的代碼行爲是不肯定的,而且可能沒法按預期正常工做。執行做業時,Spark 會分解 RDD 操做到每一個 executor 中的 task 裏。在執行以前,Spark 計算任務的closure(閉包)。而閉包是在 RDD 上的 executor 必須可以訪問的變量和方法(在此狀況下的foreach())。閉包被序列化並被髮送到每一個執行器。
閉包的變量副本發給每一個counter,當counter被foreach函數引用的時候,它已經再也不是 driver node 的counter了。雖然在 driver node 仍然有一個 counter 在內存中,可是對 executors 已經不可見。executor 看到的只是序列化的閉包一個副本。因此counter最終的值仍是 0,由於對counter全部的操做均引用序列化的 closure 內的值。
在local本地模式,在某些狀況下的foreach功能其實是同一 JVM 上的驅動程序中執行,並會引用同一個原始的counter計數器,實際上可能更新.
爲了確保這些類型的場景明確的行爲應該使用的Accumulator累加器。當一個執行的任務分配到集羣中的各個 worker 結點時,Spark 的累加器是專門提供安全更新變量的機制。本指南的累加器的部分會更詳細地討論這些。
在通常狀況下,closures - constructs 像循環或本地定義的方法,不該該被用於改動一些全局狀態。Spark 沒有規定或保證突變的行爲,以從封閉件的外側引用的對象。一些代碼,這可能以本地模式運行,可是這只是偶然和這樣的代碼如預期在分佈式模式下不會表現。若是須要一些全局的聚合功能,應使用 Accumulator(累加器)。
打印 RDD 的 elements
另外一種常見的語法用於打印 RDD 的全部元素使用rdd.foreach(println)或rdd.map(println)。在一臺機器上,這將產生預期的輸出和打印 RDD 的全部元素。然而,在集羣cluster模式下,stdout輸出正在被執行寫操做 executors 的stdout代替,而不是在一個驅動程序上,所以stdout的driver程序不會顯示這些!要打印driver程序的全部元素,能夠使用的collect()方法首先把 RDD 放到 driver 程序節點上:rdd.collect().foreach(println)。這可能會致使 driver 程序耗盡內存,雖然說,由於collect()獲取整個 RDD 到一臺機器; 若是你只須要打印 RDD 的幾個元素,一個更安全的方法是使用take():rdd.take(100).foreach(println)。
與 Key-Value Pairs 一塊兒使用
雖然大多數 Spark 操做工做在包含任何類型對象的 RDDs 上,只有少數特殊的操做可用於 Key-Value 對的 RDDs. 最多見的是分佈式 「shuffle」 操做,如經過元素的 key 來進行 grouping 或 aggregating 操做.
在 Scala 中,這些操做在 RDD 上是自動可用,它包含了Tuple2objects (the built-in tuples in the language, created by simply writing(a, b)). 在PairRDDFunctionsclass 中該 key-value pair 操做是可用的, 其中圍繞 tuple 的 RDD 進行自動封裝.
例如,下面的代碼使用的Key-Value對的reduceByKey操做統計文本文件中每一行出現了多少次:
vallines=sc.textFile("data.txt")valpairs=lines.map(s=>(s,1))valcounts=pairs.reduceByKey((a,b)=>a+b)
咱們也能夠使用counts.sortByKey(),例如,在對按字母順序排序,最後counts.collect()把他們做爲一個數據對象返回給 driver 程序。
Note(注意):當在 key-value pair 操做中使用自定義的 objects 做爲 key 時, 您必須確保有一個自定義的equals()方法有一個hashCode()方法相匹配. 有關詳情, 請參閱Object.hashCode() documentation中列出的約定.
Transformations(轉換)
下表列出了一些 Spark 經常使用的 transformations(轉換). 詳情請參考 RDD API 文檔 (Scala,Java,Python,R) 和 pair RDD 函數文檔 (Scala,Java).
Transformation(轉換)Meaning(含義)
map(func)返回一個新的 distributed dataset(分佈式數據集),它由每一個 source(數據源)中的元素應用一個函數func來生成.
filter(func)返回一個新的 distributed dataset(分佈式數據集),它由每一個 source(數據源)中應用一個函數func且返回值爲 true 的元素來生成.
flatMap(func)與 map 相似,可是每個輸入的 item 能夠被映射成 0 個或多個輸出的 items(因此func應該返回一個 Seq 而不是一個單獨的 item).
mapPartitions(func)與 map 相似,可是單獨的運行在在每一個 RDD 的 partition(分區,block)上,因此在一個類型爲 T 的 RDD 上運行時func必須是 Iterator => Iterator 類型.
mapPartitionsWithIndex(func)與 mapPartitions 相似,可是也須要提供一個表明 partition 的 index(索引)的 interger value(整型值)做爲參數的func,因此在一個類型爲 T 的 RDD 上運行時func必須是 (Int, Iterator) => Iterator 類型.
sample(withReplacement,fraction,seed)樣本數據,設置是否放回(withReplacement), 採樣的百分比(fraction)、使用指定的隨機數生成器的種子(seed).
union(otherDataset)反回一個新的 dataset,它包含了 source dataset(源數據集)和 otherDataset(其它數據集)的並集.
intersection(otherDataset)返回一個新的 RDD,它包含了 source dataset(源數據集)和 otherDataset(其它數據集)的交集.
distinct([numTasks]))返回一個新的 dataset,它包含了 source dataset(源數據集)中去重的元素.
groupByKey([numTasks])在一個 (K, V) pair 的 dataset 上調用時,返回一個 (K, Iterable) .
Note:若是分組是爲了在每個 key 上執行聚合操做(例如,sum 或 average),此時使用reduceByKey或aggregateByKey來計算性能會更好.
Note:默認狀況下,並行度取決於父 RDD 的分區數。能夠傳遞一個可選的numTasks參數來設置不一樣的任務數.
reduceByKey(func, [numTasks])在 (K, V) pairs 的 dataset 上調用時, 返回 dataset of (K, V) pairs 的 dataset, 其中的 values 是針對每一個 key 使用給定的函數func來進行聚合的, 它必須是 type (V,V) => V 的類型. 像groupByKey同樣, reduce tasks 的數量是能夠經過第二個可選的參數來配置的.
aggregateByKey(zeroValue)(seqOp,combOp, [numTasks])在 (K, V) pairs 的 dataset 上調用時, 返回 (K, U) pairs 的 dataset,其中的 values 是針對每一個 key 使用給定的 combine 函數以及一個 neutral "0" 值來進行聚合的. 容許聚合值的類型與輸入值的類型不同, 同時避免沒必要要的配置. 像groupByKey同樣, reduce tasks 的數量是能夠經過第二個可選的參數來配置的.
sortByKey([ascending], [numTasks])在一個 (K, V) pair 的 dataset 上調用時,其中的 K 實現了 Ordered,返回一個按 keys 升序或降序的 (K, V) pairs 的 dataset, 由 boolean 類型的ascending參數來指定.
join(otherDataset, [numTasks])在一個 (K, V) 和 (K, W) 類型的 dataset 上調用時,返回一個 (K, (V, W)) pairs 的 dataset,它擁有每一個 key 中全部的元素對。Outer joins 能夠經過leftOuterJoin,rightOuterJoin和fullOuterJoin來實現.
cogroup(otherDataset, [numTasks])在一個 (K, V) 和的 dataset 上調用時,返回一個 (K, (Iterable, Iterable)) tuples 的 dataset. 這個操做也調用了groupWith.
cartesian(otherDataset)在一個 T 和 U 類型的 dataset 上調用時,返回一個 (T, U) pairs 類型的 dataset(全部元素的 pairs,即笛卡爾積).
pipe(command,[envVars])經過使用 shell 命令來將每一個 RDD 的分區給 Pipe。例如,一個 Perl 或 bash 腳本。RDD 的元素會被寫入進程的標準輸入(stdin),而且 lines(行)輸出到它的標準輸出(stdout)被做爲一個字符串型 RDD 的 string 返回.
coalesce(numPartitions)Decrease(下降)RDD 中 partitions(分區)的數量爲 numPartitions。對於執行過濾後一個大的 dataset 操做是更有效的.
repartition(numPartitions)Reshuffle(從新洗牌)RDD 中的數據以建立或者更多的 partitions(分區)並將每一個分區中的數據儘可能保持均勻. 該操做老是經過網絡來 shuffles 全部的數據.
repartitionAndSortWithinPartitions(partitioner)根據給定的 partitioner(分區器)對 RDD 進行從新分區,並在每一個結果分區中,按照 key 值對記錄排序。這比每個分區中先調用repartition而後再 sorting(排序)效率更高,由於它能夠將排序過程推送到 shuffle 操做的機器上進行.
Actions(動做)
下表列出了一些 Spark 經常使用的 actions 操做。詳細請參考 RDD API 文檔 (Scala,Java,Python,R)
Action(動做)Meaning(含義)
reduce(func)使用函數func聚合 dataset 中的元素,這個函數func輸入爲兩個元素,返回爲一個元素。這個函數應該是可交換(commutative )和關聯(associative)的,這樣才能保證它能夠被並行地正確計算.
collect()在 driver 程序中,以一個 array 數組的形式返回 dataset 的全部元素。這在過濾器(filter)或其餘操做(other operation)以後返回足夠小(sufficiently small)的數據子集一般是有用的.
count()返回 dataset 中元素的個數.
first()返回 dataset 中的第一個元素(相似於 take(1).
take(n)將數據集中的前n個元素做爲一個 array 數組返回.
takeSample(withReplacement,num, [seed])對一個 dataset 進行隨機抽樣,返回一個包含num個隨機抽樣(random sample)元素的數組,參數 withReplacement 指定是否有放回抽樣,參數 seed 指定生成隨機數的種子.
takeOrdered(n,[ordering])返回 RDD 按天然順序(natural order)或自定義比較器(custom comparator)排序後的前n個元素.
saveAsTextFile(path)將 dataset 中的元素以文本文件(或文本文件集合)的形式寫入本地文件系統、HDFS 或其它 Hadoop 支持的文件系統中的給定目錄中。Spark 將對每一個元素調用 toString 方法,將數據元素轉換爲文本文件中的一行記錄.
saveAsSequenceFile(path)
(Java and Scala)將 dataset 中的元素以 Hadoop SequenceFile 的形式寫入到本地文件系統、HDFS 或其它 Hadoop 支持的文件系統指定的路徑中。該操做能夠在實現了 Hadoop 的 Writable 接口的鍵值對(key-value pairs)的 RDD 上使用。在 Scala 中,它還能夠隱式轉換爲 Writable 的類型(Spark 包括了基本類型的轉換,例如 Int, Double, String 等等).
saveAsObjectFile(path)
(Java and Scala)使用 Java 序列化(serialization)以簡單的格式(simple format)編寫數據集的元素,而後使用SparkContext.objectFile()進行加載.
countByKey()僅適用於(K,V)類型的 RDD 。返回具備每一個 key 的計數的 (K , Int)pairs 的 hashmap.
foreach(func)對 dataset 中每一個元素運行函數func。這一般用於反作用(side effects),例如更新一個Accumulator(累加器)或與外部存儲系統(external storage systems)進行交互。Note:修改除foreach()以外的累加器之外的變量(variables)可能會致使未定義的行爲(undefined behavior)。詳細介紹請閱讀Understanding closures(理解閉包)部分.
該 Spark RDD API 還暴露了一些 actions(操做)的異步版本,例如針對foreach的foreachAsync,它們會當即返回一個FutureAction到調用者,而不是在完成 action 時阻塞。 這能夠用於管理或等待 action 的異步執行。.
Shuffle 操做
Spark 裏的某些操做會觸發 shuffle。shuffle 是spark 從新分配數據的一種機制,使得這些數據能夠跨不一樣的區域進行分組。這一般涉及在 executors 和 機器之間拷貝數據,這使得 shuffle 成爲一個複雜的、代價高的操做。
Background(幕後)
爲了明白reduceByKey操做的過程,咱們以reduceByKey爲例。reduceBykey 操做產生一個新的 RDD,其中 key 全部相同的的值組合成爲一個 tuple - key 以及與 key 相關聯的全部值在 reduce 函數上的執行結果。面臨的挑戰是,一個 key 的全部值不必定都在一個同一個 paritition 分區裏,甚至是不必定在同一臺機器裏,可是它們必須共同被計算。
在 spark 裏,特定的操做須要數據不跨分區分佈。在計算期間,一個任務在一個分區上執行,爲了全部數據都在單個reduceByKey的 reduce 任務上運行,咱們須要執行一個 all-to-all 操做。它必須從全部分區讀取全部的 key 和 key對應的全部的值,而且跨分區彙集去計算每一個 key 的結果 - 這個過程就叫作shuffle.。
Although the set of elements in each partition of newly shuffled data will be deterministic, and so is the ordering of partitions themselves, the ordering of these elements is not. If one desires predictably ordered data following shuffle then it’s possible to use:
儘管每一個分區新 shuffle 的數據集將是肯定的,分區自己的順序也是這樣,可是這些數據的順序是不肯定的。若是但願 shuffle 後的數據是有序的,能夠使用:
mapPartitions對每一個 partition 分區進行排序,例如,.sorted
repartitionAndSortWithinPartitions在分區的同時對分區進行高效的排序.
sortBy對 RDD 進行全局的排序
觸發的 shuffle 操做包括repartition操做,如repartition和coalesce,‘ByKey操做 (除了 counting 以外) 像groupByKey和reduceByKey, 和join操做, 像cogroup和join.
性能影響
該Shuffle是一個代價比較高的操做,它涉及磁盤 I/O、數據序列化、網絡 I/O。爲了準備 shuffle 操做的數據,Spark 啓動了一系列的任務,map任務組織數據,reduce完成數據的聚合。這些術語來自 MapReduce,跟 Spark 的map操做和reduce操做沒有關係。
在內部,一個 map 任務的全部結果數據會保存在內存,直到內存不能所有存儲爲止。而後,這些數據將基於目標分區進行排序並寫入一個單獨的文件中。在 reduce 時,任務將讀取相關的已排序的數據塊。
某些 shuffle 操做會大量消耗堆內存空間,由於 shuffle 操做在數據轉換先後,須要在使用內存中的數據結構對數據進行組織。須要特別說明的是,reduceByKey和aggregateByKey在 map 時會建立這些數據結構,'ByKey操做在 reduce 時建立這些數據結構。當內存滿的時候,Spark 會把溢出的數據存到磁盤上,這將致使額外的磁盤 I/O 開銷和垃圾回收開銷的增長。
shuffle 操做還會在磁盤上生成大量的中間文件。在 Spark 1.3 中,這些文件將會保留至對應的 RDD 不在使用並被垃圾回收爲止。這麼作的好處是,若是在 Spark 從新計算 RDD 的血統關係(lineage)時,shuffle 操做產生的這些中間文件不須要從新建立。若是 Spark 應用長期保持對 RDD 的引用,或者垃圾回收不頻繁,這將致使垃圾回收的週期比較長。這意味着,長期運行 Spark 任務可能會消耗大量的磁盤空間。臨時數據存儲路徑能夠經過 SparkContext 中設置參數spark.local.dir進行配置。
shuffle 操做的行爲能夠經過調節多個參數進行設置。詳細的說明請看Spark 配置指南中的 「Shuffle 行爲」 部分。
RDD Persistence(持久化)
Spark 中一個很重要的能力是將數據persisting持久化(或稱爲caching緩存),在多個操做間均可以訪問這些持久化的數據。當持久化一個 RDD 時,每一個節點的其它分區均可以使用 RDD 在內存中進行計算,在該數據上的其餘 action 操做將直接使用內存中的數據。這樣會讓之後的 action 操做計算速度加快(一般運行速度會加速 10 倍)。緩存是迭代算法和快速的交互式使用的重要工具。
RDD 能夠使用persist()方法或cache()方法進行持久化。數據將會在第一次 action 操做時進行計算,並緩存在節點的內存中。Spark 的緩存具備容錯機制,若是一個緩存的 RDD 的某個分區丟失了,Spark 將按照原來的計算過程,自動從新計算並進行緩存。
另外,每一個持久化的 RDD 能夠使用不一樣的storage level存儲級別進行緩存,例如,持久化到磁盤、已序列化的 Java 對象形式持久化到內存(能夠節省空間)、跨節點間複製、以 off-heap 的方式存儲在 Tachyon。這些存儲級別經過傳遞一個StorageLevel對象 (Scala,Java,Python) 給persist()方法進行設置。cache()方法是使用默認存儲級別的快捷設置方法,默認的存儲級別是StorageLevel.MEMORY_ONLY(將反序列化的對象存儲到內存中)。詳細的存儲級別介紹以下:
Storage Level(存儲級別)Meaning(含義)
MEMORY_ONLY將 RDD 以反序列化的 Java 對象的形式存儲在 JVM 中. 若是內存空間不夠,部分數據分區將再也不緩存,在每次須要用到這些數據時從新進行計算. 這是默認的級別.
MEMORY_AND_DISK將 RDD 以反序列化的 Java 對象的形式存儲在 JVM 中。若是內存空間不夠,將未緩存的數據分區存儲到磁盤,在須要使用這些分區時從磁盤讀取.
MEMORY_ONLY_SER
(Java and Scala)將 RDD 以序列化的 Java 對象的形式進行存儲(每一個分區爲一個 byte 數組)。這種方式會比反序列化對象的方式節省不少空間,尤爲是在使用fast serializer時會節省更多的空間,可是在讀取時會增長 CPU 的計算負擔.
MEMORY_AND_DISK_SER
(Java and Scala)相似於 MEMORY_ONLY_SER ,可是溢出的分區會存儲到磁盤,而不是在用到它們時從新計算.
DISK_ONLY只在磁盤上緩存 RDD.
MEMORY_ONLY_2, MEMORY_AND_DISK_2, etc.與上面的級別功能相同,只不過每一個分區在集羣中兩個節點上創建副本.
OFF_HEAP (experimental 實驗性)相似於 MEMORY_ONLY_SER, 可是將數據存儲在off-heap memory中. 這須要啓用 off-heap 內存.
Note:在 Python 中, stored objects will 老是使用Picklelibrary 來序列化對象, 因此不管你選擇序列化級別都不要緊. 在 Python 中可用的存儲級別有MEMORY_ONLY,MEMORY_ONLY_2,MEMORY_AND_DISK,MEMORY_AND_DISK_2,DISK_ONLY, 和DISK_ONLY_2.
在 shuffle 操做中(例如reduceByKey),即使是用戶沒有調用persist方法,Spark 也會自動緩存部分中間數據.這麼作的目的是,在 shuffle 的過程當中某個節點運行失敗時,不須要從新計算全部的輸入數據。若是用戶想屢次使用某個 RDD,強烈推薦在該 RDD 上調用 persist 方法.
如何選擇存儲級別 ?
Spark 的存儲級別的選擇,核心問題是在 memory 內存使用率和 CPU 效率之間進行權衡。建議按下面的過程進行存儲級別的選擇:
若是您的 RDD 適合於默認存儲級別 (MEMORY_ONLY), leave them that way. 這是CPU效率最高的選項,容許RDD上的操做盡量快地運行.
若是不是, 試着使用MEMORY_ONLY_SER和selecting a fast serialization library以使對象更加節省空間,但仍然可以快速訪問。 (Java和Scala)
不要溢出到磁盤,除非計算您的數據集的函數是昂貴的, 或者它們過濾大量的數據. 不然, 從新計算分區可能與從磁盤讀取分區同樣快.
若是須要快速故障恢復,請使用複製的存儲級別 (e.g. 若是使用Spark來服務 來自網絡應用程序的請求).All存儲級別經過從新計算丟失的數據來提供完整的容錯能力,但複製的數據可以讓您繼續在 RDD 上運行任務,而無需等待從新計算一個丟失的分區.
刪除數據
Spark 會自動監視每一個節點上的緩存使用狀況,並使用 least-recently-used(LRU)的方式來丟棄舊數據分區。 若是您想手動刪除 RDD 而不是等待它掉出緩存,使用RDD.unpersist()方法。
共享變量
一般狀況下,一個傳遞給 Spark 操做(例如map或reduce)的函數 func 是在遠程的集羣節點上執行的。該函數 func 在多個節點執行過程當中使用的變量,是同一個變量的多個副本。這些變量的以副本的方式拷貝到每一個機器上,而且各個遠程機器上變量的更新並不會傳播回 driver program(驅動程序)。通用且支持 read-write(讀-寫) 的共享變量在任務間是不能勝任的。因此,Spark 提供了兩種特定類型的共享變量 : broadcast variables(廣播變量)和 accumulators(累加器)。
廣播變量
Broadcast variables(廣播變量)容許程序員將一個 read-only(只讀的)變量緩存到每臺機器上,而不是給任務傳遞一個副本。它們是如何來使用呢,例如,廣播變量能夠用一種高效的方式給每一個節點傳遞一份比較大的 input dataset(輸入數據集)副本。在使用廣播變量時,Spark 也嘗試使用高效廣播算法分發 broadcast variables(廣播變量)以下降通訊成本。
Spark 的 action(動做)操做是經過一系列的 stage(階段)進行執行的,這些 stage(階段)是經過分佈式的 「shuffle」 操做進行拆分的。Spark 會自動廣播出每一個 stage(階段)內任務所須要的公共數據。這種狀況下廣播的數據使用序列化的形式進行緩存,並在每一個任務運行前進行反序列化。這也就意味着,只有在跨越多個 stage(階段)的多個任務會使用相同的數據,或者在使用反序列化形式的數據特別重要的狀況下,使用廣播變量會有比較好的效果。
廣播變量經過在一個變量v上調用SparkContext.broadcast(v)方法來進行建立。廣播變量是v的一個 wrapper(包裝器),能夠經過調用value方法來訪問它的值。代碼示例以下:
scala>valbroadcastVar=sc.broadcast(Array(1,2,3))broadcastVar:org.apache.spark.broadcast.Broadcast[Array[Int]]=Broadcast(0)scala>broadcastVar.valueres0:Array[Int]=Array(1,2,3)
在建立廣播變量以後,在集羣上執行的全部的函數中,應該使用該廣播變量代替原來的v值,因此節點上的v最多分發一次。另外,對象v在廣播後不該該再被修改,以保證分發到全部的節點上的廣播變量具備一樣的值(例如,若是之後該變量會被運到一個新的節點)。
Accumulators(累加器)
Accumulators(累加器)是一個僅能夠執行 「added」(添加)的變量來經過一個關聯和交換操做,所以能夠高效地執行支持並行。累加器能夠用於實現 counter( 計數,相似在 MapReduce 中那樣)或者 sums(求和)。原生 Spark 支持數值型的累加器,而且程序員能夠添加新的支持類型。
做爲一個用戶,您能夠建立 accumulators(累加器)而且重命名. 以下圖所示, 一個命名的 accumulator 累加器(在這個例子中是counter)將顯示在 web UI 中,用於修改該累加器的階段。 Spark 在 「Tasks」 任務表中顯示由任務修改的每一個累加器的值.
在 UI 中跟蹤累加器能夠有助於瞭解運行階段的進度(注: 這在 Python 中尚不支持).
能夠經過調用SparkContext.longAccumulator()或SparkContext.doubleAccumulator()方法建立數值類型的accumulator(累加器)以分別累加 Long 或 Double 類型的值。集羣上正在運行的任務就能夠使用add方法來累計數值。然而,它們不可以讀取它的值。只有 driver program(驅動程序)才能夠使用value方法讀取累加器的值。
下面的代碼展現了一個 accumulator(累加器)被用於對一個數組中的元素求和:
scala>valaccum=sc.longAccumulator("My Accumulator")accum:org.apache.spark.util.LongAccumulator=LongAccumulator(id:0,name:Some(MyAccumulator),value:0)scala>sc.parallelize(Array(1,2,3,4)).foreach(x=>accum.add(x))...10/09/2918:41:08INFOSparkContext:Tasksfinishedin0.317106sscala>accum.valueres2:Long=10
雖然此代碼使用 Long 類型的累加器的內置支持, 可是開發者經過AccumulatorV2它的子類來建立本身的類型. AccumulatorV2 抽象類有幾個須要 override(重寫)的方法:reset方法可將累加器重置爲 0,add方法可將其它值添加到累加器中,merge方法可將其餘一樣類型的累加器合併爲一個. 其餘須要重寫的方法可參考API documentation. 例如, 假設咱們有一個表示數學上 vectors(向量)的MyVector類,咱們能夠寫成:
classVectorAccumulatorV2extendsAccumulatorV2[MyVector,MyVector]{privatevalmyVector:MyVector=MyVector.createZeroVectordefreset():Unit={myVector.reset()}defadd(v:MyVector):Unit={myVector.add(v)}...}// Then, create an Accumulator of this type:valmyVectorAcc=newVectorAccumulatorV2// Then, register it into spark context:sc.register(myVectorAcc,"MyVectorAcc1")
注意,在開發者定義本身的 AccumulatorV2 類型時, resulting type(返回值類型)可能與添加的元素的類型不一致。
累加器的更新只發生在action操做中,Spark 保證每一個任務只更新累加器一次,例如,重啓任務不會更新值。在 transformations(轉換)中, 用戶須要注意的是,若是 task(任務)或 job stages(階段)從新執行,每一個任務的更新操做可能會執行屢次。
累加器不會改變 Spark lazy evaluation(懶加載)的模式。若是累加器在 RDD 中的一個操做中進行更新,它們的值僅被更新一次,RDD 被做爲 action 的一部分來計算。所以,在一個像map()這樣的 transformation(轉換)時,累加器的更新並無執行。下面的代碼片斷證實了這個特性:
valaccum=sc.longAccumulatordata.map{x=>accum.add(x);x}// Here, accum is still 0 because no actions have caused the map operation to be computed.
部署應用到集羣中
該應用提交指南描述瞭如何將應用提交到集羣中. 簡單的說, 在您將應用打包成一個JAR(針對 Java/Scala) 或者一組.py或.zip文件 (針對Python), 該bin/spark-submit腳本可讓你提交它到任何所支持的 cluster manager 上去.
從 Java / Scala 啓動 Spark jobs
該org.apache.spark.launcherpackage 提供了 classes 用於使用簡單的 Java API 來做爲一個子進程啓動 Spark jobs.
單元測試
Spark 能夠友好的使用流行的單元測試框架進行單元測試。在將 master URL 設置爲local來測試時會簡單的建立一個SparkContext,運行您的操做,而後調用SparkContext.stop()將該做業中止。由於 Spark 不支持在同一個程序中並行的運行兩個 contexts,因此須要確保使用 finally 塊或者測試框架的tearDown方法將 context 中止。
快速連接
您能夠在 Spark 網站上看一下Spark 程序示例. 此外, Spark 在examples目錄中包含了許多示例 (Scala,Java,Python,R). 您能夠經過傳遞 class name 到 Spark 的 bin/run-example 腳本以運行 Java 和 Scala 示例; 例如:
./bin/run-example SparkPi
針對 Python 示例,使用spark-submit來代替:
./bin/spark-submit examples/src/main/python/pi.py
針對 R 示例,使用spark-submit來代替:
./bin/spark-submit examples/src/main/r/dataframe.R
針對應用程序的優化, 該配置和優化指南一些最佳實踐的信息. 這些優化建議在確保你的數據以高效的格式存儲在內存中尤爲重要. 針對部署參考, 該集羣模式概述描述了分佈式操做和支持的 cluster managers 集羣管理器的組件.
最後,全部的 API 文檔可在Scala,Java,PythonandR中獲取.
原文地址: http://spark.apachecn.org/docs/cn/2.2.0/rdd-programming-guide.html
網頁地址: http://spark.apachecn.org/
github: https://github.com/apachecn/spark-doc-zh(以爲不錯麻煩給個 Star,謝謝!~)
Geekhoo 關注
2017.09.25 11:15* 字數 32982 閱讀 894評論 0喜歡 21
Spark SQL, DataFrames and Datasets Guide
無類型的Dataset操做 (aka DataFrame 操做)
Running SQL Queries Programmatically
Untyped User-Defined Aggregate Functions
Type-Safe User-Defined Aggregate Functions
Generic Load/Save Functions (通用 加載/保存 功能)
Manually Specifying Options (手動指定選項)
Run SQL on files directly (直接在文件上運行 SQL)
Saving to Persistent Tables (保存到持久表)
Bucketing, Sorting and Partitioning (分桶, 排序和分區)
Loading Data Programmatically (以編程的方式加載數據)
Hive metastore Parquet table conversion (Hive metastore Parquet table 轉換)
Hive/Parquet Schema Reconciliation
DataFrame data reader/writer interface
DataFrame.groupBy 保留 grouping columns(分組的列)
針對 DataType 刪除在 org.apache.spark.sql 包中的一些類型別名(僅限於 Scala)
UDF 註冊遷移到sqlContext.udf中 (Java & Scala)
Python DataTypes 再也不是 Singletons(單例的)
Overview
Spark SQL 是 Spark 處理結構化數據的一個模塊.與基礎的 Spark RDD API 不一樣, Spark SQL 提供了查詢結構化數據及計算結果等信息的接口.在內部, Spark SQL 使用這個額外的信息去執行額外的優化.有幾種方式能夠跟 Spark SQL 進行交互, 包括 SQL 和 Dataset API.當使用相同執行引擎進行計算時, 不管使用哪一種 API / 語言均可以快速的計算.這種統一意味着開發人員可以在基於提供最天然的方式來表達一個給定的 transformation API 之間實現輕鬆的來回切換不一樣的 .
該頁面全部例子使用的示例數據都包含在 Spark 的發佈中, 而且能夠使用spark-shell,pysparkshell, 或者sparkRshell來運行.
SQL
Spark SQL 的功能之一是執行 SQL 查詢.Spark SQL 也可以被用於從已存在的 Hive 環境中讀取數據.更多關於如何配置這個特性的信息, 請參考Hive 表這部分. 當以另外的編程語言運行SQL 時, 查詢結果將以Dataset/DataFrame的形式返回.您也能夠使用命令行或者經過JDBC/ODBC與 SQL 接口交互.
Datasets and DataFrames
一個 Dataset 是一個分佈式的數據集合 Dataset 是在 Spark 1.6 中被添加的新接口, 它提供了 RDD 的優勢(強類型化, 可以使用強大的 lambda 函數)與Spark SQL執行引擎的優勢.一個 Dataset 能夠從 JVM 對象來構造而且使用轉換功能(map, flatMap, filter, 等等). Dataset API 在Scala和Java是可用的.Python 不支持 Dataset API.可是因爲 Python 的動態特性, 許多 Dataset API 的優勢已經可用了 (也就是說, 你可能經過 name 天生的row.columnName屬性訪問一行中的字段).這種狀況和 R 類似.
一個 DataFrame 是一個Dataset組成的指定列.它的概念與一個在關係型數據庫或者在 R/Python 中的表是相等的, 可是有不少優化. DataFrames 能夠從大量的sources中構造出來, 好比: 結構化的文本文件, Hive中的表, 外部數據庫, 或者已經存在的 RDDs. DataFrame API 能夠在 Scala, Java,Python, 和R中實現. 在 Scala 和 Java中, 一個 DataFrame 所表明的是一個多個Row(行)的的 Dataset(數據集合). 在the Scala API中,DataFrame僅僅是一個Dataset[Row]類型的別名. 然而, 在Java API中, 用戶須要去使用Dataset去表明一個DataFrame.
在此文檔中, 咱們將經常會引用 Scala/Java Datasets 的Rows 做爲 DataFrames.
開始入門
起始點: SparkSession
Spark SQL中全部功能的入口點是SparkSession類. 要建立一個SparkSession, 僅使用SparkSession.builder()就能夠了:
importorg.apache.spark.sql.SparkSessionvalspark=SparkSession.builder().appName("Spark SQL basic example").config("spark.some.config.option","some-value").getOrCreate()// For implicit conversions like converting RDDs to DataFramesimportspark.implicits._
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
Spark 2.0 中的SparkSession爲 Hive 特性提供了內嵌的支持, 包括使用 HiveQL 編寫查詢的能力, 訪問 Hive UDF,以及從 Hive 表中讀取數據的能力.爲了使用這些特性, 你不須要去有一個已存在的 Hive 設置.
建立 DataFrames
在一個SparkSession中, 應用程序能夠從一個已經存在的RDD, 從hive表, 或者從Spark數據源中建立一個DataFrames.
舉個例子, 下面就是基於一個JSON文件建立一個DataFrame:
valdf=spark.read.json("examples/src/main/resources/people.json")// Displays the content of the DataFrame to stdoutdf.show()// +----+-------+// | age| name|// +----+-------+// |null|Michael|// | 30| Andy|// | 19| Justin|// +----+-------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
無類型的Dataset操做 (aka DataFrame 操做)
DataFrames 提供了一個特定的語法用在Scala,Java,PythonandR中機構化數據的操做.
正如上面提到的同樣, Spark 2.0中, DataFrames在Scala 和 Java API中, 僅僅是多個Rows的Dataset. 這些操做也參考了與強類型的Scala/Java Datasets中的」類型轉換」 對應的」無類型轉換」 .
這裏包括一些使用 Dataset 進行結構化數據處理的示例 :
// This import is needed to use the $-notationimportspark.implicits._// Print the schema in a tree formatdf.printSchema()// root// |-- age: long (nullable = true)// |-- name: string (nullable = true)// Select only the "name" columndf.select("name").show()// +-------+// | name|// +-------+// |Michael|// | Andy|// | Justin|// +-------+// Select everybody, but increment the age by 1df.select($"name",$"age"+1).show()// +-------+---------+// | name|(age + 1)|// +-------+---------+// |Michael| null|// | Andy| 31|// | Justin| 20|// +-------+---------+// Select people older than 21df.filter($"age">21).show()// +---+----+// |age|name|// +---+----+// | 30|Andy|// +---+----+// Count people by agedf.groupBy("age").count().show()// +----+-----+// | age|count|// +----+-----+// | 19| 1|// |null| 1|// | 30| 1|// +----+-----+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
可以在 DataFrame 上被執行的操做類型的完整列表請參考API 文檔.
除了簡單的列引用和表達式以外, DataFrame 也有豐富的函數庫, 包括 string 操做, date 算術, 常見的 math 操做以及更多.可用的完整列表請參考DataFrame 函數指南.
Running SQL Queries Programmatically
SparkSession的sql函數可讓應用程序以編程的方式運行 SQL 查詢, 並將結果做爲一個DataFrame返回.
// Register the DataFrame as a SQL temporary viewdf.createOrReplaceTempView("people")valsqlDF=spark.sql("SELECT * FROM people")sqlDF.show()// +----+-------+// | age| name|// +----+-------+// |null|Michael|// | 30| Andy|// | 19| Justin|// +----+-------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
全局臨時視圖
Spark SQL中的臨時視圖是session級別的, 也就是會隨着session的消失而消失. 若是你想讓一個臨時視圖在全部session中相互傳遞而且可用, 直到Spark 應用退出, 你能夠創建一個全局的臨時視圖.全局的臨時視圖存在於系統數據庫global_temp中, 咱們必須加上庫名去引用它, 好比.SELECT * FROM global_temp.view1.
// Register the DataFrame as a global temporary viewdf.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-sessionspark.newSession().sql("SELECT * FROM global_temp.people").show()// +----+-------+// | age| name|// +----+-------+// |null|Michael|// | 30| Andy|// | 19| Justin|// +----+-------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
建立Datasets
Dataset 與 RDD 類似, 然而, 並非使用 Java 序列化或者 Kryo編碼器來序列化用於處理或者經過網絡進行傳輸的對象. 雖然編碼器和標準的序列化都負責將一個對象序列化成字節, 編碼器是動態生成的代碼, 而且使用了一種容許 Spark 去執行許多像 filtering, sorting 以及 hashing 這樣的操做, 不須要將字節反序列化成對象的格式.
// 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 interfacecaseclassPerson(name:String,age:Long)// Encoders are created for case classesvalcaseClassDS=Seq(Person("Andy",32)).toDS()caseClassDS.show()// +----+---+// |name|age|// +----+---+// |Andy| 32|// +----+---+// Encoders for most common types are automatically provided by importing spark.implicits._valprimitiveDS=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 namevalpath="examples/src/main/resources/people.json"valpeopleDS=spark.read.json(path).as[Person]peopleDS.show()// +----+-------+// | age| name|// +----+-------+// |null|Michael|// | 30| Andy|// | 19| Justin|// +----+-------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
RDD的互操做性
Spark SQL 支持兩種不一樣的方法用於轉換已存在的 RDD 成爲 Dataset.第一種方法是使用反射去推斷一個包含指定的對象類型的 RDD 的 Schema.在你的 Spark 應用程序中當你已知 Schema 時這個基於方法的反射可讓你的代碼更簡潔.
第二種用於建立 Dataset 的方法是經過一個容許你構造一個 Schema 而後把它應用到一個已存在的 RDD 的編程接口.然而這種方法更繁瑣, 當列和它們的類型知道運行時都是未知時它容許你去構造 Dataset.
使用反射推斷Schema
Spark SQL 的 Scala 接口支持自動轉換一個包含 case classes 的 RDD 爲 DataFrame.Case class 定義了表的 Schema.Case class 的參數名使用反射讀取而且成爲了列名.Case class 也能夠是嵌套的或者包含像Seq或者Array這樣的複雜類型.這個 RDD 可以被隱式轉換成一個 DataFrame 而後被註冊爲一個表.表能夠用於後續的 SQL 語句.
// For implicit conversions from RDDs to DataFramesimportspark.implicits._// Create an RDD of Person objects from a text file, convert it to a DataframevalpeopleDF=spark.sparkContext.textFile("examples/src/main/resources/people.txt").map(_.split(",")).map(attributes=>Person(attributes(0),attributes(1).trim.toInt)).toDF()// Register the DataFrame as a temporary viewpeopleDF.createOrReplaceTempView("people")// SQL statements can be run by using the sql methods provided by SparkvalteenagersDF=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 indexteenagersDF.map(teenager=>"Name: "+teenager(0)).show()// +------------+// | value|// +------------+// |Name: Justin|// +------------+// or by field nameteenagersDF.map(teenager=>"Name: "+teenager.getAs[String]("name")).show()// +------------+// | value|// +------------+// |Name: Justin|// +------------+// No pre-defined encoders for Dataset[Map[K,V]], define explicitlyimplicitvalmapEncoder=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))
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
以編程的方式指定Schema
當 case class 不可以在執行以前被定義(例如, records 記錄的結構在一個 string 字符串中被編碼了, 或者一個 text 文本 dataset 將被解析而且不一樣的用戶投影的字段是不同的).一個DataFrame能夠使用下面的三步以編程的方式來建立.
從原始的 RDD 建立 RDD 的Row(行);
Step 1 被建立後, 建立 Schema 表示一個StructType匹配 RDD 中的Row(行)的結構.
經過SparkSession提供的createDataFrame方法應用 Schema 到 RDD 的 RowS(行).
例如:
importorg.apache.spark.sql.types._// Create an RDDvalpeopleRDD=spark.sparkContext.textFile("examples/src/main/resources/people.txt")// The schema is encoded in a stringvalschemaString="name age"// Generate the schema based on the string of schemavalfields=schemaString.split(" ").map(fieldName=>StructField(fieldName,StringType,nullable=true))valschema=StructType(fields)// Convert records of the RDD (people) to RowsvalrowRDD=peopleRDD.map(_.split(",")).map(attributes=>Row(attributes(0),attributes(1).trim))// Apply the schema to the RDDvalpeopleDF=spark.createDataFrame(rowRDD,schema)// Creates a temporary view using the DataFramepeopleDF.createOrReplaceTempView("people")// SQL can be run over a temporary view created using DataFramesvalresults=spark.sql("SELECT name FROM people")// 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 nameresults.map(attributes=>"Name: "+attributes(0)).show()// +-------------+// | value|// +-------------+// |Name: Michael|// | Name: Andy|// | Name: Justin|// +-------------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
Aggregations
Thebuilt-in DataFrames functionsprovide common aggregations such ascount(),countDistinct(),avg(),max(),min(), etc. While those functions are designed for DataFrames, Spark SQL also has type-safe versions for some of them inScalaandJavato work with strongly typed Datasets. Moreover, users are not limited to the predefined aggregate functions and can create their own.
Untyped User-Defined Aggregate Functions
Users have to extend theUserDefinedAggregateFunctionabstract class to implement a custom untyped aggregate function. For example, a user-defined average can look like:
importorg.apache.spark.sql.expressions.MutableAggregationBufferimportorg.apache.spark.sql.expressions.UserDefinedAggregateFunctionimportorg.apache.spark.sql.types._importorg.apache.spark.sql.Rowimportorg.apache.spark.sql.SparkSessionobjectMyAverageextendsUserDefinedAggregateFunction{// Data types of input arguments of this aggregate functiondefinputSchema:StructType=StructType(StructField("inputColumn",LongType)::Nil)// Data types of values in the aggregation bufferdefbufferSchema:StructType={StructType(StructField("sum",LongType)::StructField("count",LongType)::Nil)}// The data type of the returned valuedefdataType:DataType=DoubleType// Whether this function always returns the same output on the identical inputdefdeterministic:Boolean=true// Initializes the given aggregation buffer. The buffer itself is a `Row` that in addition to// standard methods like retrieving a value at an index (e.g., get(), getBoolean()), provides// the opportunity to update its values. Note that arrays and maps inside the buffer are still// immutable.definitialize(buffer:MutableAggregationBuffer):Unit={buffer(0)=0Lbuffer(1)=0L}// Updates the given aggregation buffer `buffer` with new input data from `input`defupdate(buffer:MutableAggregationBuffer,input:Row):Unit={if(!input.isNullAt(0)){buffer(0)=buffer.getLong(0)+input.getLong(0)buffer(1)=buffer.getLong(1)+1}}// Merges two aggregation buffers and stores the updated buffer values back to `buffer1`defmerge(buffer1:MutableAggregationBuffer,buffer2:Row):Unit={buffer1(0)=buffer1.getLong(0)+buffer2.getLong(0)buffer1(1)=buffer1.getLong(1)+buffer2.getLong(1)}// Calculates the final resultdefevaluate(buffer:Row):Double=buffer.getLong(0).toDouble/buffer.getLong(1)}// Register the function to access itspark.udf.register("myAverage",MyAverage)valdf=spark.read.json("examples/src/main/resources/employees.json")df.createOrReplaceTempView("employees")df.show()// +-------+------+// | name|salary|// +-------+------+// |Michael| 3000|// | Andy| 4500|// | Justin| 3500|// | Berta| 4000|// +-------+------+valresult=spark.sql("SELECT myAverage(salary) as average_salary FROM employees")result.show()// +--------------+// |average_salary|// +--------------+// | 3750.0|// +--------------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/UserDefinedUntypedAggregation.scala" in the Spark repo.
Type-Safe User-Defined Aggregate Functions
User-defined aggregations for strongly typed Datasets revolve around theAggregatorabstract class. For example, a type-safe user-defined average can look like:
importorg.apache.spark.sql.expressions.Aggregatorimportorg.apache.spark.sql.Encoderimportorg.apache.spark.sql.Encodersimportorg.apache.spark.sql.SparkSessioncaseclassEmployee(name:String,salary:Long)caseclassAverage(varsum:Long,varcount:Long)objectMyAverageextendsAggregator[Employee,Average,Double]{// A zero value for this aggregation. Should satisfy the property that any b + zero = bdefzero:Average=Average(0L,0L)// Combine two values to produce a new value. For performance, the function may modify `buffer`// and return it instead of constructing a new objectdefreduce(buffer:Average,employee:Employee):Average={buffer.sum+=employee.salarybuffer.count+=1buffer}// Merge two intermediate valuesdefmerge(b1:Average,b2:Average):Average={b1.sum+=b2.sumb1.count+=b2.countb1}// Transform the output of the reductiondeffinish(reduction:Average):Double=reduction.sum.toDouble/reduction.count// Specifies the Encoder for the intermediate value typedefbufferEncoder:Encoder[Average]=Encoders.product// Specifies the Encoder for the final output value typedefoutputEncoder:Encoder[Double]=Encoders.scalaDouble}valds=spark.read.json("examples/src/main/resources/employees.json").as[Employee]ds.show()// +-------+------+// | name|salary|// +-------+------+// |Michael| 3000|// | Andy| 4500|// | Justin| 3500|// | Berta| 4000|// +-------+------+// Convert the function to a `TypedColumn` and give it a namevalaverageSalary=MyAverage.toColumn.name("average_salary")valresult=ds.select(averageSalary)result.show()// +--------------+// |average_salary|// +--------------+// | 3750.0|// +--------------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/UserDefinedTypedAggregation.scala" in the Spark repo.
Data Sources (數據源)
Spark SQL 支持經過 DataFrame 接口對各類 data sources (數據源)進行操做. DataFrame 能夠使用 relational transformations (關係轉換)操做, 也可用於建立 temporary view (臨時視圖). 將 DataFrame 註冊爲 temporary view (臨時視圖)容許您對其數據運行 SQL 查詢. 本節 描述了使用 Spark Data Sources 加載和保存數據的通常方法, 而後涉及可用於 built-in data sources (內置數據源)的 specific options (特定選項).
Generic Load/Save Functions (通用 加載/保存 功能)
在最簡單的形式中, 默認數據源(parquet, 除非另有配置spark.sql.sources.default)將用於全部操做.
valusersDF=spark.read.load("examples/src/main/resources/users.parquet")usersDF.select("name","favorite_color").write.save("namesAndFavColors.parquet")
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
Manually Specifying Options (手動指定選項)
您還能夠 manually specify (手動指定)將與任何你想傳遞給 data source 的其餘選項一塊兒使用的 data source . Data sources 由其 fully qualified name (徹底限定名稱)(即org.apache.spark.sql.parquet), 可是對於 built-in sources (內置的源), 你也能夠使用它們的 shortnames (短名稱)(json,parquet,jdbc,orc,libsvm,csv,text).從任何 data source type (數據源類型)加載 DataFrames 能夠使用此 syntax (語法)轉換爲其餘類型.
valpeopleDF=spark.read.format("json").load("examples/src/main/resources/people.json")peopleDF.select("name","age").write.format("parquet").save("namesAndAges.parquet")
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
Run SQL on files directly (直接在文件上運行 SQL)
不使用讀取 API 將文件加載到 DataFrame 並進行查詢, 也能夠直接用 SQL 查詢該文件.
valsqlDF=spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`")
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
Save Modes (保存模式)
Save operations (保存操做)能夠選擇使用SaveMode, 它指定如何處理現有數據若是存在的話. 重要的是要意識到, 這些 save modes (保存模式)不使用任何 locking (鎖定)而且不是 atomic (原子). 另外, 當執行Overwrite時, 數據將在新數據寫出以前被刪除.
Scala/JavaAny LanguageMeaning
SaveMode.ErrorIfExists(default)"error"(default)將 DataFrame 保存到 data source (數據源)時, 若是數據已經存在, 則會拋出異常.
SaveMode.Append"append"將 DataFrame 保存到 data source (數據源)時, 若是 data/table 已存在, 則 DataFrame 的內容將被 append (附加)到現有數據中.
SaveMode.Overwrite"overwrite"Overwrite mode (覆蓋模式)意味着將 DataFrame 保存到 data source (數據源)時, 若是 data/table 已經存在, 則預期 DataFrame 的內容將 overwritten (覆蓋)現有數據.
SaveMode.Ignore"ignore"Ignore mode (忽略模式)意味着當將 DataFrame 保存到 data source (數據源)時, 若是數據已經存在, 則保存操做預期不會保存 DataFrame 的內容, 而且不更改現有數據. 這與 SQL 中的CREATE TABLE IF NOT EXISTS相似.
Saving to Persistent Tables (保存到持久表)
DataFrames也能夠使用saveAsTable命令做爲 persistent tables (持久表)保存到 Hive metastore 中. 請注意, existing Hive deployment (現有的 Hive 部署)不須要使用此功能. Spark 將爲您建立默認的 local Hive metastore (本地 Hive metastore)(使用 Derby ). 與createOrReplaceTempView命令不一樣,saveAsTable將 materialize (實現) DataFrame 的內容, 並建立一個指向 Hive metastore 中數據的指針. 即便您的 Spark 程序從新啓動, Persistent tables (持久性表)仍然存在, 由於您保持與同一個 metastore 的鏈接. 能夠經過使用表的名稱在SparkSession上調用table方法來建立 persistent tabl (持久表)的 DataFrame .
對於 file-based (基於文件)的 data source (數據源), 例如 text, parquet, json等, 您能夠經過path選項指定 custom table path (自定義表路徑), 例如df.write.option("path", "/some/path").saveAsTable("t"). 當表被 dropped (刪除)時, custom table path (自定義表路徑)將不會被刪除, 而且表數據仍然存在. 若是未指定自定義表路徑, Spark 將把數據寫入 warehouse directory (倉庫目錄)下的默認表路徑. 當表被刪除時, 默認的表路徑也將被刪除.
從 Spark 2.1 開始, persistent datasource tables (持久性數據源表)將 per-partition metadata (每一個分區元數據)存儲在 Hive metastore 中. 這帶來了幾個好處:
因爲 metastore 只能返回查詢的必要 partitions (分區), 所以再也不須要將第一個查詢上的全部 partitions discovering 到表中.
Hive DDLs 如ALTER TABLE PARTITION ... SET LOCATION如今可用於使用 Datasource API 建立的表.
請注意, 建立 external datasource tables (外部數據源表)(帶有path選項)的表時, 默認狀況下不會收集 partition information (分區信息). 要 sync (同步) metastore 中的分區信息, 能夠調用MSCK REPAIR TABLE.
Bucketing, Sorting and Partitioning (分桶, 排序和分區)
對於 file-based data source (基於文件的數據源), 也能夠對 output (輸出)進行 bucket 和 sort 或者 partition . Bucketing 和 sorting 僅適用於 persistent tables :
peopleDF.write.bucketBy(42,"name").sortBy("age").saveAsTable("people_bucketed")
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
在使用 Dataset API 時, partitioning 能夠同時與save和saveAsTable一塊兒使用.
usersDF.write.partitionBy("favorite_color").format("parquet").save("namesPartByColor.parquet")
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
能夠爲 single table (單個表)使用 partitioning 和 bucketing:
peopleDF.write.partitionBy("favorite_color").bucketBy(42,"name").saveAsTable("people_partitioned_bucketed")
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
partitionBy建立一個 directory structure (目錄結構), 如Partition Discovery部分所述. 所以, 對 cardinality (基數)較高的 columns 的適用性有限. 相反,bucketBy能夠在固定數量的 buckets 中分配數據, 而且能夠在 a number of unique values is unbounded (多個惟一值無界時)使用數據.
Parquet Files
Parquet是許多其餘數據處理系統支持的 columnar format (柱狀格式). Spark SQL 支持讀寫 Parquet 文件, 可自動保留 schema of the original data (原始數據的模式). 當編寫 Parquet 文件時, 出於兼容性緣由, 全部 columns 都將自動轉換爲可空.
Loading Data Programmatically (以編程的方式加載數據)
使用上面例子中的數據:
// Encoders for most common types are automatically provided by importing spark.implicits._importspark.implicits._valpeopleDF=spark.read.json("examples/src/main/resources/people.json")// DataFrames can be saved as Parquet files, maintaining the schema informationpeopleDF.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 DataFramevalparquetFileDF=spark.read.parquet("people.parquet")// Parquet files can also be used to create a temporary view and then used in SQL statementsparquetFileDF.createOrReplaceTempView("parquetFile")valnamesDF=spark.sql("SELECT name FROM parquetFile WHERE age BETWEEN 13 AND 19")namesDF.map(attributes=>"Name: "+attributes(0)).show()// +------------+// | value|// +------------+// |Name: Justin|// +------------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
Partition Discovery (分區發現)
Table partitioning (表分區)是在像 Hive 這樣的系統中使用的常見的優化方法. 在 partitioned table (分區表)中, 數據一般存儲在不一樣的目錄中, partitioning column values encoded (分區列值編碼)在每一個 partition directory (分區目錄)的路徑中. Parquet data source (Parquet 數據源)如今能夠自動 discover (發現)和 infer (推斷)分區信息. 例如, 咱們能夠使用如下 directory structure (目錄結構)將全部之前使用的 population data (人口數據)存儲到 partitioned table (分區表)中, 其中有兩個額外的列gender和country做爲 partitioning columns (分區列):
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 將自動從路徑中提取 partitioning information (分區信息). 如今返回的 DataFrame 的 schema (模式)變成:
root|-- name: string (nullable = true)|-- age: long (nullable = true)|-- gender: string (nullable = true)|-- country: string (nullable = true)
請注意, 會自動 inferred (推斷) partitioning columns (分區列)的 data types (數據類型).目前, 支持 numeric data types (數字數據類型)和 string type (字符串類型).有些用戶可能不想自動推斷 partitioning columns (分區列)的數據類型.對於這些用例, automatic type inference (自動類型推斷)能夠由spark.sql.sources.partitionColumnTypeInference.enabled配置, 默認爲true.當禁用 type inference (類型推斷)時, string type (字符串類型)將用於 partitioning columns (分區列).
從 Spark 1.6.0 開始, 默認狀況下, partition discovery (分區發現)只能找到給定路徑下的 partitions (分區).對於上述示例, 若是用戶將path/to/table/gender=male傳遞給SparkSession.read.parquet或SparkSession.read.load, 則gender將不被視爲 partitioning column (分區列).若是用戶須要指定 partition discovery (分區發現)應該開始的基本路徑, 則能夠在數據源選項中設置basePath.例如, 當path/to/table/gender=male是數據的路徑而且用戶將basePath設置爲path/to/table/,gender將是一個 partitioning column (分區列).
Schema Merging (模式合併)
像 ProtocolBuffer , Avro 和 Thrift 同樣, Parquet 也支持 schema evolution (模式演進). 用戶能夠從一個 simple schema (簡單的架構)開始, 並根據須要逐漸向 schema 添加更多的 columns (列). 以這種方式, 用戶可能會使用不一樣但相互兼容的 schemas 的 multiple Parquet files (多個 Parquet 文件). Parquet data source (Parquet 數據源)如今可以自動檢測這種狀況並 merge (合併)全部這些文件的 schemas .
因爲 schema merging (模式合併)是一個 expensive operation (相對昂貴的操做), 而且在大多數狀況下不是必需的, 因此默認狀況下從 1.5.0 開始. 你能夠按照以下的方式啓用它:
讀取 Parquet 文件時, 將 data source option (數據源選項)mergeSchema設置爲true(以下面的例子所示), 或
將 global SQL option (全局 SQL 選項)spark.sql.parquet.mergeSchema設置爲true.
// This is used to implicitly convert an RDD to a DataFrame.importspark.implicits._// Create a simple DataFrame, store into a partition directoryvalsquaresDF=spark.sparkContext.makeRDD(1to5).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 columnvalcubesDF=spark.sparkContext.makeRDD(6to10).map(i=>(i,i*i*i)).toDF("value","cube")cubesDF.write.parquet("data/test_table/key=2")// Read the partitioned tablevalmergedDF=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)
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
Hive metastore Parquet table conversion (Hive metastore Parquet table 轉換)
當讀取和寫入 Hive metastore Parquet 表時, Spark SQL 將嘗試使用本身的 Parquet support (Parquet 支持), 而不是 Hive SerDe 來得到更好的性能. 此 behavior (行爲)由spark.sql.hive.convertMetastoreParquet配置控制, 默認狀況下 turned on (打開).
Hive/Parquet Schema Reconciliation
從 table schema processing (表格模式處理)的角度來講, Hive 和 Parquet 之間有兩個關鍵的區別.
Hive 不區分大小寫, 而 Parquet 不是
Hive 認爲全部 columns (列)均可覺得空, 而 Parquet 中的可空性是 significant (重要)的.
因爲這個緣由, 當將 Hive metastore Parquet 錶轉換爲 Spark SQL Parquet 表時, 咱們必須調整 metastore schema 與 Parquet schema. reconciliation 規則是:
在兩個 schema 中具備 same name (相同名稱)的 Fields (字段)必須具備 same data type (相同的數據類型), 而無論 nullability (可空性). reconciled field 應具備 Parquet 的數據類型, 以便 nullability (可空性)獲得尊重.
reconciled schema (調和模式)正好包含 Hive metastore schema 中定義的那些字段.
只出如今 Parquet schema 中的任何字段將被 dropped (刪除)在 reconciled schema 中.
僅在 Hive metastore schema 中出現的任何字段在 reconciled schema 中做爲 nullable field (可空字段)添加.
Metadata Refreshing (元數據刷新)
Spark SQL 緩存 Parquet metadata 以得到更好的性能. 當啓用 Hive metastore Parquet table conversion (轉換)時, 這些 converted tables (轉換表)的 metadata (元數據)也被 cached (緩存). 若是這些表由 Hive 或其餘外部工具更新, 則須要手動刷新以確保 consistent metadata (一致的元數據).
// spark is an existing SparkSessionspark.catalog.refreshTable("my_table")
Configuration (配置)
能夠使用SparkSession上的setConf方法或使用 SQL 運行SET key = value命令來完成 Parquet 的配置.
Property Name (參數名稱)Default(默認)Meaning(含義)
spark.sql.parquet.binaryAsStringfalse一些其餘 Parquet-producing systems (Parquet 生產系統), 特別是 Impala, Hive 和舊版本的 Spark SQL , 在 writing out (寫出) Parquet schema 時, 不區分 binary data (二進制數據)和 strings (字符串). 該 flag 告訴 Spark SQL 將 binary data (二進制數據)解釋爲 string (字符串)以提供與這些系統的兼容性.
spark.sql.parquet.int96AsTimestamptrue一些 Parquet-producing systems , 特別是 Impala 和 Hive , 將 Timestamp 存入INT96 . 該 flag 告訴 Spark SQL 將 INT96 數據解析爲 timestamp 以提供與這些系統的兼容性.
spark.sql.parquet.cacheMetadatatrue打開 Parquet schema metadata 的緩存. 能夠加快查詢靜態數據.
spark.sql.parquet.compression.codecsnappy在編寫 Parquet 文件時設置 compression codec (壓縮編解碼器)的使用. 可接受的值包括: uncompressed, snappy, gzip, lzo .
spark.sql.parquet.filterPushdowntrue設置爲 true 時啓用 Parquet filter push-down optimization .
spark.sql.hive.convertMetastoreParquettrue當設置爲 false 時, Spark SQL 將使用 Hive SerDe 做爲 parquet tables , 而不是內置的支持.
spark.sql.parquet.mergeSchemafalse當爲 true 時, Parquet data source (Parquet 數據源) merges (合併)從全部 data files (數據文件)收集的 schemas , 不然若是沒有可用的 summary file , 則從 summary file 或 random data file 中挑選 schema .
spark.sql.optimizer.metadataOnlytrue若是爲 true , 則啓用使用表的 metadata 的 metadata-only query optimization 來生成 partition columns (分區列)而不是 table scans (表掃描). 當 scanned (掃描)的全部 columns (列)都是 partition columns (分區列)而且 query (查詢)具備知足 distinct semantics (不一樣語義)的 aggregate operator (聚合運算符)時, 它將適用.
JSON Datasets (JSON 數據集)
Spark SQL 能夠 automatically infer (自動推斷)JSON dataset 的 schema, 並將其做爲Dataset[Row]加載. 這個 conversion (轉換)能夠在Dataset[String]上使用SparkSession.read.json()來完成, 或 JSON 文件.
請注意, 以a json file提供的文件不是典型的 JSON 文件. 每行必須包含一個 separate (單獨的), self-contained valid (獨立的有效的)JSON 對象. 有關更多信息, 請參閱JSON Lines text format, also called newline-delimited JSON.
對於 regular multi-line JSON file (常規的多行 JSON 文件), 將multiLine選項設置爲true.
// Primitive types (Int, String, etc) and Product types (case classes) encoders are// supported by importing this when creating a Dataset.importspark.implicits._// A JSON dataset is pointed to by path.// The path can be either a single text file or a directory storing text filesvalpath="examples/src/main/resources/people.json"valpeopleDF=spark.read.json(path)// The inferred schema can be visualized using the printSchema() methodpeopleDF.printSchema()// root// |-- age: long (nullable = true)// |-- name: string (nullable = true)// Creates a temporary view using the DataFramepeopleDF.createOrReplaceTempView("people")// SQL statements can be run by using the sql methods provided by sparkvalteenagerNamesDF=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// a Dataset[String] storing one JSON object per stringvalotherPeopleDataset=spark.createDataset("""{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}"""::Nil)valotherPeople=spark.read.json(otherPeopleDataset)otherPeople.show()// +---------------+----+// | address|name|// +---------------+----+// |[Columbus,Ohio]| Yin|// +---------------+----+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
Hive 表
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 支持實例化SparkSession,包括鏈接到持續的 Hive 轉移,支持 Hive serdes 和 Hive 用戶定義的功能。 沒有現有 Hive 部署的用戶仍然能夠啓用 Hive 支持。 當hive-site.xml未配置時,上下文會自動在當前目錄中建立metastore_db,並建立由spark.sql.warehouse.dir配置的目錄,該目錄默認爲Spark應用程序當前目錄中的spark-warehouse目錄 開始了 請注意,自從2.0.0以來,hive-site.xml中的hive.metastore.warehouse.dir屬性已被棄用。 而是使用spark.sql.warehouse.dir來指定倉庫中數據庫的默認位置。 您可能須要向啓動 Spark 應用程序的用戶授予寫權限。å
importjava.io.Fileimportorg.apache.spark.sql.Rowimportorg.apache.spark.sql.SparkSessioncaseclassRecord(key:Int,value:String)// warehouseLocation points to the default location for managed databases and tablesvalwarehouseLocation=newFile("spark-warehouse").getAbsolutePathvalspark=SparkSession.builder().appName("Spark Hive Example").config("spark.sql.warehouse.dir",warehouseLocation).enableHiveSupport().getOrCreate()importspark.implicits._importspark.sqlsql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING) USING hive")sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src")// Queries are expressed in HiveQLsql("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.valsqlDF=sql("SELECT key, value FROM src WHERE key < 10 ORDER BY key")// The items in DataFrames are of type Row, which allows you to access each column by ordinal.valstringsDS=sqlDF.map{caseRow(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.valrecordsDF=spark.createDataFrame((1to100).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|// ...
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/hive/SparkHiveExample.scala" in the Spark repo.
指定 Hive 表的存儲格式
建立 Hive 表時,須要定義如何 從/向 文件系統 read/write 數據,即 「輸入格式」 和 「輸出格式」。 您還須要定義該表如何將數據反序列化爲行,或將行序列化爲數據,即 「serde」。 如下選項可用於指定存儲格式 (「serde」, 「input format」, 「output format」),例如,CREATE TABLE src(id int) USING hive OPTIONS(fileFormat 'parquet')。 默認狀況下,咱們將以純文本形式讀取表格文件。 請注意,Hive 存儲處理程序在建立表時不受支持,您能夠使用 Hive 端的存儲處理程序建立一個表,並使用 Spark SQL 來讀取它。
Property NameMeaning
fileFormatfileFormat是一種存儲格式規範的包,包括 "serde","input format" 和 "output format"。 目前咱們支持6個文件格式:'sequencefile','rcfile','orc','parquet','textfile'和'avro'。
inputFormat, outputFormat這兩個選項將相應的 "InputFormat" 和 "OutputFormat" 類的名稱指定爲字符串文字,例如: `org.apache.hadoop.hive.ql.io.orc.OrcInputFormat`。 這兩個選項必須成對出現,若是您已經指定了 "fileFormat" 選項,則沒法指定它們。
serde此選項指定 serde 類的名稱。 當指定 `fileFormat` 選項時,若是給定的 `fileFormat` 已經包含 serde 的信息,那麼不要指定這個選項。 目前的 "sequencefile", "textfile" 和 "rcfile" 不包含 serde 信息,你能夠使用這3個文件格式的這個選項。
fieldDelim, escapeDelim, collectionDelim, mapkeyDelim, lineDelim這些選項只能與 "textfile" 文件格式一塊兒使用。它們定義如何將分隔的文件讀入行。
使用OPTIONS定義的全部其餘屬性將被視爲 Hive serde 屬性。
與不一樣版本的 Hive Metastore 進行交互
Spark SQL 的 Hive 支持的最重要的部分之一是與 Hive metastore 進行交互,這使得 Spark SQL 可以訪問 Hive 表的元數據。 從 Spark 1.4.0 開始,使用 Spark SQL 的單一二進制構建能夠使用下面所述的配置來查詢不一樣版本的 Hive 轉移。 請注意,獨立於用於與轉移點通訊的 Hive 版本,內部 Spark SQL 將針對 Hive 1.2.1 進行編譯,並使用這些類進行內部執行(serdes,UDF,UDAF等)。
如下選項可用於配置用於檢索元數據的 Hive 版本:
屬性名稱默認值含義
spark.sql.hive.metastore.version1.2.1Hive metastore 版本。 可用選項爲0.12.0至1.2.1。
spark.sql.hive.metastore.jarsbuiltin當啓用-Phive時,使用 Hive 1.2.1,它與 Spark 程序集捆綁在一塊兒。選擇此選項時,spark.sql.hive.metastore.version 必須爲1.2.1或未定義。 行家 使用從Maven存儲庫下載的指定版本的Hive jar。 一般不建議在生產部署中使用此配置。 ***** 應用於實例化 HiveMetastoreClient 的 jar 的位置。該屬性能夠是三個選項之一:
builtin當啓用-Phive時,使用 Hive 1.2.1,它與 Spark 程序集捆綁在一塊兒。選擇此選項時,spark.sql.hive.metastore.version必須爲1.2.1或未定義。
maven使用從 Maven 存儲庫下載的指定版本的 Hive jar。一般不建議在生產部署中使用此配置。
JVM 的標準格式的 classpath。 該類路徑必須包含全部 Hive 及其依賴項,包括正確版本的 Hadoop。這些罐只須要存在於 driver 程序中,但若是您正在運行在 yarn 集羣模式,那麼您必須確保它們與應用程序一塊兒打包。
spark.sql.hive.metastore.sharedPrefixescom.mysql.jdbc,
org.postgresql,
com.microsoft.sqlserver,
oracle.jdbc使用逗號分隔的類前綴列表,應使用在 Spark SQL 和特定版本的 Hive 之間共享的類加載器來加載。 一個共享類的示例就是用來訪問 Hive metastore 的 JDBC driver。 其它須要共享的類,是須要與已經共享的類進行交互的。 例如,log4j 使用的自定義 appender。
spark.sql.hive.metastore.barrierPrefixes(empty)一個逗號分隔的類前綴列表,應該明確地爲 Spark SQL 正在通訊的 Hive 的每一個版本從新加載。 例如,在一般將被共享的前綴中聲明的 Hive UDF (即: �org.apache.spark.*)。
JDBC 鏈接其它數據庫
Spark SQL 還包括能夠使用 JDBC 從其餘數據庫讀取數據的數據源。此功能應優於使用JdbcRDD。 這是由於結果做爲 DataFrame 返回,而且能夠輕鬆地在 Spark SQL 中處理或與其餘數據源鏈接。 JDBC 數據源也更容易從 Java 或 Python 使用,由於它不須要用戶提供 ClassTag。(請注意,這不一樣於 Spark SQL JDBC 服務器,容許其餘應用程序使用 Spark SQL 運行查詢)。
要開始使用,您須要在 Spark 類路徑中包含特定數據庫的 JDBC driver 程序。 例如,要從 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 還支持如下不區分大小寫的選項:
�屬性名稱含義
url要鏈接的JDBC URL。 源特定的鏈接屬性能夠在URL中指定。 例如jdbc:jdbc:postgresql://localhost/test?user=fred&password=secret
dbtable應該讀取的 JDBC 表。請注意,能夠使用在SQL查詢的FROM子句中有效的任何內容。 例如,您能夠使用括號中的子查詢代替完整表。
driver用於鏈接到此 URL 的 JDBC driver 程序的類名。
partitionColumn, lowerBound, upperBound若是指定了這些選項,則必須指定這些選項。 另外,必須指定numPartitions. 他們描述如何從多個 worker 並行讀取數據時將表給分區。partitionColumn必須是有問題的表中的數字列。 請注意,lowerBound和upperBound僅用於決定分區的大小,而不是用於過濾表中的行。 所以,表中的全部行將被分區並返回。此選項僅適用於讀操做。
numPartitions在表讀寫中能夠用於並行度的最大分區數。這也肯定併發JDBC鏈接的最大數量。 若是要寫入的分區數超過此限制,則在寫入以前經過調用coalesce(numPartitions)將其減小到此限制。
fetchsizeJDBC 抓取的大小,用於肯定每次數據往返傳遞的行數。 這有利於提高 JDBC driver 的性能,它們的默認值較小(例如: Oracle 是 10 行)。 該選項僅適用於讀取操做。
batchsizeJDBC 批處理的大小,用於肯定每次數據往返傳遞的行數。 這有利於提高 JDBC driver 的性能。 該選項僅適用於寫操做。默認值爲1000.
isolationLevel事務隔離級別,適用於當前鏈接。 它能夠是NONE,READ_COMMITTED,READ_UNCOMMITTED,REPEATABLE_READ, 或SERIALIZABLE之一,對應於 JDBC 鏈接對象定義的標準事務隔離級別,默認爲READ_UNCOMMITTED。 此選項僅適用於寫操做。請參考java.sql.Connection中的文檔。
truncate這是一個與 JDBC 相關的選項。 啓用SaveMode.Overwrite時,此選項會致使 Spark 截斷現有表,而不是刪除並從新建立。 這能夠更有效,而且防止表元數據(例如,索引)被移除。 可是,在某些狀況下,例如當新數據具備不一樣的模式時,它將沒法工做。 它默認爲false。 此選項僅適用於寫操做。
createTableOptions這是一個與JDBC相關的選項。 若是指定,此選項容許在建立表時設置特定於數據庫的表和分區選項(例如:CREATE TABLE t (name string) ENGINE=InnoDB.)。此選項僅適用於寫操做。
createTableColumnTypes使用數據庫列數據類型而不是默認值,建立表時。 數據類型信息應以與 CREATE TABLE 列語法相同的格式指定(例如:"name CHAR(64), comments VARCHAR(1024)")。 指定的類型應該是有效的 spark sql 數據類型。此選項僅適用於寫操做。
// Note: JDBC loading and saving can be achieved via either the load/save or jdbc methods// Loading data from a JDBC sourcevaljdbcDF=spark.read.format("jdbc").option("url","jdbc:postgresql:dbserver").option("dbtable","schema.tablename").option("user","username").option("password","password").load()valconnectionProperties=newProperties()connectionProperties.put("user","username")connectionProperties.put("password","password")valjdbcDF2=spark.read.jdbc("jdbc:postgresql:dbserver","schema.tablename",connectionProperties)// Saving data to a JDBC sourcejdbcDF.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)// Specifying create table column data types on writejdbcDF.write.option("createTableColumnTypes","name CHAR(64), comments VARCHAR(1024)").jdbc("jdbc:postgresql:dbserver","schema.tablename",connectionProperties)
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
故障排除
JDBC driver 程序類必須對客戶端會話和全部執行程序上的原始類加載器可見。 這是由於 Java 的 DriverManager 類執行安全檢查,致使它忽略原始類加載器不可見的全部 driver 程序,當打開鏈接時。一個方便的方法是修改全部工做節點上的compute_classpath.sh 以包含您的 driver 程序 JAR。
一些數據庫,例如 H2,將全部名稱轉換爲大寫。 您須要使用大寫字母來引用 Spark SQL 中的這些名稱。
性能調優
對於某些工做負載,能夠經過緩存內存中的數據或打開一些實驗選項來提升性能。
在內存中緩存數據
Spark SQL 能夠經過調用spark.catalog.cacheTable("tableName")或dataFrame.cache()來使用內存中的列格式來緩存表。 而後,Spark SQL 將只掃描所需的列,並將自動調整壓縮以最小化內存使用量和 GC 壓力。 您能夠調用spark.catalog.uncacheTable("tableName")從內存中刪除該表。
內存緩存的配置能夠使用SparkSession上的setConf方法或使用 SQL 運行SET key=value命令來完成。
屬性名稱默認含義
spark.sql.inMemoryColumnarStorage.compressedtrue當設置爲 true 時,Spark SQL 將根據數據的統計信息爲每一個列自動選擇一個壓縮編解碼器。
spark.sql.inMemoryColumnarStorage.batchSize10000控制批量的柱狀緩存的大小。更大的批量大小能夠提升內存利用率和壓縮率,可是在緩存數據時會冒出 OOM 風險。
其餘配置選項
如下選項也可用於調整查詢執行的性能。這些選項可能會在未來的版本中被廢棄,由於更多的優化是自動執行的。
屬性名稱默認值含義
spark.sql.files.maxPartitionBytes134217728 (128 MB)在讀取文件時,將單個分區打包的最大字節數。
spark.sql.files.openCostInBytes4194304 (4 MB)按照字節數來衡量的打開文件的估計費用能夠在同一時間進行掃描。 將多個文件放入分區時使用。最好過分估計,那麼具備小文件的分區將比具備較大文件的分區(首先計劃的)更快。
spark.sql.broadcastTimeout300廣播鏈接中的廣播等待時間超時(秒)
spark.sql.autoBroadcastJoinThreshold10485760 (10 MB)配置執行鏈接時將廣播給全部工做節點的表的最大大小(以字節爲單位)。 經過將此值設置爲-1能夠禁用廣播。 請注意,目前的統計信息僅支持 Hive Metastore 表,其中已運行命令ANALYZE TABLE COMPUTE STATISTICS noscan。
spark.sql.shuffle.partitions200Configures the number of partitions to use when shuffling data for joins or aggregations.
分佈式 SQL 引擎
Spark SQL 也能夠充當使用其 JDBC/ODBC 或命令行界面的分佈式查詢引擎。 在這種模式下,最終用戶或應用程序能夠直接與 Spark SQL 交互運行 SQL 查詢,而不須要編寫任何代碼。
運行 Thrift JDBC/ODBC 服務器
這裏實現的 Thrift JDBC/ODBC 服務器對應於 Hive 1.2 中的HiveServer2。 您能夠使用 Spark 或 Hive 1.2.1 附帶的直線腳本測試 JDBC 服務器。
要啓動 JDBC/ODBC 服務器,請在 Spark 目錄中運行如下命令:
./sbin/start-thriftserver.sh
此腳本接受全部bin/spark-submit命令行選項,以及--hiveconf選項來指定 Hive 屬性。 您能夠運行./sbin/start-thriftserver.sh --help查看全部可用選項的完整列表。 默認狀況下,服務器監聽 localhost:10000. 您能夠經過環境變量覆蓋此行爲,即:
exportHIVE_SERVER2_THRIFT_PORT=exportHIVE_SERVER2_THRIFT_BIND_HOST=./sbin/start-thriftserver.sh\--master \...
or system properties:
./sbin/start-thriftserver.sh\--hiveconf hive.server2.thrift.port=\--hiveconf hive.server2.thrift.bind.host=\--master ...
如今,您能夠使用 beeline 來測試 Thrift JDBC/ODBC 服務器:
./bin/beeline
使用 beeline 方式鏈接到 JDBC/ODBC 服務器:
beeline> !connect jdbc:hive2://localhost:10000
Beeline 將要求您輸入用戶名和密碼。 在非安全模式下,只需輸入機器上的用戶名和空白密碼便可。 對於安全模式,請按照beeline 文檔中的說明進行操做。
配置Hive是經過將hive-site.xml,core-site.xml和hdfs-site.xml文件放在conf/中完成的。
您也能夠使用 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 to listen on; default is 10001
hive.server2.http.endpoint - HTTP endpoint; default is cliservice
要測試,請使用 beeline 以 http 模式鏈接到 JDBC/ODBC 服務器:
beeline> !connect jdbc:hive2://:/?hive.server2.transport.mode=http;hive.server2.thrift.http.path=
運行 Spark SQL CLI
Spark SQL CLI 是在本地模式下運行 Hive 轉移服務並執行從命令行輸入的查詢的方便工具。 請注意,Spark SQL CLI 不能與 Thrift JDBC 服務器通訊。
要啓動 Spark SQL CLI,請在 Spark 目錄中運行如下命令:
./bin/spark-sql
配置 Hive 是經過將hive-site.xml,core-site.xml和hdfs-site.xml文件放在conf/中完成的。 您能夠運行./bin/spark-sql --help獲取全部可用選項的完整列表。
遷移指南
從 Spark SQL 2.1 升級到 2.2
Spark 2.1.1 介紹了一個新的配置 key:spark.sql.hive.caseSensitiveInferenceMode. 它的默認設置是NEVER_INFER, 其行爲與 2.1.0 保持一致. 可是,Spark 2.2.0 將此設置的默認值更改成 「INFER_AND_SAVE」,以恢復與底層文件 schema(模式)具備大小寫混合的列名稱的 Hive metastore 表的兼容性。使用INFER_AND_SAVE配置的 value, 在第一次訪問 Spark 將對其還沒有保存推測 schema(模式)的任何 Hive metastore 表執行 schema inference(模式推斷). 請注意,對於具備數千個 partitions(分區)的表,模式推斷多是很是耗時的操做。若是不兼容大小寫混合的列名,您能夠安全地將spark.sql.hive.caseSensitiveInferenceMode設置爲NEVER_INFER,以免模式推斷的初始開銷。請注意,使用新的默認INFER_AND_SAVE設置,模式推理的結果被保存爲 metastore key 以供未來使用。所以,初始模式推斷僅發生在表的第一次訪問。
從 Spark SQL 2.0 升級到 2.1
Datasource tables(數據源表)如今存儲了 Hive metastore 中的 partition metadata(分區元數據). 這意味着諸如ALTER TABLE PARTITION ... SET LOCATION這樣的 Hive DDLs 如今使用 Datasource API 可用於建立 tables(表).
遺留的數據源表能夠經過MSCK REPAIR TABLE命令遷移到這種格式。建議遷移遺留表利用 Hive DDL 的支持和提供的計劃性能。
要肯定表是否已遷移,當在表上發出DESCRIBE FORMATTED命令時請查找PartitionProvider: Catalog屬性.
Datasource tables(數據源表)的INSERT OVERWRITE TABLE ... PARTITION ...行爲的更改。
在之前的 Spark 版本中,INSERT OVERWRITE覆蓋了整個 Datasource table,即便給出一個指定的 partition. 如今只有匹配規範的 partition 被覆蓋。
請注意,這仍然與 Hive 表的行爲不一樣,Hive 表僅覆蓋與新插入數據重疊的分區。
從 Spark SQL 1.6 升級到 2.0
SparkSession如今是 Spark 新的切入點, 它替代了老的SQLContext和HiveContext。注意 : 爲了向下兼容,老的 SQLContext 和 HiveContext 仍然保留。能夠從SparkSession獲取一個新的catalog接口 — 現有的訪問數據庫和表的 API,如listTables,createExternalTable,dropTempView,cacheTable都被移到該接口。
Dataset API 和 DataFrame API 進行了統一。在 Scala 中,DataFrame變成了Dataset[Row]類型的一個別名,而 Java API 使用者必須將DataFrame替換成Dataset。Dataset 類既提供了強類型轉換操做(如map,filter以及groupByKey)也提供了非強類型轉換操做(如select和groupBy)。因爲編譯期的類型安全不是 Python 和 R 語言的一個特性,Dataset 的概念並不適用於這些語言的 API。相反,DataFrame仍然是最基本的編程抽象, 就相似於這些語言中單節點 data frame 的概念。
Dataset 和 DataFrame API 中 unionAll 已通過時而且由union替代。
Dataset 和 DataFrame API 中 explode 已通過時,做爲選擇,能夠結合 select 或 flatMap 使用functions.explode()。
Dataset 和 DataFrame API 中registerTempTable已通過時而且由createOrReplaceTempView替代。
對 Hive tablesCREATE TABLE ... LOCATION行爲的更改.
從 Spark 2.0 開始,CREATE TABLE ... LOCATION與CREATE EXTERNAL TABLE ... LOCATION是相同的,以防止意外丟棄用戶提供的 locations(位置)中的現有數據。這意味着,在用戶指定位置的 Spark SQL 中建立的 Hive 表始終是 Hive 外部表。刪除外部表將不會刪除數據。 用戶不能指定 Hive managed tables(管理表)的位置. 請注意,這與Hive行爲不一樣。
所以,這些表上的 「DROP TABLE」 語句不會刪除數據。
從 Spark SQL 1.5 升級到 1.6
從 Spark 1.6 開始,默認狀況下服務器在多 session(會話)模式下運行。這意味着每一個 JDBC/ODBC 鏈接擁有一份本身的 SQL 配置和臨時函數註冊。緩存表仍在並共享。若是您但願以舊的單會話模式運行 Thrift server,請設置選項spark.sql.hive.thriftServer.singleSession爲true。您既能夠將此選項添加到spark-defaults.conf,或者經過--conf將它傳遞給start-thriftserver.sh。
./sbin/start-thriftserver.sh\--conf spark.sql.hive.thriftServer.singleSession=true\...
從 1.6.1 開始,在 sparkR 中 withColumn 方法支持添加一個新列或更換 DataFrame 同名的現有列。
從 Spark 1.6 開始,LongType 強制轉換爲 TimestampType 指望是秒,而不是微秒。這種更改是爲了匹配 Hive 1.2 的行爲,以便從 numeric(數值)類型進行更一致的類型轉換到 TimestampType。更多詳情請參閱SPARK-11724。
從 Spark SQL 1.4 升級到 1.5
使用手動管理的內存優化執行,如今是默認啓用的,以及代碼生成表達式求值。這些功能既能夠經過設置spark.sql.tungsten.enabled爲false來禁止使用。
Parquet 的模式合併默認狀況下再也不啓用。它能夠經過設置spark.sql.parquet.mergeSchema到true以從新啓用。
字符串在 Python 列的 columns(列)如今支持使用點(.)來限定列或訪問嵌套值。例如df['table.column.nestedField']。可是,這意味着若是你的列名中包含任何圓點,你如今必須避免使用反引號(如table.column.with.dots.nested)。
在內存中的列存儲分區修剪默認是開啓的。它能夠經過設置spark.sql.inMemoryColumnarStorage.partitionPruning爲false來禁用。
無限精度的小數列再也不支持,而不是 Spark SQL 最大精度爲 38 。當從BigDecimal對象推斷模式時,如今使用(38,18)。在 DDL 沒有指定精度時,則默認保留Decimal(10, 0)。
時間戳如今存儲在 1 微秒的精度,而不是 1 納秒的。
在 sql 語句中,floating point(浮點數)如今解析爲 decimal。HiveQL 解析保持不變。
SQL / DataFrame 函數的規範名稱如今是小寫(例如 sum vs SUM)。
JSON 數據源不會自動加載由其餘應用程序(未經過 Spark SQL 插入到數據集的文件)建立的新文件。對於 JSON 持久表(即表的元數據存儲在 Hive Metastore),用戶能夠使用REFRESH TABLESQL 命令或HiveContext的refreshTable方法,把那些新文件列入到表中。對於表明一個 JSON dataset 的 DataFrame,用戶須要從新建立 DataFrame,同時 DataFrame 中將包括新的文件。
PySpark 中 DataFrame 的 withColumn 方法支持添加新的列或替換現有的同名列。
從 Spark SQL 1.3 升級到 1.4
DataFrame data reader/writer interface
基於用戶反饋,咱們建立了一個新的更流暢的 API,用於讀取 (SQLContext.read) 中的數據並寫入數據 (DataFrame.write), 而且舊的 API 將過期(例如,SQLContext.parquetFile,SQLContext.jsonFile).
針對SQLContext.read(Scala,Java,Python) 和DataFrame.write(Scala,Java,Python) 的更多細節,請看 API 文檔.
DataFrame.groupBy 保留 grouping columns(分組的列)
根據用戶的反饋, 咱們更改了DataFrame.groupBy().agg()的默認行爲以保留DataFrame結果中的 grouping columns(分組列). 爲了在 1.3 中保持該行爲,請設置spark.sql.retainGroupColumns爲false.
// In 1.3.x, in order for the grouping column "department" to show up,// it must be included explicitly as part of the agg function call.df.groupBy("department").agg($"department",max("age"),sum("expense"))// In 1.4+, grouping column "department" is included automatically.df.groupBy("department").agg(max("age"),sum("expense"))// Revert to 1.3 behavior (not retaining grouping column) by:sqlContext.setConf("spark.sql.retainGroupColumns","false")
DataFrame.withColumn 上的行爲更改
以前 1.4 版本中,DataFrame.withColumn() 只支持添加列。該列將始終在 DateFrame 結果中被加入做爲新的列,即便現有的列可能存在相同的名稱。從 1.4 版本開始,DataFrame.withColumn() 支持添加與全部現有列的名稱不一樣的列或替換現有的同名列。
請注意,這一變化僅適用於 Scala API,並不適用於 PySpark 和 SparkR。
從 Spark SQL 1.0-1.2 升級到 1.3
在 Spark 1.3 中,咱們從 Spark SQL 中刪除了 「Alpha」 的標籤,做爲一部分已經清理過的可用的 API 。從 Spark 1.3 版本以上,Spark SQL 將提供在 1.X 系列的其餘版本的二進制兼容性。這種兼容性保證不包括被明確標記爲不穩定的(即 DeveloperApi 類或 Experimental) API。
重命名 DataFrame 的 SchemaRDD
升級到 Spark SQL 1.3 版本時,用戶會發現最大的變化是,SchemaRDD已改名爲DataFrame。這主要是由於 DataFrames 再也不從 RDD 直接繼承,而是由 RDDS 本身來實現這些功能。DataFrames 仍然能夠經過調用.rdd方法轉換爲 RDDS 。
在 Scala 中,有一個從SchemaRDD到DataFrame類型別名,能夠爲一些狀況提供源代碼兼容性。它仍然建議用戶更新他們的代碼以使用DataFrame來代替。Java 和 Python 用戶須要更新他們的代碼。
Java 和 Scala APIs 的統一
此前 Spark 1.3 有單獨的Java兼容類(JavaSQLContext和JavaSchemaRDD),借鑑於 Scala API。在 Spark 1.3 中,Java API 和 Scala API 已經統一。兩種語言的用戶能夠使用SQLContext和DataFrame。通常來講論文類嘗試使用兩種語言的共有類型(如Array替代了一些特定集合)。在某些狀況下不通用的類型狀況下,(例如,passing in closures 或 Maps)使用函數重載代替。
此外,該 Java 的特定類型的 API 已被刪除。Scala 和 Java 的用戶能夠使用存在於org.apache.spark.sql.types類來描述編程模式。
隔離隱式轉換和刪除 dsl 包(僅Scala)
許多 Spark 1.3 版本之前的代碼示例都以import sqlContext._開始,這提供了從 sqlContext 範圍的全部功能。在 Spark 1.3 中,咱們移除了從RDDs 到DateFrame再到SQLContext內部對象的隱式轉換。用戶如今應該寫成import sqlContext.implicits._.
此外,隱式轉換如今只能使用方法toDF來增長由Product(即 case classes or tuples)構成的RDD,而不是自動應用。
當使用 DSL 內部的函數時(如今使用DataFrameAPI 來替換), 用戶習慣導入org.apache.spark.sql.catalyst.dsl. 相反,應該使用公共的 dataframe 函數 API:import org.apache.spark.sql.functions._.
針對 DataType 刪除在 org.apache.spark.sql 包中的一些類型別名(僅限於 Scala)
Spark 1.3 移除存在於基本 SQL 包的DataType類型別名。開發人員應改成導入類org.apache.spark.sql.types。
UDF 註冊遷移到sqlContext.udf中 (Java & Scala)
用於註冊 UDF 的函數,無論是 DataFrame DSL 仍是 SQL 中用到的,都被遷移到SQLContext中的 udf 對象中。
sqlContext.udf.register("strLen",(s:String)=>s.length())
Python UDF 註冊保持不變。
Python DataTypes 再也不是 Singletons(單例的)
在 Python 中使用 DataTypes 時,你須要先構造它們(如:StringType()),而不是引用一個單例對象。
與 Apache Hive 的兼容
Spark SQL 在設計時就考慮到了和 Hive metastore,SerDes 以及 UDF 之間的兼容性。目前 Hive SerDes 和 UDF 都是基於 Hive 1.2.1 版本,而且Spark SQL 能夠鏈接到不一樣版本的Hive metastore(從 0.12.0 到 1.2.1,能夠參考與不一樣版本的 Hive Metastore 交互)
在現有的 Hive Warehouses 中部署
Spark SQL Thrift JDBC server 採用了開箱即用的設計以兼容已有的 Hive 安裝版本。你不須要修改現有的 Hive Metastore , 或者改變數據的位置和表的分區。
所支持的 Hive 特性
Spark SQL 支持絕大部分的 Hive 功能,如:
Hive query(查詢)語句, 包括:
SELECT
GROUP BY
ORDER BY
CLUSTER BY
SORT BY
全部 Hive 操做, 包括:
關係運算符 (=,⇔,==,<>,<,>,>=,<=, 等等)
算術運算符 (+,-,*,/,%, 等等)
邏輯運算符 (AND,&&,OR,||, 等等)
複雜類型的構造
數學函數 (sign,ln,cos, 等等)
String 函數 (instr,length,printf, 等等)
用戶定義函數 (UDF)
用戶定義聚合函數 (UDAF)
用戶定義 serialization formats (SerDes)
窗口函數
Joins
JOIN
{LEFT|RIGHT|FULL} OUTER JOIN
LEFT SEMI JOIN
CROSS JOIN
Unions
Sub-queries(子查詢)
SELECT col FROM ( SELECT a + b AS col from t1) t2
Sampling
Explain
Partitioned tables including dynamic partition insertion
View
全部的 Hive DDL 函數, 包括:
CREATE TABLE
CREATE TABLE AS SELECT
ALTER TABLE
大部分的 Hive Data types(數據類型), 包括:
TINYINT
SMALLINT
INT
BIGINT
BOOLEAN
FLOAT
DOUBLE
STRING
BINARY
TIMESTAMP
DATE
ARRAY<>
MAP<>
STRUCT<>
未支持的 Hive 函數
如下是目前還不支持的 Hive 函數列表。在 Hive 部署中這些功能大部分都用不到。
主要的 Hive 功能
Tables 使用 buckets 的 Tables: bucket 是 Hive table partition 中的 hash partitioning. Spark SQL 還不支持 buckets.
Esoteric Hive 功能
UNION類型
Unique join
Column 統計信息的收集: Spark SQL does not piggyback scans to collect column statistics at the moment and only supports populating the sizeInBytes field of the hive metastore.
Hive Input/Output Formats
File format for CLI: For results showing back to the CLI, Spark SQL only supports TextOutputFormat.
Hadoop archive
Hive 優化
有少數 Hive 優化尚未包含在 Spark 中。其中一些(好比 indexes 索引)因爲 Spark SQL 的這種內存計算模型而顯得不那麼重要。另一些在 Spark SQL 將來的版本中會持續跟蹤。
Block 級別的 bitmap indexes 和虛擬 columns (用於構建 indexes)
自動爲 join 和 groupBy 計算 reducer 個數 : 目前在 Spark SQL 中, 你須要使用 「SET spark.sql.shuffle.partitions=[num_tasks];」 來控制 post-shuffle 的並行度.
僅 Meta-data 的 query: 對於只使用 metadata 就能回答的查詢,Spark SQL 仍然會啓動計算結果的任務.
Skew data flag: Spark SQL 不遵循 Hive 中 skew 數據的標記.
STREAMTABLEhint in join: Spark SQL 不遵循STREAMTABLEhint.
對於查詢結果合併多個小文件: 若是輸出的結果包括多個小文件, Hive 能夠可選的合併小文件到一些大文件中去,以免溢出 HDFS metadata. Spark SQL 還不支持這樣.
參考
數據類型
Spark SQL 和 DataFrames 支持下面的數據類型:
Numeric types
ByteType: Represents 1-byte signed integer numbers. The range of numbers is from-128to127.
ShortType: Represents 2-byte signed integer numbers. The range of numbers is from-32768to32767.
IntegerType: Represents 4-byte signed integer numbers. The range of numbers is from-2147483648to2147483647.
LongType: Represents 8-byte signed integer numbers. The range of numbers is from-9223372036854775808to9223372036854775807.
FloatType: Represents 4-byte single-precision floating point numbers.
DoubleType: Represents 8-byte double-precision floating point numbers.
DecimalType: Represents arbitrary-precision signed decimal numbers. Backed internally byjava.math.BigDecimal. ABigDecimalconsists of an arbitrary precision integer unscaled value and a 32-bit integer scale.
String type
StringType: Represents character string values.
Binary type
BinaryType: Represents byte sequence values.
Boolean type
BooleanType: Represents boolean values.
Datetime type
TimestampType: Represents values comprising values of fields year, month, day, hour, minute, and second.
DateType: Represents values comprising values of fields year, month, day.
Complex types
ArrayType(elementType, containsNull): Represents values comprising a sequence of elements with the type ofelementType.containsNullis used to indicate if elements in aArrayTypevalue can havenullvalues.
MapType(keyType, valueType, valueContainsNull): Represents values comprising a set of key-value pairs. The data type of keys are described bykeyTypeand the data type of values are described byvalueType. For aMapTypevalue, keys are not allowed to havenullvalues.valueContainsNullis used to indicate if values of aMapTypevalue can havenullvalues.
StructType(fields): Represents values with the structure described by a sequence ofStructFields (fields).
StructField(name, dataType, nullable): Represents a field in aStructType. The name of a field is indicated byname. The data type of a field is indicated bydataType.nullableis used to indicate if values of this fields can havenullvalues.
Spark SQL 的全部數據類型都在包org.apache.spark.sql.types中. 你能夠用下示例示例來訪問它們.
importorg.apache.spark.sql.types._
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
Data type(數據類型)Scala 中的 Value 類型訪問或建立數據類型的 API
ByteTypeByteByteType
ShortTypeShortShortType
IntegerTypeIntIntegerType
LongTypeLongLongType
FloatTypeFloatFloatType
DoubleTypeDoubleDoubleType
DecimalTypejava.math.BigDecimalDecimalType
StringTypeStringStringType
BinaryTypeArray[Byte]BinaryType
BooleanTypeBooleanBooleanType
TimestampTypejava.sql.TimestampTimestampType
DateTypejava.sql.DateDateType
ArrayTypescala.collection.SeqArrayType(elementType, [containsNull])
Note(注意):containsNull的默認值是true.
MapTypescala.collection.MapMapType(keyType,valueType, [valueContainsNull])
Note(注意):valueContainsNull的默認值是true.
StructTypeorg.apache.spark.sql.RowStructType(fields)
Note(注意):fields是 StructFields 的 Seq. 全部, 兩個 fields 擁有相同的名稱是不被容許的.
StructField該 field(字段)數據類型的 Scala 中的 value 類型 (例如, 數據類型爲 IntegerType 的 StructField 是 Int)StructField(name,dataType, [nullable])
Note:nullable的默認值是true.
NaN Semantics
當處理一些不符合標準浮點數語義的float或double類型時,對於 Not-a-Number(NaN) 須要作一些特殊處理. 具體以下:
NaN = NaN 返回 true.
在 aggregations(聚合)操做中,全部的 NaN values 將被分到同一個組中.
在 join key 中 NaN 能夠當作一個普通的值.
NaN 值在升序排序中排到最後,比任何其餘數值都大.
Spark SQL, DataFrames and Datasets Guide
無類型的Dataset操做 (aka DataFrame 操做)
Running SQL Queries Programmatically
Untyped User-Defined Aggregate Functions
Type-Safe User-Defined Aggregate Functions
Generic Load/Save Functions (通用 加載/保存 功能)
Manually Specifying Options (手動指定選項)
Run SQL on files directly (直接在文件上運行 SQL)
Saving to Persistent Tables (保存到持久表)
Bucketing, Sorting and Partitioning (分桶, 排序和分區)
Loading Data Programmatically (以編程的方式加載數據)
Hive metastore Parquet table conversion (Hive metastore Parquet table 轉換)
Hive/Parquet Schema Reconciliation
DataFrame data reader/writer interface
DataFrame.groupBy 保留 grouping columns(分組的列)
針對 DataType 刪除在 org.apache.spark.sql 包中的一些類型別名(僅限於 Scala)
UDF 註冊遷移到sqlContext.udf中 (Java & Scala)
Python DataTypes 再也不是 Singletons(單例的)
Overview
Spark SQL 是 Spark 處理結構化數據的一個模塊.與基礎的 Spark RDD API 不一樣, Spark SQL 提供了查詢結構化數據及計算結果等信息的接口.在內部, Spark SQL 使用這個額外的信息去執行額外的優化.有幾種方式能夠跟 Spark SQL 進行交互, 包括 SQL 和 Dataset API.當使用相同執行引擎進行計算時, 不管使用哪一種 API / 語言均可以快速的計算.這種統一意味着開發人員可以在基於提供最天然的方式來表達一個給定的 transformation API 之間實現輕鬆的來回切換不一樣的 .
該頁面全部例子使用的示例數據都包含在 Spark 的發佈中, 而且能夠使用spark-shell,pysparkshell, 或者sparkRshell來運行.
SQL
Spark SQL 的功能之一是執行 SQL 查詢.Spark SQL 也可以被用於從已存在的 Hive 環境中讀取數據.更多關於如何配置這個特性的信息, 請參考Hive 表這部分. 當以另外的編程語言運行SQL 時, 查詢結果將以Dataset/DataFrame的形式返回.您也能夠使用命令行或者經過JDBC/ODBC與 SQL 接口交互.
Datasets and DataFrames
一個 Dataset 是一個分佈式的數據集合 Dataset 是在 Spark 1.6 中被添加的新接口, 它提供了 RDD 的優勢(強類型化, 可以使用強大的 lambda 函數)與Spark SQL執行引擎的優勢.一個 Dataset 能夠從 JVM 對象來構造而且使用轉換功能(map, flatMap, filter, 等等). Dataset API 在Scala和Java是可用的.Python 不支持 Dataset API.可是因爲 Python 的動態特性, 許多 Dataset API 的優勢已經可用了 (也就是說, 你可能經過 name 天生的row.columnName屬性訪問一行中的字段).這種狀況和 R 類似.
一個 DataFrame 是一個Dataset組成的指定列.它的概念與一個在關係型數據庫或者在 R/Python 中的表是相等的, 可是有不少優化. DataFrames 能夠從大量的sources中構造出來, 好比: 結構化的文本文件, Hive中的表, 外部數據庫, 或者已經存在的 RDDs. DataFrame API 能夠在 Scala, Java,Python, 和R中實現. 在 Scala 和 Java中, 一個 DataFrame 所表明的是一個多個Row(行)的的 Dataset(數據集合). 在the Scala API中,DataFrame僅僅是一個Dataset[Row]類型的別名. 然而, 在Java API中, 用戶須要去使用Dataset去表明一個DataFrame.
在此文檔中, 咱們將經常會引用 Scala/Java Datasets 的Rows 做爲 DataFrames.
開始入門
起始點: SparkSession
Spark SQL中全部功能的入口點是SparkSession類. 要建立一個SparkSession, 僅使用SparkSession.builder()就能夠了:
importorg.apache.spark.sql.SparkSessionvalspark=SparkSession.builder().appName("Spark SQL basic example").config("spark.some.config.option","some-value").getOrCreate()// For implicit conversions like converting RDDs to DataFramesimportspark.implicits._
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
Spark 2.0 中的SparkSession爲 Hive 特性提供了內嵌的支持, 包括使用 HiveQL 編寫查詢的能力, 訪問 Hive UDF,以及從 Hive 表中讀取數據的能力.爲了使用這些特性, 你不須要去有一個已存在的 Hive 設置.
建立 DataFrames
在一個SparkSession中, 應用程序能夠從一個已經存在的RDD, 從hive表, 或者從Spark數據源中建立一個DataFrames.
舉個例子, 下面就是基於一個JSON文件建立一個DataFrame:
valdf=spark.read.json("examples/src/main/resources/people.json")// Displays the content of the DataFrame to stdoutdf.show()// +----+-------+// | age| name|// +----+-------+// |null|Michael|// | 30| Andy|// | 19| Justin|// +----+-------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
無類型的Dataset操做 (aka DataFrame 操做)
DataFrames 提供了一個特定的語法用在Scala,Java,PythonandR中機構化數據的操做.
正如上面提到的同樣, Spark 2.0中, DataFrames在Scala 和 Java API中, 僅僅是多個Rows的Dataset. 這些操做也參考了與強類型的Scala/Java Datasets中的」類型轉換」 對應的」無類型轉換」 .
這裏包括一些使用 Dataset 進行結構化數據處理的示例 :
// This import is needed to use the $-notationimportspark.implicits._// Print the schema in a tree formatdf.printSchema()// root// |-- age: long (nullable = true)// |-- name: string (nullable = true)// Select only the "name" columndf.select("name").show()// +-------+// | name|// +-------+// |Michael|// | Andy|// | Justin|// +-------+// Select everybody, but increment the age by 1df.select($"name",$"age"+1).show()// +-------+---------+// | name|(age + 1)|// +-------+---------+// |Michael| null|// | Andy| 31|// | Justin| 20|// +-------+---------+// Select people older than 21df.filter($"age">21).show()// +---+----+// |age|name|// +---+----+// | 30|Andy|// +---+----+// Count people by agedf.groupBy("age").count().show()// +----+-----+// | age|count|// +----+-----+// | 19| 1|// |null| 1|// | 30| 1|// +----+-----+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
可以在 DataFrame 上被執行的操做類型的完整列表請參考API 文檔.
除了簡單的列引用和表達式以外, DataFrame 也有豐富的函數庫, 包括 string 操做, date 算術, 常見的 math 操做以及更多.可用的完整列表請參考DataFrame 函數指南.
Running SQL Queries Programmatically
SparkSession的sql函數可讓應用程序以編程的方式運行 SQL 查詢, 並將結果做爲一個DataFrame返回.
// Register the DataFrame as a SQL temporary viewdf.createOrReplaceTempView("people")valsqlDF=spark.sql("SELECT * FROM people")sqlDF.show()// +----+-------+// | age| name|// +----+-------+// |null|Michael|// | 30| Andy|// | 19| Justin|// +----+-------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
全局臨時視圖
Spark SQL中的臨時視圖是session級別的, 也就是會隨着session的消失而消失. 若是你想讓一個臨時視圖在全部session中相互傳遞而且可用, 直到Spark 應用退出, 你能夠創建一個全局的臨時視圖.全局的臨時視圖存在於系統數據庫global_temp中, 咱們必須加上庫名去引用它, 好比.SELECT * FROM global_temp.view1.
// Register the DataFrame as a global temporary viewdf.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-sessionspark.newSession().sql("SELECT * FROM global_temp.people").show()// +----+-------+// | age| name|// +----+-------+// |null|Michael|// | 30| Andy|// | 19| Justin|// +----+-------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
建立Datasets
Dataset 與 RDD 類似, 然而, 並非使用 Java 序列化或者 Kryo編碼器來序列化用於處理或者經過網絡進行傳輸的對象. 雖然編碼器和標準的序列化都負責將一個對象序列化成字節, 編碼器是動態生成的代碼, 而且使用了一種容許 Spark 去執行許多像 filtering, sorting 以及 hashing 這樣的操做, 不須要將字節反序列化成對象的格式.
// 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 interfacecaseclassPerson(name:String,age:Long)// Encoders are created for case classesvalcaseClassDS=Seq(Person("Andy",32)).toDS()caseClassDS.show()// +----+---+// |name|age|// +----+---+// |Andy| 32|// +----+---+// Encoders for most common types are automatically provided by importing spark.implicits._valprimitiveDS=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 namevalpath="examples/src/main/resources/people.json"valpeopleDS=spark.read.json(path).as[Person]peopleDS.show()// +----+-------+// | age| name|// +----+-------+// |null|Michael|// | 30| Andy|// | 19| Justin|// +----+-------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
RDD的互操做性
Spark SQL 支持兩種不一樣的方法用於轉換已存在的 RDD 成爲 Dataset.第一種方法是使用反射去推斷一個包含指定的對象類型的 RDD 的 Schema.在你的 Spark 應用程序中當你已知 Schema 時這個基於方法的反射可讓你的代碼更簡潔.
第二種用於建立 Dataset 的方法是經過一個容許你構造一個 Schema 而後把它應用到一個已存在的 RDD 的編程接口.然而這種方法更繁瑣, 當列和它們的類型知道運行時都是未知時它容許你去構造 Dataset.
使用反射推斷Schema
Spark SQL 的 Scala 接口支持自動轉換一個包含 case classes 的 RDD 爲 DataFrame.Case class 定義了表的 Schema.Case class 的參數名使用反射讀取而且成爲了列名.Case class 也能夠是嵌套的或者包含像Seq或者Array這樣的複雜類型.這個 RDD 可以被隱式轉換成一個 DataFrame 而後被註冊爲一個表.表能夠用於後續的 SQL 語句.
// For implicit conversions from RDDs to DataFramesimportspark.implicits._// Create an RDD of Person objects from a text file, convert it to a DataframevalpeopleDF=spark.sparkContext.textFile("examples/src/main/resources/people.txt").map(_.split(",")).map(attributes=>Person(attributes(0),attributes(1).trim.toInt)).toDF()// Register the DataFrame as a temporary viewpeopleDF.createOrReplaceTempView("people")// SQL statements can be run by using the sql methods provided by SparkvalteenagersDF=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 indexteenagersDF.map(teenager=>"Name: "+teenager(0)).show()// +------------+// | value|// +------------+// |Name: Justin|// +------------+// or by field nameteenagersDF.map(teenager=>"Name: "+teenager.getAs[String]("name")).show()// +------------+// | value|// +------------+// |Name: Justin|// +------------+// No pre-defined encoders for Dataset[Map[K,V]], define explicitlyimplicitvalmapEncoder=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))
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
以編程的方式指定Schema
當 case class 不可以在執行以前被定義(例如, records 記錄的結構在一個 string 字符串中被編碼了, 或者一個 text 文本 dataset 將被解析而且不一樣的用戶投影的字段是不同的).一個DataFrame能夠使用下面的三步以編程的方式來建立.
從原始的 RDD 建立 RDD 的Row(行);
Step 1 被建立後, 建立 Schema 表示一個StructType匹配 RDD 中的Row(行)的結構.
經過SparkSession提供的createDataFrame方法應用 Schema 到 RDD 的 RowS(行).
例如:
importorg.apache.spark.sql.types._// Create an RDDvalpeopleRDD=spark.sparkContext.textFile("examples/src/main/resources/people.txt")// The schema is encoded in a stringvalschemaString="name age"// Generate the schema based on the string of schemavalfields=schemaString.split(" ").map(fieldName=>StructField(fieldName,StringType,nullable=true))valschema=StructType(fields)// Convert records of the RDD (people) to RowsvalrowRDD=peopleRDD.map(_.split(",")).map(attributes=>Row(attributes(0),attributes(1).trim))// Apply the schema to the RDDvalpeopleDF=spark.createDataFrame(rowRDD,schema)// Creates a temporary view using the DataFramepeopleDF.createOrReplaceTempView("people")// SQL can be run over a temporary view created using DataFramesvalresults=spark.sql("SELECT name FROM people")// 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 nameresults.map(attributes=>"Name: "+attributes(0)).show()// +-------------+// | value|// +-------------+// |Name: Michael|// | Name: Andy|// | Name: Justin|// +-------------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
Aggregations
Thebuilt-in DataFrames functionsprovide common aggregations such ascount(),countDistinct(),avg(),max(),min(), etc. While those functions are designed for DataFrames, Spark SQL also has type-safe versions for some of them inScalaandJavato work with strongly typed Datasets. Moreover, users are not limited to the predefined aggregate functions and can create their own.
Untyped User-Defined Aggregate Functions
Users have to extend theUserDefinedAggregateFunctionabstract class to implement a custom untyped aggregate function. For example, a user-defined average can look like:
importorg.apache.spark.sql.expressions.MutableAggregationBufferimportorg.apache.spark.sql.expressions.UserDefinedAggregateFunctionimportorg.apache.spark.sql.types._importorg.apache.spark.sql.Rowimportorg.apache.spark.sql.SparkSessionobjectMyAverageextendsUserDefinedAggregateFunction{// Data types of input arguments of this aggregate functiondefinputSchema:StructType=StructType(StructField("inputColumn",LongType)::Nil)// Data types of values in the aggregation bufferdefbufferSchema:StructType={StructType(StructField("sum",LongType)::StructField("count",LongType)::Nil)}// The data type of the returned valuedefdataType:DataType=DoubleType// Whether this function always returns the same output on the identical inputdefdeterministic:Boolean=true// Initializes the given aggregation buffer. The buffer itself is a `Row` that in addition to// standard methods like retrieving a value at an index (e.g., get(), getBoolean()), provides// the opportunity to update its values. Note that arrays and maps inside the buffer are still// immutable.definitialize(buffer:MutableAggregationBuffer):Unit={buffer(0)=0Lbuffer(1)=0L}// Updates the given aggregation buffer `buffer` with new input data from `input`defupdate(buffer:MutableAggregationBuffer,input:Row):Unit={if(!input.isNullAt(0)){buffer(0)=buffer.getLong(0)+input.getLong(0)buffer(1)=buffer.getLong(1)+1}}// Merges two aggregation buffers and stores the updated buffer values back to `buffer1`defmerge(buffer1:MutableAggregationBuffer,buffer2:Row):Unit={buffer1(0)=buffer1.getLong(0)+buffer2.getLong(0)buffer1(1)=buffer1.getLong(1)+buffer2.getLong(1)}// Calculates the final resultdefevaluate(buffer:Row):Double=buffer.getLong(0).toDouble/buffer.getLong(1)}// Register the function to access itspark.udf.register("myAverage",MyAverage)valdf=spark.read.json("examples/src/main/resources/employees.json")df.createOrReplaceTempView("employees")df.show()// +-------+------+// | name|salary|// +-------+------+// |Michael| 3000|// | Andy| 4500|// | Justin| 3500|// | Berta| 4000|// +-------+------+valresult=spark.sql("SELECT myAverage(salary) as average_salary FROM employees")result.show()// +--------------+// |average_salary|// +--------------+// | 3750.0|// +--------------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/UserDefinedUntypedAggregation.scala" in the Spark repo.
Type-Safe User-Defined Aggregate Functions
User-defined aggregations for strongly typed Datasets revolve around theAggregatorabstract class. For example, a type-safe user-defined average can look like:
importorg.apache.spark.sql.expressions.Aggregatorimportorg.apache.spark.sql.Encoderimportorg.apache.spark.sql.Encodersimportorg.apache.spark.sql.SparkSessioncaseclassEmployee(name:String,salary:Long)caseclassAverage(varsum:Long,varcount:Long)objectMyAverageextendsAggregator[Employee,Average,Double]{// A zero value for this aggregation. Should satisfy the property that any b + zero = bdefzero:Average=Average(0L,0L)// Combine two values to produce a new value. For performance, the function may modify `buffer`// and return it instead of constructing a new objectdefreduce(buffer:Average,employee:Employee):Average={buffer.sum+=employee.salarybuffer.count+=1buffer}// Merge two intermediate valuesdefmerge(b1:Average,b2:Average):Average={b1.sum+=b2.sumb1.count+=b2.countb1}// Transform the output of the reductiondeffinish(reduction:Average):Double=reduction.sum.toDouble/reduction.count// Specifies the Encoder for the intermediate value typedefbufferEncoder:Encoder[Average]=Encoders.product// Specifies the Encoder for the final output value typedefoutputEncoder:Encoder[Double]=Encoders.scalaDouble}valds=spark.read.json("examples/src/main/resources/employees.json").as[Employee]ds.show()// +-------+------+// | name|salary|// +-------+------+// |Michael| 3000|// | Andy| 4500|// | Justin| 3500|// | Berta| 4000|// +-------+------+// Convert the function to a `TypedColumn` and give it a namevalaverageSalary=MyAverage.toColumn.name("average_salary")valresult=ds.select(averageSalary)result.show()// +--------------+// |average_salary|// +--------------+// | 3750.0|// +--------------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/UserDefinedTypedAggregation.scala" in the Spark repo.
Data Sources (數據源)
Spark SQL 支持經過 DataFrame 接口對各類 data sources (數據源)進行操做. DataFrame 能夠使用 relational transformations (關係轉換)操做, 也可用於建立 temporary view (臨時視圖). 將 DataFrame 註冊爲 temporary view (臨時視圖)容許您對其數據運行 SQL 查詢. 本節 描述了使用 Spark Data Sources 加載和保存數據的通常方法, 而後涉及可用於 built-in data sources (內置數據源)的 specific options (特定選項).
Generic Load/Save Functions (通用 加載/保存 功能)
在最簡單的形式中, 默認數據源(parquet, 除非另有配置spark.sql.sources.default)將用於全部操做.
valusersDF=spark.read.load("examples/src/main/resources/users.parquet")usersDF.select("name","favorite_color").write.save("namesAndFavColors.parquet")
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
Manually Specifying Options (手動指定選項)
您還能夠 manually specify (手動指定)將與任何你想傳遞給 data source 的其餘選項一塊兒使用的 data source . Data sources 由其 fully qualified name (徹底限定名稱)(即org.apache.spark.sql.parquet), 可是對於 built-in sources (內置的源), 你也能夠使用它們的 shortnames (短名稱)(json,parquet,jdbc,orc,libsvm,csv,text).從任何 data source type (數據源類型)加載 DataFrames 能夠使用此 syntax (語法)轉換爲其餘類型.
valpeopleDF=spark.read.format("json").load("examples/src/main/resources/people.json")peopleDF.select("name","age").write.format("parquet").save("namesAndAges.parquet")
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
Run SQL on files directly (直接在文件上運行 SQL)
不使用讀取 API 將文件加載到 DataFrame 並進行查詢, 也能夠直接用 SQL 查詢該文件.
valsqlDF=spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`")
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
Save Modes (保存模式)
Save operations (保存操做)能夠選擇使用SaveMode, 它指定如何處理現有數據若是存在的話. 重要的是要意識到, 這些 save modes (保存模式)不使用任何 locking (鎖定)而且不是 atomic (原子). 另外, 當執行Overwrite時, 數據將在新數據寫出以前被刪除.
Scala/JavaAny LanguageMeaning
SaveMode.ErrorIfExists(default)"error"(default)將 DataFrame 保存到 data source (數據源)時, 若是數據已經存在, 則會拋出異常.
SaveMode.Append"append"將 DataFrame 保存到 data source (數據源)時, 若是 data/table 已存在, 則 DataFrame 的內容將被 append (附加)到現有數據中.
SaveMode.Overwrite"overwrite"Overwrite mode (覆蓋模式)意味着將 DataFrame 保存到 data source (數據源)時, 若是 data/table 已經存在, 則預期 DataFrame 的內容將 overwritten (覆蓋)現有數據.
SaveMode.Ignore"ignore"Ignore mode (忽略模式)意味着當將 DataFrame 保存到 data source (數據源)時, 若是數據已經存在, 則保存操做預期不會保存 DataFrame 的內容, 而且不更改現有數據. 這與 SQL 中的CREATE TABLE IF NOT EXISTS相似.
Saving to Persistent Tables (保存到持久表)
DataFrames也能夠使用saveAsTable命令做爲 persistent tables (持久表)保存到 Hive metastore 中. 請注意, existing Hive deployment (現有的 Hive 部署)不須要使用此功能. Spark 將爲您建立默認的 local Hive metastore (本地 Hive metastore)(使用 Derby ). 與createOrReplaceTempView命令不一樣,saveAsTable將 materialize (實現) DataFrame 的內容, 並建立一個指向 Hive metastore 中數據的指針. 即便您的 Spark 程序從新啓動, Persistent tables (持久性表)仍然存在, 由於您保持與同一個 metastore 的鏈接. 能夠經過使用表的名稱在SparkSession上調用table方法來建立 persistent tabl (持久表)的 DataFrame .
對於 file-based (基於文件)的 data source (數據源), 例如 text, parquet, json等, 您能夠經過path選項指定 custom table path (自定義表路徑), 例如df.write.option("path", "/some/path").saveAsTable("t"). 當表被 dropped (刪除)時, custom table path (自定義表路徑)將不會被刪除, 而且表數據仍然存在. 若是未指定自定義表路徑, Spark 將把數據寫入 warehouse directory (倉庫目錄)下的默認表路徑. 當表被刪除時, 默認的表路徑也將被刪除.
從 Spark 2.1 開始, persistent datasource tables (持久性數據源表)將 per-partition metadata (每一個分區元數據)存儲在 Hive metastore 中. 這帶來了幾個好處:
因爲 metastore 只能返回查詢的必要 partitions (分區), 所以再也不須要將第一個查詢上的全部 partitions discovering 到表中.
Hive DDLs 如ALTER TABLE PARTITION ... SET LOCATION如今可用於使用 Datasource API 建立的表.
請注意, 建立 external datasource tables (外部數據源表)(帶有path選項)的表時, 默認狀況下不會收集 partition information (分區信息). 要 sync (同步) metastore 中的分區信息, 能夠調用MSCK REPAIR TABLE.
Bucketing, Sorting and Partitioning (分桶, 排序和分區)
對於 file-based data source (基於文件的數據源), 也能夠對 output (輸出)進行 bucket 和 sort 或者 partition . Bucketing 和 sorting 僅適用於 persistent tables :
peopleDF.write.bucketBy(42,"name").sortBy("age").saveAsTable("people_bucketed")
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
在使用 Dataset API 時, partitioning 能夠同時與save和saveAsTable一塊兒使用.
usersDF.write.partitionBy("favorite_color").format("parquet").save("namesPartByColor.parquet")
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
能夠爲 single table (單個表)使用 partitioning 和 bucketing:
peopleDF.write.partitionBy("favorite_color").bucketBy(42,"name").saveAsTable("people_partitioned_bucketed")
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
partitionBy建立一個 directory structure (目錄結構), 如Partition Discovery部分所述. 所以, 對 cardinality (基數)較高的 columns 的適用性有限. 相反,bucketBy能夠在固定數量的 buckets 中分配數據, 而且能夠在 a number of unique values is unbounded (多個惟一值無界時)使用數據.
Parquet Files
Parquet是許多其餘數據處理系統支持的 columnar format (柱狀格式). Spark SQL 支持讀寫 Parquet 文件, 可自動保留 schema of the original data (原始數據的模式). 當編寫 Parquet 文件時, 出於兼容性緣由, 全部 columns 都將自動轉換爲可空.
Loading Data Programmatically (以編程的方式加載數據)
使用上面例子中的數據:
// Encoders for most common types are automatically provided by importing spark.implicits._importspark.implicits._valpeopleDF=spark.read.json("examples/src/main/resources/people.json")// DataFrames can be saved as Parquet files, maintaining the schema informationpeopleDF.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 DataFramevalparquetFileDF=spark.read.parquet("people.parquet")// Parquet files can also be used to create a temporary view and then used in SQL statementsparquetFileDF.createOrReplaceTempView("parquetFile")valnamesDF=spark.sql("SELECT name FROM parquetFile WHERE age BETWEEN 13 AND 19")namesDF.map(attributes=>"Name: "+attributes(0)).show()// +------------+// | value|// +------------+// |Name: Justin|// +------------+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
Partition Discovery (分區發現)
Table partitioning (表分區)是在像 Hive 這樣的系統中使用的常見的優化方法. 在 partitioned table (分區表)中, 數據一般存儲在不一樣的目錄中, partitioning column values encoded (分區列值編碼)在每一個 partition directory (分區目錄)的路徑中. Parquet data source (Parquet 數據源)如今能夠自動 discover (發現)和 infer (推斷)分區信息. 例如, 咱們能夠使用如下 directory structure (目錄結構)將全部之前使用的 population data (人口數據)存儲到 partitioned table (分區表)中, 其中有兩個額外的列gender和country做爲 partitioning columns (分區列):
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 將自動從路徑中提取 partitioning information (分區信息). 如今返回的 DataFrame 的 schema (模式)變成:
root|-- name: string (nullable = true)|-- age: long (nullable = true)|-- gender: string (nullable = true)|-- country: string (nullable = true)
請注意, 會自動 inferred (推斷) partitioning columns (分區列)的 data types (數據類型).目前, 支持 numeric data types (數字數據類型)和 string type (字符串類型).有些用戶可能不想自動推斷 partitioning columns (分區列)的數據類型.對於這些用例, automatic type inference (自動類型推斷)能夠由spark.sql.sources.partitionColumnTypeInference.enabled配置, 默認爲true.當禁用 type inference (類型推斷)時, string type (字符串類型)將用於 partitioning columns (分區列).
從 Spark 1.6.0 開始, 默認狀況下, partition discovery (分區發現)只能找到給定路徑下的 partitions (分區).對於上述示例, 若是用戶將path/to/table/gender=male傳遞給SparkSession.read.parquet或SparkSession.read.load, 則gender將不被視爲 partitioning column (分區列).若是用戶須要指定 partition discovery (分區發現)應該開始的基本路徑, 則能夠在數據源選項中設置basePath.例如, 當path/to/table/gender=male是數據的路徑而且用戶將basePath設置爲path/to/table/,gender將是一個 partitioning column (分區列).
Schema Merging (模式合併)
像 ProtocolBuffer , Avro 和 Thrift 同樣, Parquet 也支持 schema evolution (模式演進). 用戶能夠從一個 simple schema (簡單的架構)開始, 並根據須要逐漸向 schema 添加更多的 columns (列). 以這種方式, 用戶可能會使用不一樣但相互兼容的 schemas 的 multiple Parquet files (多個 Parquet 文件). Parquet data source (Parquet 數據源)如今可以自動檢測這種狀況並 merge (合併)全部這些文件的 schemas .
因爲 schema merging (模式合併)是一個 expensive operation (相對昂貴的操做), 而且在大多數狀況下不是必需的, 因此默認狀況下從 1.5.0 開始. 你能夠按照以下的方式啓用它:
讀取 Parquet 文件時, 將 data source option (數據源選項)mergeSchema設置爲true(以下面的例子所示), 或
將 global SQL option (全局 SQL 選項)spark.sql.parquet.mergeSchema設置爲true.
// This is used to implicitly convert an RDD to a DataFrame.importspark.implicits._// Create a simple DataFrame, store into a partition directoryvalsquaresDF=spark.sparkContext.makeRDD(1to5).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 columnvalcubesDF=spark.sparkContext.makeRDD(6to10).map(i=>(i,i*i*i)).toDF("value","cube")cubesDF.write.parquet("data/test_table/key=2")// Read the partitioned tablevalmergedDF=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)
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
Hive metastore Parquet table conversion (Hive metastore Parquet table 轉換)
當讀取和寫入 Hive metastore Parquet 表時, Spark SQL 將嘗試使用本身的 Parquet support (Parquet 支持), 而不是 Hive SerDe 來得到更好的性能. 此 behavior (行爲)由spark.sql.hive.convertMetastoreParquet配置控制, 默認狀況下 turned on (打開).
Hive/Parquet Schema Reconciliation
從 table schema processing (表格模式處理)的角度來講, Hive 和 Parquet 之間有兩個關鍵的區別.
Hive 不區分大小寫, 而 Parquet 不是
Hive 認爲全部 columns (列)均可覺得空, 而 Parquet 中的可空性是 significant (重要)的.
因爲這個緣由, 當將 Hive metastore Parquet 錶轉換爲 Spark SQL Parquet 表時, 咱們必須調整 metastore schema 與 Parquet schema. reconciliation 規則是:
在兩個 schema 中具備 same name (相同名稱)的 Fields (字段)必須具備 same data type (相同的數據類型), 而無論 nullability (可空性). reconciled field 應具備 Parquet 的數據類型, 以便 nullability (可空性)獲得尊重.
reconciled schema (調和模式)正好包含 Hive metastore schema 中定義的那些字段.
只出如今 Parquet schema 中的任何字段將被 dropped (刪除)在 reconciled schema 中.
僅在 Hive metastore schema 中出現的任何字段在 reconciled schema 中做爲 nullable field (可空字段)添加.
Metadata Refreshing (元數據刷新)
Spark SQL 緩存 Parquet metadata 以得到更好的性能. 當啓用 Hive metastore Parquet table conversion (轉換)時, 這些 converted tables (轉換表)的 metadata (元數據)也被 cached (緩存). 若是這些表由 Hive 或其餘外部工具更新, 則須要手動刷新以確保 consistent metadata (一致的元數據).
// spark is an existing SparkSessionspark.catalog.refreshTable("my_table")
Configuration (配置)
能夠使用SparkSession上的setConf方法或使用 SQL 運行SET key = value命令來完成 Parquet 的配置.
Property Name (參數名稱)Default(默認)Meaning(含義)
spark.sql.parquet.binaryAsStringfalse一些其餘 Parquet-producing systems (Parquet 生產系統), 特別是 Impala, Hive 和舊版本的 Spark SQL , 在 writing out (寫出) Parquet schema 時, 不區分 binary data (二進制數據)和 strings (字符串). 該 flag 告訴 Spark SQL 將 binary data (二進制數據)解釋爲 string (字符串)以提供與這些系統的兼容性.
spark.sql.parquet.int96AsTimestamptrue一些 Parquet-producing systems , 特別是 Impala 和 Hive , 將 Timestamp 存入INT96 . 該 flag 告訴 Spark SQL 將 INT96 數據解析爲 timestamp 以提供與這些系統的兼容性.
spark.sql.parquet.cacheMetadatatrue打開 Parquet schema metadata 的緩存. 能夠加快查詢靜態數據.
spark.sql.parquet.compression.codecsnappy在編寫 Parquet 文件時設置 compression codec (壓縮編解碼器)的使用. 可接受的值包括: uncompressed, snappy, gzip, lzo .
spark.sql.parquet.filterPushdowntrue設置爲 true 時啓用 Parquet filter push-down optimization .
spark.sql.hive.convertMetastoreParquettrue當設置爲 false 時, Spark SQL 將使用 Hive SerDe 做爲 parquet tables , 而不是內置的支持.
spark.sql.parquet.mergeSchemafalse當爲 true 時, Parquet data source (Parquet 數據源) merges (合併)從全部 data files (數據文件)收集的 schemas , 不然若是沒有可用的 summary file , 則從 summary file 或 random data file 中挑選 schema .
spark.sql.optimizer.metadataOnlytrue若是爲 true , 則啓用使用表的 metadata 的 metadata-only query optimization 來生成 partition columns (分區列)而不是 table scans (表掃描). 當 scanned (掃描)的全部 columns (列)都是 partition columns (分區列)而且 query (查詢)具備知足 distinct semantics (不一樣語義)的 aggregate operator (聚合運算符)時, 它將適用.
JSON Datasets (JSON 數據集)
Spark SQL 能夠 automatically infer (自動推斷)JSON dataset 的 schema, 並將其做爲Dataset[Row]加載. 這個 conversion (轉換)能夠在Dataset[String]上使用SparkSession.read.json()來完成, 或 JSON 文件.
請注意, 以a json file提供的文件不是典型的 JSON 文件. 每行必須包含一個 separate (單獨的), self-contained valid (獨立的有效的)JSON 對象. 有關更多信息, 請參閱JSON Lines text format, also called newline-delimited JSON.
對於 regular multi-line JSON file (常規的多行 JSON 文件), 將multiLine選項設置爲true.
// Primitive types (Int, String, etc) and Product types (case classes) encoders are// supported by importing this when creating a Dataset.importspark.implicits._// A JSON dataset is pointed to by path.// The path can be either a single text file or a directory storing text filesvalpath="examples/src/main/resources/people.json"valpeopleDF=spark.read.json(path)// The inferred schema can be visualized using the printSchema() methodpeopleDF.printSchema()// root// |-- age: long (nullable = true)// |-- name: string (nullable = true)// Creates a temporary view using the DataFramepeopleDF.createOrReplaceTempView("people")// SQL statements can be run by using the sql methods provided by sparkvalteenagerNamesDF=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// a Dataset[String] storing one JSON object per stringvalotherPeopleDataset=spark.createDataset("""{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}"""::Nil)valotherPeople=spark.read.json(otherPeopleDataset)otherPeople.show()// +---------------+----+// | address|name|// +---------------+----+// |[Columbus,Ohio]| Yin|// +---------------+----+
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
Hive 表
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 支持實例化SparkSession,包括鏈接到持續的 Hive 轉移,支持 Hive serdes 和 Hive 用戶定義的功能。 沒有現有 Hive 部署的用戶仍然能夠啓用 Hive 支持。 當hive-site.xml未配置時,上下文會自動在當前目錄中建立metastore_db,並建立由spark.sql.warehouse.dir配置的目錄,該目錄默認爲Spark應用程序當前目錄中的spark-warehouse目錄 開始了 請注意,自從2.0.0以來,hive-site.xml中的hive.metastore.warehouse.dir屬性已被棄用。 而是使用spark.sql.warehouse.dir來指定倉庫中數據庫的默認位置。 您可能須要向啓動 Spark 應用程序的用戶授予寫權限。å
importjava.io.Fileimportorg.apache.spark.sql.Rowimportorg.apache.spark.sql.SparkSessioncaseclassRecord(key:Int,value:String)// warehouseLocation points to the default location for managed databases and tablesvalwarehouseLocation=newFile("spark-warehouse").getAbsolutePathvalspark=SparkSession.builder().appName("Spark Hive Example").config("spark.sql.warehouse.dir",warehouseLocation).enableHiveSupport().getOrCreate()importspark.implicits._importspark.sqlsql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING) USING hive")sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src")// Queries are expressed in HiveQLsql("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.valsqlDF=sql("SELECT key, value FROM src WHERE key < 10 ORDER BY key")// The items in DataFrames are of type Row, which allows you to access each column by ordinal.valstringsDS=sqlDF.map{caseRow(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.valrecordsDF=spark.createDataFrame((1to100).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|// ...
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/hive/SparkHiveExample.scala" in the Spark repo.
指定 Hive 表的存儲格式
建立 Hive 表時,須要定義如何 從/向 文件系統 read/write 數據,即 「輸入格式」 和 「輸出格式」。 您還須要定義該表如何將數據反序列化爲行,或將行序列化爲數據,即 「serde」。 如下選項可用於指定存儲格式 (「serde」, 「input format」, 「output format」),例如,CREATE TABLE src(id int) USING hive OPTIONS(fileFormat 'parquet')。 默認狀況下,咱們將以純文本形式讀取表格文件。 請注意,Hive 存儲處理程序在建立表時不受支持,您能夠使用 Hive 端的存儲處理程序建立一個表,並使用 Spark SQL 來讀取它。
Property NameMeaning
fileFormatfileFormat是一種存儲格式規範的包,包括 "serde","input format" 和 "output format"。 目前咱們支持6個文件格式:'sequencefile','rcfile','orc','parquet','textfile'和'avro'。
inputFormat, outputFormat這兩個選項將相應的 "InputFormat" 和 "OutputFormat" 類的名稱指定爲字符串文字,例如: `org.apache.hadoop.hive.ql.io.orc.OrcInputFormat`。 這兩個選項必須成對出現,若是您已經指定了 "fileFormat" 選項,則沒法指定它們。
serde此選項指定 serde 類的名稱。 當指定 `fileFormat` 選項時,若是給定的 `fileFormat` 已經包含 serde 的信息,那麼不要指定這個選項。 目前的 "sequencefile", "textfile" 和 "rcfile" 不包含 serde 信息,你能夠使用這3個文件格式的這個選項。
fieldDelim, escapeDelim, collectionDelim, mapkeyDelim, lineDelim這些選項只能與 "textfile" 文件格式一塊兒使用。它們定義如何將分隔的文件讀入行。
使用OPTIONS定義的全部其餘屬性將被視爲 Hive serde 屬性。
與不一樣版本的 Hive Metastore 進行交互
Spark SQL 的 Hive 支持的最重要的部分之一是與 Hive metastore 進行交互,這使得 Spark SQL 可以訪問 Hive 表的元數據。 從 Spark 1.4.0 開始,使用 Spark SQL 的單一二進制構建能夠使用下面所述的配置來查詢不一樣版本的 Hive 轉移。 請注意,獨立於用於與轉移點通訊的 Hive 版本,內部 Spark SQL 將針對 Hive 1.2.1 進行編譯,並使用這些類進行內部執行(serdes,UDF,UDAF等)。
如下選項可用於配置用於檢索元數據的 Hive 版本:
屬性名稱默認值含義
spark.sql.hive.metastore.version1.2.1Hive metastore 版本。 可用選項爲0.12.0至1.2.1。
spark.sql.hive.metastore.jarsbuiltin當啓用-Phive時,使用 Hive 1.2.1,它與 Spark 程序集捆綁在一塊兒。選擇此選項時,spark.sql.hive.metastore.version 必須爲1.2.1或未定義。 行家 使用從Maven存儲庫下載的指定版本的Hive jar。 一般不建議在生產部署中使用此配置。 ***** 應用於實例化 HiveMetastoreClient 的 jar 的位置。該屬性能夠是三個選項之一:
builtin當啓用-Phive時,使用 Hive 1.2.1,它與 Spark 程序集捆綁在一塊兒。選擇此選項時,spark.sql.hive.metastore.version必須爲1.2.1或未定義。
maven使用從 Maven 存儲庫下載的指定版本的 Hive jar。一般不建議在生產部署中使用此配置。
JVM 的標準格式的 classpath。 該類路徑必須包含全部 Hive 及其依賴項,包括正確版本的 Hadoop。這些罐只須要存在於 driver 程序中,但若是您正在運行在 yarn 集羣模式,那麼您必須確保它們與應用程序一塊兒打包。
spark.sql.hive.metastore.sharedPrefixescom.mysql.jdbc,
org.postgresql,
com.microsoft.sqlserver,
oracle.jdbc使用逗號分隔的類前綴列表,應使用在 Spark SQL 和特定版本的 Hive 之間共享的類加載器來加載。 一個共享類的示例就是用來訪問 Hive metastore 的 JDBC driver。 其它須要共享的類,是須要與已經共享的類進行交互的。 例如,log4j 使用的自定義 appender。
spark.sql.hive.metastore.barrierPrefixes(empty)一個逗號分隔的類前綴列表,應該明確地爲 Spark SQL 正在通訊的 Hive 的每一個版本從新加載。 例如,在一般將被共享的前綴中聲明的 Hive UDF (即: �org.apache.spark.*)。
JDBC 鏈接其它數據庫
Spark SQL 還包括能夠使用 JDBC 從其餘數據庫讀取數據的數據源。此功能應優於使用JdbcRDD。 這是由於結果做爲 DataFrame 返回,而且能夠輕鬆地在 Spark SQL 中處理或與其餘數據源鏈接。 JDBC 數據源也更容易從 Java 或 Python 使用,由於它不須要用戶提供 ClassTag。(請注意,這不一樣於 Spark SQL JDBC 服務器,容許其餘應用程序使用 Spark SQL 運行查詢)。
要開始使用,您須要在 Spark 類路徑中包含特定數據庫的 JDBC driver 程序。 例如,要從 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 還支持如下不區分大小寫的選項:
�屬性名稱含義
url要鏈接的JDBC URL。 源特定的鏈接屬性能夠在URL中指定。 例如jdbc:jdbc:postgresql://localhost/test?user=fred&password=secret
dbtable應該讀取的 JDBC 表。請注意,能夠使用在SQL查詢的FROM子句中有效的任何內容。 例如,您能夠使用括號中的子查詢代替完整表。
driver用於鏈接到此 URL 的 JDBC driver 程序的類名。
partitionColumn, lowerBound, upperBound若是指定了這些選項,則必須指定這些選項。 另外,必須指定numPartitions. 他們描述如何從多個 worker 並行讀取數據時將表給分區。partitionColumn必須是有問題的表中的數字列。 請注意,lowerBound和upperBound僅用於決定分區的大小,而不是用於過濾表中的行。 所以,表中的全部行將被分區並返回。此選項僅適用於讀操做。
numPartitions在表讀寫中能夠用於並行度的最大分區數。這也肯定併發JDBC鏈接的最大數量。 若是要寫入的分區數超過此限制,則在寫入以前經過調用coalesce(numPartitions)將其減小到此限制。
fetchsizeJDBC 抓取的大小,用於肯定每次數據往返傳遞的行數。 這有利於提高 JDBC driver 的性能,它們的默認值較小(例如: Oracle 是 10 行)。 該選項僅適用於讀取操做。
batchsizeJDBC 批處理的大小,用於肯定每次數據往返傳遞的行數。 這有利於提高 JDBC driver 的性能。 該選項僅適用於寫操做。默認值爲1000.
isolationLevel事務隔離級別,適用於當前鏈接。 它能夠是NONE,READ_COMMITTED,READ_UNCOMMITTED,REPEATABLE_READ, 或SERIALIZABLE之一,對應於 JDBC 鏈接對象定義的標準事務隔離級別,默認爲READ_UNCOMMITTED。 此選項僅適用於寫操做。請參考java.sql.Connection中的文檔。
truncate這是一個與 JDBC 相關的選項。 啓用SaveMode.Overwrite時,此選項會致使 Spark 截斷現有表,而不是刪除並從新建立。 這能夠更有效,而且防止表元數據(例如,索引)被移除。 可是,在某些狀況下,例如當新數據具備不一樣的模式時,它將沒法工做。 它默認爲false。 此選項僅適用於寫操做。
createTableOptions這是一個與JDBC相關的選項。 若是指定,此選項容許在建立表時設置特定於數據庫的表和分區選項(例如:CREATE TABLE t (name string) ENGINE=InnoDB.)。此選項僅適用於寫操做。
createTableColumnTypes使用數據庫列數據類型而不是默認值,建立表時。 數據類型信息應以與 CREATE TABLE 列語法相同的格式指定(例如:"name CHAR(64), comments VARCHAR(1024)")。 指定的類型應該是有效的 spark sql 數據類型。此選項僅適用於寫操做。
// Note: JDBC loading and saving can be achieved via either the load/save or jdbc methods// Loading data from a JDBC sourcevaljdbcDF=spark.read.format("jdbc").option("url","jdbc:postgresql:dbserver").option("dbtable","schema.tablename").option("user","username").option("password","password").load()valconnectionProperties=newProperties()connectionProperties.put("user","username")connectionProperties.put("password","password")valjdbcDF2=spark.read.jdbc("jdbc:postgresql:dbserver","schema.tablename",connectionProperties)// Saving data to a JDBC sourcejdbcDF.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)// Specifying create table column data types on writejdbcDF.write.option("createTableColumnTypes","name CHAR(64), comments VARCHAR(1024)").jdbc("jdbc:postgresql:dbserver","schema.tablename",connectionProperties)
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.
故障排除
JDBC driver 程序類必須對客戶端會話和全部執行程序上的原始類加載器可見。 這是由於 Java 的 DriverManager 類執行安全檢查,致使它忽略原始類加載器不可見的全部 driver 程序,當打開鏈接時。一個方便的方法是修改全部工做節點上的compute_classpath.sh 以包含您的 driver 程序 JAR。
一些數據庫,例如 H2,將全部名稱轉換爲大寫。 您須要使用大寫字母來引用 Spark SQL 中的這些名稱。
性能調優
對於某些工做負載,能夠經過緩存內存中的數據或打開一些實驗選項來提升性能。
在內存中緩存數據
Spark SQL 能夠經過調用spark.catalog.cacheTable("tableName")或dataFrame.cache()來使用內存中的列格式來緩存表。 而後,Spark SQL 將只掃描所需的列,並將自動調整壓縮以最小化內存使用量和 GC 壓力。 您能夠調用spark.catalog.uncacheTable("tableName")從內存中刪除該表。
內存緩存的配置能夠使用SparkSession上的setConf方法或使用 SQL 運行SET key=value命令來完成。
屬性名稱默認含義
spark.sql.inMemoryColumnarStorage.compressedtrue當設置爲 true 時,Spark SQL 將根據數據的統計信息爲每一個列自動選擇一個壓縮編解碼器。
spark.sql.inMemoryColumnarStorage.batchSize10000控制批量的柱狀緩存的大小。更大的批量大小能夠提升內存利用率和壓縮率,可是在緩存數據時會冒出 OOM 風險。
其餘配置選項
如下選項也可用於調整查詢執行的性能。這些選項可能會在未來的版本中被廢棄,由於更多的優化是自動執行的。
屬性名稱默認值含義
spark.sql.files.maxPartitionBytes134217728 (128 MB)在讀取文件時,將單個分區打包的最大字節數。
spark.sql.files.openCostInBytes4194304 (4 MB)按照字節數來衡量的打開文件的估計費用能夠在同一時間進行掃描。 將多個文件放入分區時使用。最好過分估計,那麼具備小文件的分區將比具備較大文件的分區(首先計劃的)更快。
spark.sql.broadcastTimeout300廣播鏈接中的廣播等待時間超時(秒)
spark.sql.autoBroadcastJoinThreshold10485760 (10 MB)配置執行鏈接時將廣播給全部工做節點的表的最大大小(以字節爲單位)。 經過將此值設置爲-1能夠禁用廣播。 請注意,目前的統計信息僅支持 Hive Metastore 表,其中已運行命令ANALYZE TABLE COMPUTE STATISTICS noscan。
spark.sql.shuffle.partitions200Configures the number of partitions to use when shuffling data for joins or aggregations.
分佈式 SQL 引擎
Spark SQL 也能夠充當使用其 JDBC/ODBC 或命令行界面的分佈式查詢引擎。 在這種模式下,最終用戶或應用程序能夠直接與 Spark SQL 交互運行 SQL 查詢,而不須要編寫任何代碼。
運行 Thrift JDBC/ODBC 服務器
這裏實現的 Thrift JDBC/ODBC 服務器對應於 Hive 1.2 中的HiveServer2。 您能夠使用 Spark 或 Hive 1.2.1 附帶的直線腳本測試 JDBC 服務器。
要啓動 JDBC/ODBC 服務器,請在 Spark 目錄中運行如下命令:
./sbin/start-thriftserver.sh
此腳本接受全部bin/spark-submit命令行選項,以及--hiveconf選項來指定 Hive 屬性。 您能夠運行./sbin/start-thriftserver.sh --help查看全部可用選項的完整列表。 默認狀況下,服務器監聽 localhost:10000. 您能夠經過環境變量覆蓋此行爲,即:
exportHIVE_SERVER2_THRIFT_PORT=exportHIVE_SERVER2_THRIFT_BIND_HOST=./sbin/start-thriftserver.sh\--master \...
or system properties:
./sbin/start-thriftserver.sh\--hiveconf hive.server2.thrift.port=\--hiveconf hive.server2.thrift.bind.host=\--master ...
如今,您能夠使用 beeline 來測試 Thrift JDBC/ODBC 服務器:
./bin/beeline
使用 beeline 方式鏈接到 JDBC/ODBC 服務器:
beeline> !connect jdbc:hive2://localhost:10000
Beeline 將要求您輸入用戶名和密碼。 在非安全模式下,只需輸入機器上的用戶名和空白密碼便可。 對於安全模式,請按照beeline 文檔中的說明進行操做。
配置Hive是經過將hive-site.xml,core-site.xml和hdfs-site.xml文件放在conf/中完成的。
您也能夠使用 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 to listen on; default is 10001
hive.server2.http.endpoint - HTTP endpoint; default is cliservice
要測試,請使用 beeline 以 http 模式鏈接到 JDBC/ODBC 服務器:
beeline> !connect jdbc:hive2://:/?hive.server2.transport.mode=http;hive.server2.thrift.http.path=
運行 Spark SQL CLI
Spark SQL CLI 是在本地模式下運行 Hive 轉移服務並執行從命令行輸入的查詢的方便工具。 請注意,Spark SQL CLI 不能與 Thrift JDBC 服務器通訊。
要啓動 Spark SQL CLI,請在 Spark 目錄中運行如下命令:
./bin/spark-sql
配置 Hive 是經過將hive-site.xml,core-site.xml和hdfs-site.xml文件放在conf/中完成的。 您能夠運行./bin/spark-sql --help獲取全部可用選項的完整列表。
遷移指南
從 Spark SQL 2.1 升級到 2.2
Spark 2.1.1 介紹了一個新的配置 key:spark.sql.hive.caseSensitiveInferenceMode. 它的默認設置是NEVER_INFER, 其行爲與 2.1.0 保持一致. 可是,Spark 2.2.0 將此設置的默認值更改成 「INFER_AND_SAVE」,以恢復與底層文件 schema(模式)具備大小寫混合的列名稱的 Hive metastore 表的兼容性。使用INFER_AND_SAVE配置的 value, 在第一次訪問 Spark 將對其還沒有保存推測 schema(模式)的任何 Hive metastore 表執行 schema inference(模式推斷). 請注意,對於具備數千個 partitions(分區)的表,模式推斷多是很是耗時的操做。若是不兼容大小寫混合的列名,您能夠安全地將spark.sql.hive.caseSensitiveInferenceMode設置爲NEVER_INFER,以免模式推斷的初始開銷。請注意,使用新的默認INFER_AND_SAVE設置,模式推理的結果被保存爲 metastore key 以供未來使用。所以,初始模式推斷僅發生在表的第一次訪問。
從 Spark SQL 2.0 升級到 2.1
Datasource tables(數據源表)如今存儲了 Hive metastore 中的 partition metadata(分區元數據). 這意味着諸如ALTER TABLE PARTITION ... SET LOCATION這樣的 Hive DDLs 如今使用 Datasource API 可用於建立 tables(表).
遺留的數據源表能夠經過MSCK REPAIR TABLE命令遷移到這種格式。建議遷移遺留表利用 Hive DDL 的支持和提供的計劃性能。
要肯定表是否已遷移,當在表上發出DESCRIBE FORMATTED命令時請查找PartitionProvider: Catalog屬性.
Datasource tables(數據源表)的INSERT OVERWRITE TABLE ... PARTITION ...行爲的更改。
在之前的 Spark 版本中,INSERT OVERWRITE覆蓋了整個 Datasource table,即便給出一個指定的 partition. 如今只有匹配規範的 partition 被覆蓋。
請注意,這仍然與 Hive 表的行爲不一樣,Hive 表僅覆蓋與新插入數據重疊的分區。
從 Spark SQL 1.6 升級到 2.0
SparkSession如今是 Spark 新的切入點, 它替代了老的SQLContext和HiveContext。注意 : 爲了向下兼容,老的 SQLContext 和 HiveContext 仍然保留。能夠從SparkSession獲取一個新的catalog接口 — 現有的訪問數據庫和表的 API,如listTables,createExternalTable,dropTempView,cacheTable都被移到該接口。
Dataset API 和 DataFrame API 進行了統一。在 Scala 中,DataFrame變成了Dataset[Row]類型的一個別名,而 Java API 使用者必須將DataFrame替換成Dataset。Dataset 類既提供了強類型轉換操做(如map,filter以及groupByKey)也提供了非強類型轉換操做(如select和groupBy)。因爲編譯期的類型安全不是 Python 和 R 語言的一個特性,Dataset 的概念並不適用於這些語言的 API。相反,DataFrame仍然是最基本的編程抽象, 就相似於這些語言中單節點 data frame 的概念。
Dataset 和 DataFrame API 中 unionAll 已通過時而且由union替代。
Dataset 和 DataFrame API 中 explode 已通過時,做爲選擇,能夠結合 select 或 flatMap 使用functions.explode()。
Dataset 和 DataFrame API 中registerTempTable已通過時而且由createOrReplaceTempView替代。
對 Hive tablesCREATE TABLE ... LOCATION行爲的更改.
從 Spark 2.0 開始,CREATE TABLE ... LOCATION與CREATE EXTERNAL TABLE ... LOCATION是相同的,以防止意外丟棄用戶提供的 locations(位置)中的現有數據。這意味着,在用戶指定位置的 Spark SQL 中建立的 Hive 表始終是 Hive 外部表。刪除外部表將不會刪除數據。 用戶不能指定 Hive managed tables(管理表)的位置. 請注意,這與Hive行爲不一樣。
所以,這些表上的 「DROP TABLE」 語句不會刪除數據。
從 Spark SQL 1.5 升級到 1.6
從 Spark 1.6 開始,默認狀況下服務器在多 session(會話)模式下運行。這意味着每一個 JDBC/ODBC 鏈接擁有一份本身的 SQL 配置和臨時函數註冊。緩存表仍在並共享。若是您但願以舊的單會話模式運行 Thrift server,請設置選項spark.sql.hive.thriftServer.singleSession爲true。您既能夠將此選項添加到spark-defaults.conf,或者經過--conf將它傳遞給start-thriftserver.sh。
./sbin/start-thriftserver.sh\--conf spark.sql.hive.thriftServer.singleSession=true\...
從 1.6.1 開始,在 sparkR 中 withColumn 方法支持添加一個新列或更換 DataFrame 同名的現有列。
從 Spark 1.6 開始,LongType 強制轉換爲 TimestampType 指望是秒,而不是微秒。這種更改是爲了匹配 Hive 1.2 的行爲,以便從 numeric(數值)類型進行更一致的類型轉換到 TimestampType。更多詳情請參閱SPARK-11724。
從 Spark SQL 1.4 升級到 1.5
使用手動管理的內存優化執行,如今是默認啓用的,以及代碼生成表達式求值。這些功能既能夠經過設置spark.sql.tungsten.enabled爲false來禁止使用。
Parquet 的模式合併默認狀況下再也不啓用。它能夠經過設置spark.sql.parquet.mergeSchema到true以從新啓用。
字符串在 Python 列的 columns(列)如今支持使用點(.)來限定列或訪問嵌套值。例如df['table.column.nestedField']。可是,這意味着若是你的列名中包含任何圓點,你如今必須避免使用反引號(如table.column.with.dots.nested)。
在內存中的列存儲分區修剪默認是開啓的。它能夠經過設置spark.sql.inMemoryColumnarStorage.partitionPruning爲false來禁用。
無限精度的小數列再也不支持,而不是 Spark SQL 最大精度爲 38 。當從BigDecimal對象推斷模式時,如今使用(38,18)。在 DDL 沒有指定精度時,則默認保留Decimal(10, 0)。
時間戳如今存儲在 1 微秒的精度,而不是 1 納秒的。
在 sql 語句中,floating point(浮點數)如今解析爲 decimal。HiveQL 解析保持不變。
SQL / DataFrame 函數的規範名稱如今是小寫(例如 sum vs SUM)。
JSON 數據源不會自動加載由其餘應用程序(未經過 Spark SQL 插入到數據集的文件)建立的新文件。對於 JSON 持久表(即表的元數據存儲在 Hive Metastore),用戶能夠使用REFRESH TABLESQL 命令或HiveContext的refreshTable方法,把那些新文件列入到表中。對於表明一個 JSON dataset 的 DataFrame,用戶須要從新建立 DataFrame,同時 DataFrame 中將包括新的文件。
PySpark 中 DataFrame 的 withColumn 方法支持添加新的列或替換現有的同名列。
從 Spark SQL 1.3 升級到 1.4
DataFrame data reader/writer interface
基於用戶反饋,咱們建立了一個新的更流暢的 API,用於讀取 (SQLContext.read) 中的數據並寫入數據 (DataFrame.write), 而且舊的 API 將過期(例如,SQLContext.parquetFile,SQLContext.jsonFile).
針對SQLContext.read(Scala,Java,Python) 和DataFrame.write(Scala,Java,Python) 的更多細節,請看 API 文檔.
DataFrame.groupBy 保留 grouping columns(分組的列)
根據用戶的反饋, 咱們更改了DataFrame.groupBy().agg()的默認行爲以保留DataFrame結果中的 grouping columns(分組列). 爲了在 1.3 中保持該行爲,請設置spark.sql.retainGroupColumns爲false.
// In 1.3.x, in order for the grouping column "department" to show up,// it must be included explicitly as part of the agg function call.df.groupBy("department").agg($"department",max("age"),sum("expense"))// In 1.4+, grouping column "department" is included automatically.df.groupBy("department").agg(max("age"),sum("expense"))// Revert to 1.3 behavior (not retaining grouping column) by:sqlContext.setConf("spark.sql.retainGroupColumns","false")
DataFrame.withColumn 上的行爲更改
以前 1.4 版本中,DataFrame.withColumn() 只支持添加列。該列將始終在 DateFrame 結果中被加入做爲新的列,即便現有的列可能存在相同的名稱。從 1.4 版本開始,DataFrame.withColumn() 支持添加與全部現有列的名稱不一樣的列或替換現有的同名列。
請注意,這一變化僅適用於 Scala API,並不適用於 PySpark 和 SparkR。
從 Spark SQL 1.0-1.2 升級到 1.3
在 Spark 1.3 中,咱們從 Spark SQL 中刪除了 「Alpha」 的標籤,做爲一部分已經清理過的可用的 API 。從 Spark 1.3 版本以上,Spark SQL 將提供在 1.X 系列的其餘版本的二進制兼容性。這種兼容性保證不包括被明確標記爲不穩定的(即 DeveloperApi 類或 Experimental) API。
重命名 DataFrame 的 SchemaRDD
升級到 Spark SQL 1.3 版本時,用戶會發現最大的變化是,SchemaRDD已改名爲DataFrame。這主要是由於 DataFrames 再也不從 RDD 直接繼承,而是由 RDDS 本身來實現這些功能。DataFrames 仍然能夠經過調用.rdd方法轉換爲 RDDS 。
在 Scala 中,有一個從SchemaRDD到DataFrame類型別名,能夠爲一些狀況提供源代碼兼容性。它仍然建議用戶更新他們的代碼以使用DataFrame來代替。Java 和 Python 用戶須要更新他們的代碼。
Java 和 Scala APIs 的統一
此前 Spark 1.3 有單獨的Java兼容類(JavaSQLContext和JavaSchemaRDD),借鑑於 Scala API。在 Spark 1.3 中,Java API 和 Scala API 已經統一。兩種語言的用戶能夠使用SQLContext和DataFrame。通常來講論文類嘗試使用兩種語言的共有類型(如Array替代了一些特定集合)。在某些狀況下不通用的類型狀況下,(例如,passing in closures 或 Maps)使用函數重載代替。
此外,該 Java 的特定類型的 API 已被刪除。Scala 和 Java 的用戶能夠使用存在於org.apache.spark.sql.types類來描述編程模式。
隔離隱式轉換和刪除 dsl 包(僅Scala)
許多 Spark 1.3 版本之前的代碼示例都以import sqlContext._開始,這提供了從 sqlContext 範圍的全部功能。在 Spark 1.3 中,咱們移除了從RDDs 到DateFrame再到SQLContext內部對象的隱式轉換。用戶如今應該寫成import sqlContext.implicits._.
此外,隱式轉換如今只能使用方法toDF來增長由Product(即 case classes or tuples)構成的RDD,而不是自動應用。
當使用 DSL 內部的函數時(如今使用DataFrameAPI 來替換), 用戶習慣導入org.apache.spark.sql.catalyst.dsl. 相反,應該使用公共的 dataframe 函數 API:import org.apache.spark.sql.functions._.
針對 DataType 刪除在 org.apache.spark.sql 包中的一些類型別名(僅限於 Scala)
Spark 1.3 移除存在於基本 SQL 包的DataType類型別名。開發人員應改成導入類org.apache.spark.sql.types。
UDF 註冊遷移到sqlContext.udf中 (Java & Scala)
用於註冊 UDF 的函數,無論是 DataFrame DSL 仍是 SQL 中用到的,都被遷移到SQLContext中的 udf 對象中。
sqlContext.udf.register("strLen",(s:String)=>s.length())
Python UDF 註冊保持不變。
Python DataTypes 再也不是 Singletons(單例的)
在 Python 中使用 DataTypes 時,你須要先構造它們(如:StringType()),而不是引用一個單例對象。
與 Apache Hive 的兼容
Spark SQL 在設計時就考慮到了和 Hive metastore,SerDes 以及 UDF 之間的兼容性。目前 Hive SerDes 和 UDF 都是基於 Hive 1.2.1 版本,而且Spark SQL 能夠鏈接到不一樣版本的Hive metastore(從 0.12.0 到 1.2.1,能夠參考與不一樣版本的 Hive Metastore 交互)
在現有的 Hive Warehouses 中部署
Spark SQL Thrift JDBC server 採用了開箱即用的設計以兼容已有的 Hive 安裝版本。你不須要修改現有的 Hive Metastore , 或者改變數據的位置和表的分區。
所支持的 Hive 特性
Spark SQL 支持絕大部分的 Hive 功能,如:
Hive query(查詢)語句, 包括:
SELECT
GROUP BY
ORDER BY
CLUSTER BY
SORT BY
全部 Hive 操做, 包括:
關係運算符 (=,⇔,==,<>,<,>,>=,<=, 等等)
算術運算符 (+,-,*,/,%, 等等)
邏輯運算符 (AND,&&,OR,||, 等等)
複雜類型的構造
數學函數 (sign,ln,cos, 等等)
String 函數 (instr,length,printf, 等等)
用戶定義函數 (UDF)
用戶定義聚合函數 (UDAF)
用戶定義 serialization formats (SerDes)
窗口函數
Joins
JOIN
{LEFT|RIGHT|FULL} OUTER JOIN
LEFT SEMI JOIN
CROSS JOIN
Unions
Sub-queries(子查詢)
SELECT col FROM ( SELECT a + b AS col from t1) t2
Sampling
Explain
Partitioned tables including dynamic partition insertion
View
全部的 Hive DDL 函數, 包括:
CREATE TABLE
CREATE TABLE AS SELECT
ALTER TABLE
大部分的 Hive Data types(數據類型), 包括:
TINYINT
SMALLINT
INT
BIGINT
BOOLEAN
FLOAT
DOUBLE
STRING
BINARY
TIMESTAMP
DATE
ARRAY<>
MAP<>
STRUCT<>
未支持的 Hive 函數
如下是目前還不支持的 Hive 函數列表。在 Hive 部署中這些功能大部分都用不到。
主要的 Hive 功能
Tables 使用 buckets 的 Tables: bucket 是 Hive table partition 中的 hash partitioning. Spark SQL 還不支持 buckets.
Esoteric Hive 功能
UNION類型
Unique join
Column 統計信息的收集: Spark SQL does not piggyback scans to collect column statistics at the moment and only supports populating the sizeInBytes field of the hive metastore.
Hive Input/Output Formats
File format for CLI: For results showing back to the CLI, Spark SQL only supports TextOutputFormat.
Hadoop archive
Hive 優化
有少數 Hive 優化尚未包含在 Spark 中。其中一些(好比 indexes 索引)因爲 Spark SQL 的這種內存計算模型而顯得不那麼重要。另一些在 Spark SQL 將來的版本中會持續跟蹤。
Block 級別的 bitmap indexes 和虛擬 columns (用於構建 indexes)
自動爲 join 和 groupBy 計算 reducer 個數 : 目前在 Spark SQL 中, 你須要使用 「SET spark.sql.shuffle.partitions=[num_tasks];」 來控制 post-shuffle 的並行度.
僅 Meta-data 的 query: 對於只使用 metadata 就能回答的查詢,Spark SQL 仍然會啓動計算結果的任務.
Skew data flag: Spark SQL 不遵循 Hive 中 skew 數據的標記.
STREAMTABLEhint in join: Spark SQL 不遵循STREAMTABLEhint.
對於查詢結果合併多個小文件: 若是輸出的結果包括多個小文件, Hive 能夠可選的合併小文件到一些大文件中去,以免溢出 HDFS metadata. Spark SQL 還不支持這樣.
參考
數據類型
Spark SQL 和 DataFrames 支持下面的數據類型:
Numeric types
ByteType: Represents 1-byte signed integer numbers. The range of numbers is from-128to127.
ShortType: Represents 2-byte signed integer numbers. The range of numbers is from-32768to32767.
IntegerType: Represents 4-byte signed integer numbers. The range of numbers is from-2147483648to2147483647.
LongType: Represents 8-byte signed integer numbers. The range of numbers is from-9223372036854775808to9223372036854775807.
FloatType: Represents 4-byte single-precision floating point numbers.
DoubleType: Represents 8-byte double-precision floating point numbers.
DecimalType: Represents arbitrary-precision signed decimal numbers. Backed internally byjava.math.BigDecimal. ABigDecimalconsists of an arbitrary precision integer unscaled value and a 32-bit integer scale.
String type
StringType: Represents character string values.
Binary type
BinaryType: Represents byte sequence values.
Boolean type
BooleanType: Represents boolean values.
Datetime type
TimestampType: Represents values comprising values of fields year, month, day, hour, minute, and second.
DateType: Represents values comprising values of fields year, month, day.
Complex types
ArrayType(elementType, containsNull): Represents values comprising a sequence of elements with the type ofelementType.containsNullis used to indicate if elements in aArrayTypevalue can havenullvalues.
MapType(keyType, valueType, valueContainsNull): Represents values comprising a set of key-value pairs. The data type of keys are described bykeyTypeand the data type of values are described byvalueType. For aMapTypevalue, keys are not allowed to havenullvalues.valueContainsNullis used to indicate if values of aMapTypevalue can havenullvalues.
StructType(fields): Represents values with the structure described by a sequence ofStructFields (fields).
StructField(name, dataType, nullable): Represents a field in aStructType. The name of a field is indicated byname. The data type of a field is indicated bydataType.nullableis used to indicate if values of this fields can havenullvalues.
Spark SQL 的全部數據類型都在包org.apache.spark.sql.types中. 你能夠用下示例示例來訪問它們.
importorg.apache.spark.sql.types._
Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.
Data type(數據類型)Scala 中的 Value 類型訪問或建立數據類型的 API
ByteTypeByteByteType
ShortTypeShortShortType
IntegerTypeIntIntegerType
LongTypeLongLongType
FloatTypeFloatFloatType
DoubleTypeDoubleDoubleType
DecimalTypejava.math.BigDecimalDecimalType
StringTypeStringStringType
BinaryTypeArray[Byte]BinaryType
BooleanTypeBooleanBooleanType
TimestampTypejava.sql.TimestampTimestampType
DateTypejava.sql.DateDateType
ArrayTypescala.collection.SeqArrayType(elementType, [containsNull])
Note(注意):containsNull的默認值是true.
MapTypescala.collection.MapMapType(keyType,valueType, [valueContainsNull])
Note(注意):valueContainsNull的默認值是true.
StructTypeorg.apache.spark.sql.RowStructType(fields)
Note(注意):fields是 StructFields 的 Seq. 全部, 兩個 fields 擁有相同的名稱是不被容許的.
StructField該 field(字段)數據類型的 Scala 中的 value 類型 (例如, 數據類型爲 IntegerType 的 StructField 是 Int)StructField(name,dataType, [nullable])
Note:nullable的默認值是true.
NaN Semantics
當處理一些不符合標準浮點數語義的float或double類型時,對於 Not-a-Number(NaN) 須要作一些特殊處理. 具體以下:
NaN = NaN 返回 true.
在 aggregations(聚合)操做中,全部的 NaN values 將被分到同一個組中.
在 join key 中 NaN 能夠當作一個普通的值.
NaN 值在升序排序中排到最後,比任何其餘數值都大.
原文地址: http://spark.apachecn.org/docs/cn/2.2.0/sql-programming-guide.html
網頁地址: http://spark.apachecn.org/
github: https://github.com/apachecn/spark-doc-zh(以爲不錯麻煩給個 Star,謝謝!~)
Joyyx 關注
2017.09.27 10:44* 字數 18441 閱讀 254評論 0喜歡 5
Discretized Streams (DStreams)(離散化流)
Input DStreams 和 Receivers(接收器)
DStreams 上的 Transformations(轉換)
Accumulators, Broadcast 變量, 和 Checkpoint
Monitoring Applications (監控應用程序)
Reducing the Batch Processing Times (減小批處理時間)
Setting the Right Batch Interval (設置正確的批次間隔)
Fault-tolerance Semantics (容錯語義)
概述
Spark Streaming 是 Spark Core API 的擴展, 它支持彈性的, 高吞吐的, 容錯的實時數據流的處理. 數據能夠經過多種數據源獲取, 例如 Kafka, Flume, Kinesis 以及 TCP sockets, 也能夠經過例如map,reduce,join,window等的高級函數組成的複雜算法處理. 最終, 處理後的數據能夠輸出到文件系統, 數據庫以及實時儀表盤中. 事實上, 你還能夠在 data streams(數據流)上使用機器學習以及圖形處理算法.
在內部, 它工做原理以下, Spark Streaming 接收實時輸入數據流並將數據切分紅多個 batch(批)數據, 而後由 Spark 引擎處理它們以生成最終的 stream of results in batches(分批流結果).
Spark Streaming 提供了一個名爲discretized stream或DStream的高級抽象, 它表明一個連續的數據流. DStream 能夠從數據源的輸入數據流建立, 例如 Kafka, Flume 以及 Kinesis, 或者在其餘 DStream 上進行高層次的操做以建立. 在內部, 一個 DStream 是經過一系列的RDDs來表示.
本指南告訴你如何使用 DStream 來編寫一個 Spark Streaming 程序. 你能夠使用 Scala , Java 或者 Python(Spark 1.2 版本後引進)來編寫 Spark Streaming 程序. 全部這些都在本指南中介紹. 您能夠在本指南中找到標籤, 讓您能夠選擇不一樣語言的代碼段.
Note(注意):在 Python 有些 API 可能會有不一樣或不可用. 在本指南, 您將找到Python API的標籤來高亮顯示不一樣的地方.
一個入門示例
在咱們詳細介紹如何編寫你本身的 Spark Streaming 程序的細節以前, 讓咱們先來看一看一個簡單的 Spark Streaming 程序的樣子. 比方說, 咱們想要計算從一個監聽 TCP socket 的數據服務器接收到的文本數據(text data)中的字數. 你須要作的就是照着下面的步驟作.
首先, 咱們導入了 Spark Streaming 類和部分從 StreamingContext 隱式轉換到咱們的環境的名稱, 目的是添加有用的方法到咱們須要的其餘類(如 DStream).StreamingContext是全部流功能的主要入口點. 咱們建立了一個帶有 2 個執行線程和間歇時間爲 1 秒的本地 StreamingContext.
importorg.apache.spark._importorg.apache.spark.streaming._importorg.apache.spark.streaming.StreamingContext._// 自從 Spark 1.3 開始, 再也不是必要的了// 建立一個具備兩個工做線程(working thread)而且批次間隔爲 1 秒的本地 StreamingContext .// master 須要 2 個核, 以防止飢餓狀況(starvation scenario).valconf=newSparkConf().setMaster("local[2]").setAppName("NetworkWordCount")valssc=newStreamingContext(conf,Seconds(1))
Using this context, we can create a DStream that represents streaming data from a TCP source, specified as hostname (e.g.localhost) and port (e.g.9999). 使用該 context, 咱們能夠建立一個表明從 TCP 源流數據的離散流(DStream), 指定主機名(hostname)(例如 localhost)和端口(例如 9999).
// 建立一個將要鏈接到 hostname:port 的 DStream,如 localhost:9999vallines=ssc.socketTextStream("localhost",9999)
上一步的這個linesDStream 表示將要從數據服務器接收到的數據流. 在這個離散流(DStream)中的每一條記錄都是一行文本(text). 接下來,咱們想要經過空格字符(space characters)拆分這些數據行(lines)成單詞(words).
// 將每一行拆分紅 words(單詞)valwords=lines.flatMap(_.split(" "))
flatMap是一種 one-to-many(一對多)的離散流(DStream)操做,它會經過在源離散流(source DStream)中根據每一個記錄(record)生成多個新紀錄的形式建立一個新的離散流(DStream). 在這種狀況下,在這種狀況下,每一行(each line)都將被拆分紅多個單詞(words)和表明單詞離散流(words DStream)的單詞流. 接下來,咱們想要計算這些單詞.
importorg.apache.spark.streaming.StreamingContext._// not necessary since Spark 1.3// 計算每個 batch(批次)中的每個 word(單詞)valpairs=words.map(word=>(word,1))valwordCounts=pairs.reduceByKey(_+_)// 在控制檯打印出在這個離散流(DStream)中生成的每一個 RDD 的前十個元素// 注意: 必須要觸發 action(不少初學者會忘記觸發 action 操做,致使報錯:No output operations registered, so nothing to execute)wordCounts.print()
上一步的wordsDStream 進行了進一步的映射(一對一的轉換)爲一個 (word, 1) paris 的離散流(DStream),這個 DStream 而後被規約(reduce)來得到數據中每一個批次(batch)的單詞頻率. 最後,wordCounts.print()將會打印一些每秒生成的計數.
Note that when these lines are executed, Spark Streaming only sets up the computation it will perform when it is started, and no real processing has started yet. To start the processing after all the transformations have been setup, we finally call
請注意,當這些行(lines)被執行的時候, Spark Streaming 僅僅設置了計算, 只有在啓動時纔會執行,並無開始真正地處理. 爲了在全部的轉換都已經設置好以後開始處理,咱們在最後調用:
ssc.start()// 開始計算ssc.awaitTermination()// 等待計算被中斷
該部分完整的代碼能夠在 Spark Streaming 示例NetworkWordCount中找到.
若是你已經下載而且構建Spark, 您能夠使用以下方式來運行該示例. 你首先須要運行 Netcat(一個在大多數類 Unix 系統中的小工具)做爲咱們使用的數據服務器.
$ nc -lk9999
而後,在另外一個不一樣的終端,你能夠經過執行以下命令來運行該示例:
$ ./bin/run-example streaming.NetworkWordCount localhost9999
而後,在運行在 netcat 服務器上的終端輸入的任何行(lines),都將被計算,而且每一秒都顯示在屏幕上,它看起來就像下面這樣:
# TERMINAL 1:# Running Netcat$ nc -lk9999hello world...
# TERMINAL 2: RUNNING NetworkWordCount$ ./bin/run-example streaming.NetworkWordCount localhost9999...-------------------------------------------Time:1357008430000ms-------------------------------------------(hello,1)(world,1)...
基礎概念
接下來,咱們瞭解完了簡單的例子,開始闡述 Spark Streaming 的基本知識。
依賴
與 Spark 相似,Spark Streaming 能夠經過 Maven 來管理依賴. 爲了編寫你本身的 Spark Streaming 程序,你必須添加如下的依賴到你的 SBT 或者 Maven 項目中.
org.apache.spark
spark-streaming_2.11
2.2.0
針對從 Spark Streaming Core API 中不存在的數據源中獲取數據,如 Kafka, Flume,Kinesis ,你必須添加相應的座標spark-streaming-xyz_2.11到依賴中. 例如,有一些常見的依賴以下.
Source(數據源)Artifact(座標)
Kafkaspark-streaming-kafka-0-8_2.11
Flumespark-streaming-flume_2.11
Kinesis
spark-streaming-kinesis-asl_2.11 [Amazon Software License]
想要查看一個實時更新的列表,請參閱Maven repository來了解支持的 sources(數據源)和 artifacts(座標)的完整列表。
初始化 StreamingContext
爲了初始化一個 Spark Streaming 程序, 一個StreamingContext對象必需要被建立出來,它是全部的 Spark Streaming 功能的主入口點。
一個StreamingContext對象能夠從一個SparkConf對象中來建立.
importorg.apache.spark._importorg.apache.spark.streaming._valconf=newSparkConf().setAppName(appName).setMaster(master)valssc=newStreamingContext(conf,Seconds(1))
這個appName參數是展現在集羣 UI 界面上的應用程序的名稱.master是一個Spark, Mesos or YARN cluster URL, 或者一個特殊的「local[*]」字符串以使用 local mode(本地模式)來運行. 在實踐中,當在集羣上運行時,你不會想在應用程序中硬編碼master,而是使用spark-submit來啓動應用程序, 而且接受該參數. 然而,對於本地測試和單元測試,你能夠傳遞 「local[*]」 來運行 Spark Streaming 進程(檢測本地系統中內核的個數). 請注意,作個內部建立了一個SparkContext(全部 Spark 功能的出發點),它能夠像 ssc.sparkContext 這樣被訪問.
這個 batch interval(批間隔)必須根據您的應用程序和可用的集羣資源的等待時間要求進行設置. 更多詳情請參閱優化指南部分.
一個StreamingContext對象也能夠從一個現有的SparkContext對象來建立.
importorg.apache.spark.streaming._valsc=...// 已存在的 SparkContextvalssc=newStreamingContext(sc,Seconds(1))
在定義一個 context 以後,您必須執行如下操做.
經過建立輸入 DStreams 來定義輸入源.
經過應用轉換和輸出操做 DStreams 定義流計算(streaming computations).
開始接收輸入而且使用streamingContext.start()來處理數據.
使用streamingContext.awaitTermination()等待處理被終止(手動或者因爲任何錯誤).
使用streamingContext.stop()來手動的中止處理.
須要記住的幾點:
一旦一個 context 已經啓動,將不會有新的數據流的計算能夠被建立或者添加到它。.
一旦一個 context 已經中止,它不會被從新啓動.
同一時間內在 JVM 中只有一個 StreamingContext 能夠被激活.
在 StreamingContext 上的 stop() 一樣也中止了 SparkContext 。爲了只中止 StreamingContext ,設置stop()的可選參數,名叫stopSparkContext爲 false.
一個 SparkContext 就能夠被重用以建立多個 StreamingContexts,只要前一個 StreamingContext 在下一個StreamingContext 被建立以前中止(不中止 SparkContext).
Discretized Streams (DStreams)(離散化流)
Discretized StreamorDStream是 Spark Streaming 提供的基本抽象. 它表明了一個連續的數據流, 不管是從 source(數據源)接收到的輸入數據流, 仍是經過轉換輸入流所產生的處理過的數據流. 在內部, 一個 DStream 被表示爲一系列連續的 RDDs, 它是 Spark 中一個不可改變的抽象, distributed dataset (的更多細節請看Spark 編程指南. 在一個 DStream 中的每一個 RDD 包含來自必定的時間間隔的數據,以下圖所示.
應用於 DStream 的任何操做轉化爲對於底層的 RDDs 的操做. 例如,在先前的示例,轉換一個行(lines)流成爲單詞(words)中,flatMap 操做被應用於在行離散流(lines DStream)中的每一個 RDD 來生成單詞離散流(words DStream)的 RDDs . 以下所示.
這些底層的 RDD 變換由 Spark 引擎(engine)計算。 DStream 操做隱藏了大多數這些細節併爲了方便起見,提供給了開發者一個更高級別的 API 。這些操做細節會在後邊的章節中討論。
Input DStreams 和 Receivers(接收器)
輸入 DStreams 是表明輸入數據是從流的源數據(streaming sources)接收到的流的 DStream. 在一個入門示例中,lines是一個 input DStream, 由於它表明着從 netcat 服務器接收到的數據的流. 每個 input DStream(除了 file stream 以外, 會在本章的後面來討論)與一個Receiver(Scala doc,Java doc) 對象關聯, 它從 source(數據源)中獲取數據,而且存儲它到 Sparl 的內存中用於處理.
Spark Streaming 提供了兩種內置的 streaming source(流的數據源).
Basic sources(基礎的數據源): 在 StreamingContext API 中直接能夠使用的數據源. 例如: file systems 和 socket connections.
Advanced sources(高級的數據源): 像 Kafka, Flume, Kinesis, 等等這樣的數據源. 能夠經過額外的 utility classes 來使用. 像在依賴中討論的同樣, 這些都須要額外的外部依賴.
在本節的後邊,咱們將討論每種類別中的現有的一些數據源.
請注意, 若是你想要在你的流處理程序中並行的接收多個數據流, 你能夠建立多個 input DStreams(在性能優化部分進一步討論). 這將建立同時接收多個數據流的多個 receivers(接收器). 但須要注意,一個 Spark 的 worker/executor 是一個長期運行的任務(task),所以它將佔用分配給 Spark Streaming 的應用程序的全部核中的一個核(core). 所以,要記住,一個 Spark Streaming 應用須要分配足夠的核(core)(或線程(threads),若是本地運行的話)來處理所接收的數據,以及來運行接收器(receiver(s)).
要記住的幾點
當在本地運行一個 Spark Streaming 程序的時候,不要使用 「local」 或者 「local[1]」 做爲 master 的 URL. 這兩種方法中的任何一個都意味着只有一個線程將用於運行本地任務. 若是你正在使用一個基於接收器(receiver)的輸入離散流(input DStream)(例如, sockets ,Kafka ,Flume 等),則該單獨的線程將用於運行接收器(receiver),而沒有留下任何的線程用於處理接收到的數據. 所以,在本地運行時,老是用 「local[n]」 做爲 master URL ,其中的 n > 運行接收器的數量(查看Spark 屬性來了解怎樣去設置 master 的信息).
將邏輯擴展到集羣上去運行,分配給 Spark Streaming 應用程序的內核(core)的內核數必須大於接收器(receiver)的數量。不然系統將接收數據,可是沒法處理它.
基礎的 Sources(數據源)
咱們已經簡單地瞭解過了在入門示例中ssc.socketTextStream(...)的例子,例子中是經過從一個 TCP socket 鏈接接收到的文本數據來建立了一個離散流(DStream). 除了 sockets 以外,StreamingContext API 也提供了根據文件做爲輸入來源建立離散流(DStreams)的方法。
File Streams:用於從文件中讀取數據,在任何與 HDFS API 兼容的文件系統中(即,HDFS,S3,NFS 等),一個 DStream 能夠像下面這樣建立:
streamingContext.fileStream[KeyClass, ValueClass, InputFormatClass](dataDirectory)
Spark Streaming 將監控dataDirectory目錄而且該目錄中任何新建的文件 (寫在嵌套目錄中的文件是不支持的). 注意
文件必須具備相同的數據格式.
文件必須被建立在dataDirectory目錄中, 經過 atomically(院子的)moving(移動)或renaming(重命名)它們到數據目錄.
一旦移動,這些文件必須不能再更改,所以若是文件被連續地追加,新的數據將不會被讀取.
對於簡單的文本文件,還有一個更加簡單的方法streamingContext.textFileStream(dataDirectory). 而且文件流(file streams)不須要運行一個接收器(receiver),所以,不須要分配內核(core)。
Python API在 Python API 中fileStream是不可用的, 只有textFileStream是可用的.
Streams based on Custom Receivers(基於自定義的接收器的流):DStreams 能夠使用經過自定義的 receiver(接收器)接收到的數據來建立. 更多細節請參閱自定義 Receiver 指南.
Queue of RDDs as a Stream(RDDs 隊列做爲一個流):爲了使用測試數據測試 Spark Streaming 應用程序,還能夠使用streamingContext.queueStream(queueOfRDDs)建立一個基於 RDDs 隊列的 DStream,每一個進入隊列的 RDD 都將被視爲 DStream 中的一個批次數據,而且就像一個流進行處理.
想要了解更多的關於從 sockets 和文件(files)建立的流的細節, 請參閱相關函數的 API文檔, 它們在StreamingContextfor Scala,JavaStreamingContextfor Java 以及StreamingContextfor Python 中.
高級 Sources(數據源)
Python API從 Spark 2.2.0 開始, 在 Python API 中的 Kafka, Kinesis 和 Flume 這樣的外部數據源都是可用的.
這一類別的 sources(數據源)須要使用非 Spark 庫中的外部接口,它們中的其中一些還須要比較複雜的依賴關係(例如, Kafka 和 Flume). 所以,爲了最小化有關的依賴關係的版本衝突的問題,這些資源自己不能建立 DStream 的功能,它是經過依賴單獨的類庫實現建立 DStream 的功能.
請注意, 這些高級 sources(數據源)不能再 Spark shell 中使用, 所以,基於這些高級 sources(數據源)的應用程序不能在 shell 中被測試. 若是你真的想要在 Spark shell 中使用它們,你必須下載帶有它的依賴的相應的 Maven 組件的 JAR ,而且將其添加到 classpath.
一些高級的 sources(數據源)以下.
Kafka:Spark Streaming 2.2.0 與 Kafka broker 版本 0.8.2.1 或更高是兼容的. 更多細節請參閱Kafka 集成指南.
Flume:Spark Streaming 2.2.0 與 Flume 1.6.0 相兼容. 更多細節請參閱Flume 集成指南.
Kinesis:Spark Streaming 2.2.0 與 Kinesis Client Library 1.2.1 相兼容. 更多細節請參閱Kinesis 集成指南.
自定義 Sources(數據源)
Python API在 Python 中還不支持這一功能.
Input DStreams 也能夠從自定義數據源中建立. 若是您想這樣作, 須要實現一個用戶自定義的receiver(看下一節以瞭解它是什麼), 它能夠從自定義的 sources(數據源)中接收數據而且推送它到 Spark. 更多細節請參閱自定義 Receiver 指南.
Receiver Reliability(接收器的可靠性)
能夠有兩種基於他們的reliability可靠性的數據源. 數據源(如 Kafka 和 Flume)容許傳輸的數據被確認. 若是系統從這些可靠的數據來源接收數據,而且被確認(acknowledges)正確地接收數據,它能夠確保數據不會由於任何類型的失敗而致使數據丟失. 這樣就出現了 2 種接收器(receivers):
Reliable Receiver(可靠的接收器)- 當數據被接收並存儲在 Spark 中並帶有備份副本時,一個可靠的接收器(reliable receiver)正確地發送確認(acknowledgment)給一個可靠的數據源(reliable source).
Unreliable Receiver(不可靠的接收器)- 一個不可靠的接收器( unreliable receiver )不發送確認(acknowledgment)到數據源。這能夠用於不支持確認的數據源,或者甚至是可靠的數據源當你不想或者不須要進行復雜的確認的時候.
在自定義 Receiver 指南中描述了關於如何去編寫一個 reliable receiver(可靠的接收器)的細節.
DStreams 上的 Transformations(轉換)
與 RDD 相似,transformation 容許從 input DStream 輸入的數據作修改. DStreams 支持不少在 RDD 中可用的 transformation 算子。一些經常使用的以下所示 :
與RDD相似,相似,transformation 容許修改來自 input DStream 的數據. DStreams 支持標準的 Spark RDD 上可用的許多轉換. 一些常見的以下.
Transformation(轉換)Meaning(含義)
map(func)利用函數func處理原 DStream 的每一個元素,返回一個新的 DStream.
flatMap(func)與 map 類似,可是每一個輸入項可用被映射爲 0 個或者多個輸出項。.
filter(func)返回一個新的 DStream,它僅僅包含原 DStream 中函數func返回值爲 true 的項.
repartition(numPartitions)經過建立更多或者更少的 partition 以改變這個 DStream 的並行級別(level of parallelism).
union(otherStream)返回一個新的 DStream,它包含源 DStream 和otherDStream的全部元素.
count()經過 count 源 DStream 中每一個 RDD 的元素數量,返回一個包含單元素(single-element)RDDs 的新 DStream.
reduce(func)利用函數func彙集源 DStream 中每一個 RDD 的元素,返回一個包含單元素(single-element)RDDs 的新 DStream。函數應該是相關聯的,以使計算能夠並行化.
countByValue()在元素類型爲 K 的 DStream上,返回一個(K,long)pair 的新的 DStream,每一個 key 的值是在原 DStream 的每一個 RDD 中的次數.
reduceByKey(func, [numTasks])當在一個由 (K,V) pairs 組成的 DStream 上調用這個算子時,返回一個新的, 由 (K,V) pairs 組成的 DStream,每個 key 的值均由給定的 reduce 函數聚合起來.注意:在默認狀況下,這個算子利用了 Spark 默認的併發任務數去分組。你能夠用 numTasks 參數設置不一樣的任務數。
join(otherStream, [numTasks])當應用於兩個 DStream(一個包含(K,V)對,一個包含 (K,W) 對),返回一個包含 (K, (V, W)) 對的新 DStream.
cogroup(otherStream, [numTasks])當應用於兩個 DStream(一個包含(K,V)對,一個包含 (K,W) 對),返回一個包含 (K, Seq[V], Seq[W]) 的 tuples(元組).
transform(func)經過對源 DStream 的每一個 RDD 應用 RDD-to-RDD 函數,建立一個新的 DStream. 這個能夠在 DStream 中的任何 RDD 操做中使用.
updateStateByKey(func)返回一個新的 "狀態" 的 DStream,其中每一個 key 的狀態經過在 key 的先前狀態應用給定的函數和 key 的新 valyes 來更新. 這能夠用於維護每一個 key 的任意狀態數據.
其中一些轉換值得深刻討論.
UpdateStateByKey 操做
該updateStateByKey操做容許您維護任意狀態,同時不斷更新新信息. 你須要經過兩步來使用它.
定義 state - state 能夠是任何的數據類型.
定義 state update function(狀態更新函數) - 使用函數指定如何使用先前狀態來更新狀態,並從輸入流中指定新值.
在每一個 batch 中,Spark 會使用狀態更新函數爲全部已有的 key 更新狀態,無論在 batch 中是否含有新的數據。若是這個更新函數返回一個 none,這個 key-value pair 也會被消除.
讓咱們舉個例子來講明. 在例子中,假設你想保持在文本數據流中看到的每一個單詞的運行計數,運行次數用一個 state 表示,它的類型是整數, 咱們能夠使用以下方式來定義 update 函數:
defupdateFunction(newValues:Seq[Int],runningCount:Option[Int]):Option[Int]={valnewCount=...// add the new values with the previous running count to get the new countSome(newCount)}
這裏是一個應用於包含 words(單詞)的 DStream 上(也就是說,在先前的示例中,該pairsDStream 包含了 (word, 1) pair).
valrunningCounts=pairs.updateStateByKey[Int](updateFunction_)
update 函數將會被每一個單詞調用,newValues擁有一系列的 1(來自 (word, 1) pairs),runningCount 擁有以前的次數.
請注意, 使用updateStateByKey須要配置的checkpoint(檢查點)的目錄,這裏是更詳細關於討論checkpointing的部分.
Transform Operation*(轉換操做)
transform 操做(以及它的變化形式如transformWith)容許在 DStream 運行任何 RDD-to-RDD 函數. 它可以被用來應用任何沒在 DStream API 中提供的 RDD 操做. 例如,鏈接數據流中的每一個批(batch)和另一個數據集的功能並無在 DStream API 中提供,然而你能夠簡單的利用transform方法作到. 這使得有很是強大的可能性. 例如,能夠經過將輸入數據流與預先計算的垃圾郵件信息(也能夠使用 Spark 一塊兒生成)進行實時數據清理,而後根據它進行過濾.
valspamInfoRDD=ssc.sparkContext.newAPIHadoopRDD(...)// RDD containing spam informationvalcleanedDStream=wordCounts.transform{rdd=>rdd.join(spamInfoRDD).filter(...)// join data stream with spam information to do data cleaning...}
請注意,每一個 batch interval(批間隔)提供的函數被調用. 這容許你作隨時間變更的 RDD 操做, 即 RDD 操做, 分區的數量,廣播變量,等等. batch 之間等能夠改變。
Window Operations(窗口操做)
Spark Streaming 也支持windowed computations(窗口計算),它容許你在數據的一個滑動窗口上應用 transformation(轉換). 下圖說明了這個滑動窗口.
如上圖顯示,窗口在源 DStream 上slides(滑動),合併和操做落入窗內的源 RDDs,產生窗口化的 DStream 的 RDDs。在這個具體的例子中,程序在三個時間單元的數據上進行窗口操做,而且每兩個時間單元滑動一次。 這說明,任何一個窗口操做都須要指定兩個參數.
window length(窗口長度)- 窗口的持續時間(圖 3).
sliding interval(滑動間隔)- 執行窗口操做的間隔(圖 2).
這兩個參數必須是 source DStream 的 batch interval(批間隔)的倍數(圖 1).
讓咱們舉例以說明窗口操做. 例如,你想擴展前面的例子用來計算過去 30 秒的詞頻,間隔時間是 10 秒. 爲了達到這個目的,咱們必須在過去 30 秒的(wrod, 1)pairs 的pairsDStream 上應用reduceByKey操做. 用方法reduceByKeyAndWindow實現.
// Reduce last 30 seconds of data, every 10 secondsvalwindowedWordCounts=pairs.reduceByKeyAndWindow((a:Int,b:Int)=>(a+b),Seconds(30),Seconds(10))
一些經常使用的窗口操做以下所示,這些操做都須要用到上文提到的兩個參數 -windowLength(窗口長度)和slideInterval(滑動的時間間隔).
Transformation(轉換)Meaning(含義)
window(windowLength,slideInterval)返回一個新的 DStream, 它是基於 source DStream 的窗口 batch 進行計算的.
countByWindow(windowLength,slideInterval)返回 stream(流)中滑動窗口元素的數
reduceByWindow(func,windowLength,slideInterval)返回一個新的單元素 stream(流),它經過在一個滑動間隔的 stream 中使用func來聚合以建立. 該函數應該是 associative(關聯的)且 commutative(可交換的),以便它能夠並行計算
reduceByKeyAndWindow(func,windowLength,slideInterval, [numTasks])在一個 (K, V) pairs 的 DStream 上調用時, 返回一個新的 (K, V) pairs 的 Stream, 其中的每一個 key 的 values 是在滑動窗口上的 batch 使用給定的函數func來聚合產生的.Note(注意):默認狀況下, 該操做使用 Spark 的默認並行任務數量(local model 是 2, 在 cluster mode 中的數量經過spark.default.parallelism來肯定)來作 grouping. 您能夠經過一個可選的numTasks參數來設置一個不一樣的 tasks(任務)數量.
reduceByKeyAndWindow(func,invFunc,windowLength,slideInterval, [numTasks])上述reduceByKeyAndWindow()的更有效的一個版本,其中使用前一窗口的 reduce 值逐漸計算每一個窗口的 reduce值. 這是經過減小進入滑動窗口的新數據,以及 「inverse reducing(逆減)」 離開窗口的舊數據來完成的. 一個例子是當窗口滑動時」添加」 和 「減」 keys 的數量. 然而,它僅適用於 「invertible reduce functions(可逆減小函數)」,即具備相應 「inverse reduce(反向減小)」 函數的 reduce 函數(做爲參數invFunc ). 像在reduceByKeyAndWindow中的那樣, reduce 任務的數量能夠經過可選參數進行配置. 請注意, 針對該操做的使用必須啓用checkpointing.
countByValueAndWindow(windowLength,slideInterval, [numTasks])在一個 (K, V) pairs 的 DStream 上調用時, 返回一個新的 (K, Long) pairs 的 DStream, 其中每一個 key 的 value 是它在一個滑動窗口以內的頻次. 像 code>reduceByKeyAndWindow 中的那樣, reduce 任務的數量能夠經過可選參數進行配置.
Join 操做
最後,它值得強調的是,您能夠輕鬆地在 Spark Streaming 中執行不一樣類型的 join.
Stream-stream joins
Streams(流)能夠很是容易地與其餘流進行 join.
valstream1:DStream[String,String]=...valstream2:DStream[String,String]=...valjoinedStream=stream1.join(stream2)
這裏,在每一個 batch interval(批間隔)中,由stream1生成的 RDD 將與stream2生成的 RDD 進行 jion. 你也能夠作leftOuterJoin,rightOuterJoin,fullOuterJoin. 此外,在 stream(流)的窗口上進行 join 一般是很是有用的. 這也很容易作到.
valwindowedStream1=stream1.window(Seconds(20))valwindowedStream2=stream2.window(Minutes(1))valjoinedStream=windowedStream1.join(windowedStream2)
Stream-dataset joins
這在解釋DStream.transform操做時已經在前面演示過了. 這是另外一個 join window stream(窗口流)與 dataset 的例子.
valdataset:RDD[String,String]=...valwindowedStream=stream.window(Seconds(20))...valjoinedStream=windowedStream.transform{rdd=>rdd.join(dataset)}
實際上,您也能夠動態更改要加入的 dataset. 提供給transform的函數是每一個 batch interval(批次間隔)進行評估,所以將使用dataset引用指向當前的 dataset.
DStream 轉換的完整列表可在 API 文檔中找到. 針對 Scala API,請看DStream和PairDStreamFunctions. 針對 Java API,請看JavaDStream和JavaPairDStream. 針對 Python API,請看DStream.
DStreams 上的輸出操做
輸出操做容許將 DStream 的數據推送到外部系統, 如數據庫或文件系統. 因爲輸出操做實際上容許外部系統使用變換後的數據, 因此它們觸發全部 DStream 變換的實際執行(相似於RDD的動做). 目前, 定義瞭如下輸出操做:
Output OperationMeaning
print()在運行流應用程序的 driver 節點上的DStream中打印每批數據的前十個元素. 這對於開發和調試頗有用.
Python API這在 Python API 中稱爲pprint().
saveAsTextFiles(prefix, [suffix])將此 DStream 的內容另存爲文本文件. 每一個批處理間隔的文件名是根據前綴和後綴:"prefix-TIME_IN_MS[.suffix]"生成的.
saveAsObjectFiles(prefix, [suffix])將此 DStream 的內容另存爲序列化 Java 對象的SequenceFiles. 每一個批處理間隔的文件名是根據前綴和後綴:"prefix-TIME_IN_MS[.suffix]"生成的.
Python API這在Python API中是不可用的.
saveAsHadoopFiles(prefix, [suffix])將此 DStream 的內容另存爲 Hadoop 文件. 每一個批處理間隔的文件名是根據前綴和後綴:"prefix-TIME_IN_MS[.suffix]"生成的.
Python API這在Python API中是不可用的.
foreachRDD(func)對從流中生成的每一個 RDD 應用函數func的最通用的輸出運算符. 此功能應將每一個 RDD 中的數據推送到外部系統, 例如將 RDD 保存到文件, 或將其經過網絡寫入數據庫. 請注意, 函數func在運行流應用程序的 driver 進程中執行, 一般會在其中具備 RDD 動做, 這將強制流式傳輸 RDD 的計算.
foreachRDD 設計模式的使用
dstream.foreachRDD是一個強大的原語, 容許將數據發送到外部系統.可是, 瞭解如何正確有效地使用這個原語很重要. 避免一些常見的錯誤以下.
一般向外部系統寫入數據須要建立鏈接對象(例如與遠程服務器的 TCP 鏈接), 並使用它將數據發送到遠程系統.爲此, 開發人員可能會無心中嘗試在Spark driver 中建立鏈接對象, 而後嘗試在Spark工做人員中使用它來在RDD中保存記錄.例如(在 Scala 中):
dstream.foreachRDD{rdd=>valconnection=createNewConnection()// executed at the driverrdd.foreach{record=>connection.send(record)// executed at the worker}}
這是不正確的, 由於這須要將鏈接對象序列化並從 driver 發送到 worker. 這種鏈接對象不多能跨機器轉移. 此錯誤可能會顯示爲序列化錯誤(鏈接對象不可序列化), 初始化錯誤(鏈接對象須要在 worker 初始化)等. 正確的解決方案是在 worker 建立鏈接對象.
可是, 這可能會致使另外一個常見的錯誤 - 爲每一個記錄建立一個新的鏈接. 例如:
dstream.foreachRDD{rdd=>rdd.foreach{record=>valconnection=createNewConnection()connection.send(record)connection.close()}}
一般, 建立鏈接對象具備時間和資源開銷. 所以, 建立和銷燬每一個記錄的鏈接對象可能會引發沒必要要的高開銷, 並可顯着下降系統的整體吞吐量. 一個更好的解決方案是使用rdd.foreachPartition- 建立一個鏈接對象, 並使用該鏈接在 RDD 分區中發送全部記錄.
dstream.foreachRDD{rdd=>rdd.foreachPartition{partitionOfRecords=>valconnection=createNewConnection()partitionOfRecords.foreach(record=>connection.send(record))connection.close()}}
這樣能夠在多個記錄上分攤鏈接建立開銷.
最後, 能夠經過跨多個RDD /批次重用鏈接對象來進一步優化. 能夠維護鏈接對象的靜態池, 而不是將多個批次的 RDD 推送到外部系統時從新使用, 從而進一步減小開銷.
dstream.foreachRDD{rdd=>rdd.foreachPartition{partitionOfRecords=>// ConnectionPool is a static, lazily initialized pool of connectionsvalconnection=ConnectionPool.getConnection()partitionOfRecords.foreach(record=>connection.send(record))ConnectionPool.returnConnection(connection)// return to the pool for future reuse}}
請注意, 池中的鏈接應根據須要懶惰建立, 若是不使用一段時間, 則會超時. 這實現了最有效地將數據發送到外部系統.
其餘要記住的要點:
DStreams 經過輸出操做進行延遲執行, 就像 RDD 由 RDD 操做懶惰地執行. 具體來講, DStream 輸出操做中的 RDD 動做強制處理接收到的數據.所以, 若是您的應用程序沒有任何輸出操做, 或者具備dstream.foreachRDD()等輸出操做, 而在其中沒有任何 RDD 操做, 則不會執行任何操做.系統將簡單地接收數據並將其丟棄.
默認狀況下, 輸出操做是 one-at-a-time 執行的. 它們按照它們在應用程序中定義的順序執行.
DataFrame 和 SQL 操做
您能夠輕鬆地在流數據上使用DataFrames and SQL和 SQL 操做. 您必須使用 StreamingContext 正在使用的 SparkContext 建立一個 SparkSession.此外, 必須這樣作, 以即可以在 driver 故障時從新啓動. 這是經過建立一個簡單實例化的 SparkSession 單例實例來實現的.這在下面的示例中顯示.它使用 DataFrames 和 SQL 來修改早期的字數示例以生成單詞計數.將每一個 RDD 轉換爲 DataFrame, 註冊爲臨時表, 而後使用 SQL 進行查詢.
/** DataFrame operations inside your streaming program */valwords:DStream[String]=...words.foreachRDD{rdd=>// Get the singleton instance of SparkSessionvalspark=SparkSession.builder.config(rdd.sparkContext.getConf).getOrCreate()importspark.implicits._// Convert RDD[String] to DataFramevalwordsDataFrame=rdd.toDF("word")// Create a temporary viewwordsDataFrame.createOrReplaceTempView("words")// Do word count on DataFrame using SQL and print itvalwordCountsDataFrame=spark.sql("select word, count(*) as total from words group by word")wordCountsDataFrame.show()}
請參閱完整的源代碼.
您還能夠對來自不一樣線程的流數據(即異步運行的 StreamingContext )上定義的表運行 SQL 查詢. 只需確保您將 StreamingContext 設置爲記住足夠數量的流數據, 以便查詢能夠運行. 不然, 不知道任何異步 SQL 查詢的 StreamingContext 將在查詢完成以前刪除舊的流數據. 例如, 若是要查詢最後一個批次, 可是您的查詢可能須要5分鐘才能運行, 則能夠調用streamingContext.remember(Minutes(5))(以 Scala 或其餘語言的等價物).
有關DataFrames的更多信息, 請參閱DataFrames 和 SQL 指南.
MLlib 操做
您還能夠輕鬆使用MLlib提供的機器學習算法. 首先, 有 streaming 機器學習算法(例如:Streaming 線性迴歸,Streaming KMeans等), 其能夠同時從 streaming 數據中學習, 並將該模型應用於 streaming 數據. 除此以外, 對於更大類的機器學習算法, 您能夠離線學習一個學習模型(即便用歷史數據), 而後將該模型在線應用於流數據.有關詳細信息, 請參閱MLlib指南.
緩存 / 持久性
與 RDD 相似, DStreams 還容許開發人員將流的數據保留在內存中. 也就是說, 在 DStream 上使用persist()方法會自動將該 DStream 的每一個 RDD 保留在內存中. 若是 DStream 中的數據將被屢次計算(例如, 相同數據上的多個操做), 這將很是有用. 對於基於窗口的操做, 如reduceByWindow和reduceByKeyAndWindow以及基於狀態的操做, 如updateStateByKey, 這是隱含的.所以, 基於窗口的操做生成的 DStream 會自動保存在內存中, 而不須要開發人員調用persist().
對於經過網絡接收數據(例如: Kafka, Flume, sockets 等)的輸入流, 默認持久性級別被設置爲將數據複製到兩個節點進行容錯.
請注意, 與 RDD 不一樣, DStreams 的默認持久性級別將數據序列化在內存中. 這在性能調優部分進一步討論. 有關不一樣持久性級別的更多信息, 請參見Spark編程指南.
Checkpointing
streaming 應用程序必須 24/7 運行, 所以必須對應用邏輯無關的故障(例如, 系統故障, JVM 崩潰等)具備彈性. 爲了能夠這樣作, Spark Streaming 須要checkpoint足夠的信息到容錯存儲系統, 以即可以從故障中恢復.checkpoint有兩種類型的數據.
Metadata checkpointing- 將定義 streaming 計算的信息保存到容錯存儲(如 HDFS)中.這用於從運行 streaming 應用程序的 driver 的節點的故障中恢復(稍後詳細討論). 元數據包括:
Configuration- 用於建立流應用程序的配置.
DStream operations- 定義 streaming 應用程序的 DStream 操做集.
Incomplete batches- 批量的job 排隊但還沒有完成.
Data checkpointing- 將生成的 RDD 保存到可靠的存儲.這在一些將多個批次之間的數據進行組合的狀態變換中是必需的.在這種轉換中, 生成的 RDD 依賴於先前批次的 RDD, 這致使依賴鏈的長度隨時間而增長.爲了不恢復時間的這種無限增長(與依賴關係鏈成比例), 有狀態轉換的中間 RDD 會按期checkpoint到可靠的存儲(例如 HDFS)以切斷依賴關係鏈.
總而言之, 元數據 checkpoint 主要用於從 driver 故障中恢復, 而數據或 RDD checkpoint 對於基本功能(若是使用有狀態轉換)則是必需的.
什麼時候啓用 checkpoint
對於具備如下任一要求的應用程序, 必須啓用 checkpoint:
使用狀態轉換- 若是在應用程序中使用updateStateByKey或reduceByKeyAndWindow(具備反向功能), 則必須提供 checkpoint 目錄以容許按期的 RDD checkpoint.
從運行應用程序的 driver 的故障中恢復- 元數據 checkpoint 用於使用進度信息進行恢復.
請注意, 無需進行上述有狀態轉換的簡單 streaming 應用程序便可運行, 無需啓用 checkpoint. 在這種狀況下, 驅動器故障的恢復也將是部分的(一些接收但未處理的數據可能會丟失). 這一般是能夠接受的, 許多運行 Spark Streaming 應用程序. 將來對非 Hadoop 環境的支持預計會有所改善.
如何配置 checkpoint
能夠經過在保存 checkpoint 信息的容錯, 可靠的文件系統(例如, HDFS, S3等)中設置目錄來啓用 checkpoint. 這是經過使用streamingContext.checkpoint(checkpointDirectory)完成的. 這將容許您使用上述有狀態轉換. 另外, 若是要使應用程序從 driver 故障中恢復, 您應該重寫 streaming 應用程序以具備如下行爲.
當程序第一次啓動時, 它將建立一個新的 StreamingContext, 設置全部流, 而後調用 start().
當程序在失敗後從新啓動時, 它將從 checkpoint 目錄中的 checkpoint 數據從新建立一個 StreamingContext.
使用StreamingContext.getOrCreate能夠簡化此行爲. 這樣使用以下.
// Function to create and setup a new StreamingContextdeffunctionToCreateContext():StreamingContext={valssc=newStreamingContext(...)// new contextvallines=ssc.socketTextStream(...)// create DStreams...ssc.checkpoint(checkpointDirectory)// set checkpoint directoryssc}// Get StreamingContext from checkpoint data or create a new onevalcontext=StreamingContext.getOrCreate(checkpointDirectory,functionToCreateContext_)// Do additional setup on context that needs to be done,// irrespective of whether it is being started or restartedcontext....// Start the contextcontext.start()context.awaitTermination()
If thecheckpointDirectoryexists, then the context will be recreated from the checkpoint data. If the directory does not exist (i.e., running for the first time), then the functionfunctionToCreateContextwill be called to create a new context and set up the DStreams. See the Scala exampleRecoverableNetworkWordCount. This example appends the word counts of network data into a file.
除了使用getOrCreate以外, 還須要確保在失敗時自動從新啓動 driver 進程. 這隻能由用於運行應用程序的部署基礎架構完成. 這在部署部分進一步討論.
請注意, RDD 的 checkpoint 會致使保存到可靠存儲的成本. 這可能會致使 RDD 獲得 checkpoint 的批次的處理時間增長. 所以, 須要仔細設置 checkpoint 的間隔. 在小批量大小(例如: 1秒), 檢查每一個批次可能會顯着下降操做吞吐量. 相反, checkpoint 太少會致使譜系和任務大小增加, 這可能會產生不利影響. 對於須要 RDD checkpoint 的狀態轉換, 默認間隔是至少10秒的批間隔的倍數. 它能夠經過使用dstream.checkpoint(checkpointInterval)進行設置. 一般, DStream 的5到10個滑動間隔的 checkpoint 間隔是一個很好的設置.
Accumulators, Broadcast 變量, 和 Checkpoint
在Spark Streaming中, 沒法從 checkpoint 恢復Accumulators和Broadcast 變量. 若是啓用 checkpoint 並使用Accumulators或Broadcast 變量, 則必須爲Accumulators和Broadcast 變量建立延遲實例化的單例實例, 以便在 driver 從新啓動失敗後從新實例化. 這在下面的示例中顯示:
objectWordBlacklist{@volatileprivatevarinstance:Broadcast[Seq[String]]=nulldefgetInstance(sc:SparkContext):Broadcast[Seq[String]]={if(instance==null){synchronized{if(instance==null){valwordBlacklist=Seq("a","b","c")instance=sc.broadcast(wordBlacklist)}}}instance}}objectDroppedWordsCounter{@volatileprivatevarinstance:LongAccumulator=nulldefgetInstance(sc:SparkContext):LongAccumulator={if(instance==null){synchronized{if(instance==null){instance=sc.longAccumulator("WordsInBlacklistCounter")}}}instance}}wordCounts.foreachRDD{(rdd:RDD[(String,Int)],time:Time)=>// Get or register the blacklist Broadcastvalblacklist=WordBlacklist.getInstance(rdd.sparkContext)// Get or register the droppedWordsCounter AccumulatorvaldroppedWordsCounter=DroppedWordsCounter.getInstance(rdd.sparkContext)// Use blacklist to drop words and use droppedWordsCounter to count themvalcounts=rdd.filter{case(word,count)=>if(blacklist.value.contains(word)){droppedWordsCounter.add(count)false}else{true}}.collect().mkString("[",", ","]")valoutput="Counts at time "+time+" "+counts})
請參閱完整的源代碼.
應用程序部署
本節討論部署 Spark Streaming 應用程序的步驟.
要求
要運行 Spark Streaming 應用程序, 您須要具有如下功能.
集羣管理器集羣- 這是任何 Spark 應用程序的通常要求, 並在部署指南中詳細討論.
打包應用程序 JAR- 您必須將 streaming 應用程序編譯爲 JAR. 若是您正在使用spark-submit啓動應用程序, 則不須要在 JAR 中提供 Spark 和 Spark Streaming.可是, 若是您的應用程序使用高級資源(例如: Kafka, Flume), 那麼您將必須將他們連接的額外工件及其依賴項打包在用於部署應用程序的 JAR 中.例如, 使用KafkaUtils的應用程序必須在應用程序 JAR 中包含spark-streaming-kafka-0-8_2.11及其全部傳遞依賴關係.
爲 executor 配置足夠的內存- 因爲接收到的數據必須存儲在內存中, 因此 executor 必須配置足夠的內存來保存接收到的數據. 請注意, 若是您正在進行10分鐘的窗口操做, 系統必須至少保留最近10分鐘的內存中的數據. 所以, 應用程序的內存要求取決於其中使用的操做.
配置 checkpoint- 若是 streaming 應用程序須要它, 則 Hadoop API 兼容容錯存儲(例如:HDFS, S3等)中的目錄必須配置爲 checkpoint 目錄, 而且流程應用程序以 checkpoint 信息的方式編寫 用於故障恢復. 有關詳細信息, 請參閱checkpoint部分.
配置應用程序 driver 的自動從新啓動- 要從 driver 故障自動恢復, 用於運行流應用程序的部署基礎架構必須監視 driver 進程, 並在 driver 發生故障時從新啓動 driver.不一樣的集羣管理者有不一樣的工具來實現這一點.
Spark Standalone- 能夠提交 Spark 應用程序 driver 以在Spark Standalone集羣中運行(請參閱集羣部署模式), 即應用程序 driver 自己在其中一個工做節點上運行. 此外, 能夠指示獨立的羣集管理器來監督 driver, 若是因爲非零退出代碼而致使 driver 發生故障, 或因爲運行 driver 的節點發生故障, 則能夠從新啓動它. 有關詳細信息, 請參閱 [Spark Standalone 指南]](spark-standalone.html) 中的羣集模式和監督.
YARN- Yarn 支持相似的機制來自動從新啓動應用程序.有關詳細信息, 請參閱 YARN文檔.
Mesos-Marathon已被用來實現這一點與Mesos.
配置預寫日誌- 自 Spark 1.2 以來, 咱們引入了寫入日誌來實現強大的容錯保證.若是啓用, 則從 receiver 接收的全部數據都將寫入配置 checkpoint 目錄中的寫入日誌.這能夠防止 driver 恢復時的數據丟失, 從而確保零數據丟失(在容錯語義部分中詳細討論).能夠經過將配置參數spark.streaming.receiver.writeAheadLog.enable設置爲true來啓用此功能.然而, 這些更強的語義可能以單個 receiver 的接收吞吐量爲代價.經過並行運行更多的 receiver能夠糾正這一點, 以增長總吞吐量.另外, 建議在啓用寫入日誌時, 在日誌已經存儲在複製的存儲系統中時, 禁用在 Spark 中接收到的數據的複製.這能夠經過將輸入流的存儲級別設置爲StorageLevel.MEMORY_AND_DISK_SER來完成.使用 S3(或任何不支持刷新的文件系統)寫入日誌時, 請記住啓用spark.streaming.driver.writeAheadLog.closeFileAfterWrite和spark.streaming.receiver.writeAheadLog.closeFileAfterWrite.有關詳細信息, 請參閱Spark Streaming配.請注意, 啓用 I/O 加密時, Spark 不會將寫入寫入日誌的數據加密.若是須要對提早記錄數據進行加密, 則應將其存儲在本地支持加密的文件系統中.
設置最大接收速率- 若是集羣資源不夠大, streaming 應用程序可以像接收到的那樣快速處理數據, 則能夠經過設置 記錄/秒 的最大速率限制來對 receiver 進行速率限制. 請參閱 receiver 的spark.streaming.receiver.maxRate和用於 Direct Kafka 方法的spark.streaming.kafka.maxRatePerPartition的配置參數. 在Spark 1.5中, 咱們引入了一個稱爲背壓的功能, 無需設置此速率限制, 由於Spark Streaming會自動計算速率限制, 並在處理條件發生變化時動態調整速率限制. 能夠經過將配置參數spark.streaming.backpressure.enabled設置爲true來啓用此 backpressure.
升級應用程序代碼
若是運行的 Spark Streaming 應用程序須要使用新的應用程序代碼進行升級, 則有兩種可能的機制.
升級後的 Spark Streaming 應用程序與現有應用程序並行啓動並運行.一旦新的(接收與舊的數據相同的數據)已經升溫並準備好黃金時段, 舊的能夠被關掉.請注意, 這能夠用於支持將數據發送到兩個目的地(即較早和已升級的應用程序)的數據源.
現有應用程序正常關閉(請參閱StreamingContext.stop(...)或JavaStreamingContext.stop(...)以獲取正常的關閉選項), 以確保已關閉的數據在關閉以前被徹底處理.而後能夠啓動升級的應用程序, 這將從較早的應用程序中止的同一點開始處理.請注意, 只有在支持源端緩衝的輸入源(如: Kafka 和 Flume)時才能夠進行此操做, 由於數據須要在先前的應用程序關閉而且升級的應用程序還沒有啓動時進行緩衝.從升級前代碼的早期 checkpoint 信息從新啓動不能完成.checkpoint 信息基本上包含序列化的 Scala/Java/Python 對象, 並嘗試使用新的修改的類反序列化對象可能會致使錯誤.在這種狀況下, 能夠使用不一樣的 checkpoint 目錄啓動升級的應用程序, 也能夠刪除之前的 checkpoint 目錄.
Monitoring Applications (監控應用程序)
除了 Spark 的monitoring capabilities(監控功能), 還有其餘功能特定於 Spark Streaming .當使用 StreamingContext 時,Spark web UI顯示一個額外的Streaming選項卡, 顯示 running receivers (運行接收器)的統計信息(不管是 receivers (接收器)是否處於 active (活動狀態), 接收到的 records (記錄)數, receiver error (接收器錯誤)等)並完成 batches (批次)(batch processing times (批處理時間), queueing delays (排隊延遲)等).這能夠用來監視 streaming application (流應用程序)的進度.
web UI 中的如下兩個 metrics (指標)特別重要:
Processing Time (處理時間)- 處理每 batch (批)數據的時間 .
Scheduling Delay (調度延遲)- batch (批處理)在 queue (隊列)中等待處理 previous batches (之前批次)完成的時間.
若是 batch processing time (批處理時間)始終 more than (超過) batch interval (批間隔) and/or queueing delay (排隊延遲)不斷增長, 表示系統是沒法快速 process the batches (處理批次), 而且正在 falling behind (落後). 在這種狀況下, 請考慮reducing (減小)batch processing time (批處理時間).
Spark Streaming 程序的進展也能夠使用StreamingListener接口, 這容許您得到 receiver status (接收器狀態)和 processing times (處理時間).請注意, 這是一個開發人員 API 而且未來可能會改善(即, 更多的信息報告).
Performance Tuning (性能調優)
在集羣上的 Spark Streaming application 中得到最佳性能須要一些調整.本節介紹了可調整的多個 parameters (參數)和 configurations (配置)提升你的應用程序性能.在高層次上, 你須要考慮兩件事情:
經過有效利用集羣資源, Reducing the processing time of each batch of data (減小每批數據的處理時間).
設置正確的 batch size (批量大小), 以便 batches of data (批量的數據)能夠像 received (被接收)處理同樣快(即 data processing (數據處理)與 data ingestion (數據攝取)保持一致).
Reducing the Batch Processing Times (減小批處理時間)
在 Spark 中能夠進行一些優化, 以 minimize the processing time of each batch (最小化每批處理時間).這些已在Tuning Guide (調優指南)中詳細討論過.本節突出了一些最重要的.
Level of Parallelism in Data Receiving (數據接收中的並行級別)
經過網絡接收數據(如Kafka, Flume, socket 等)須要 deserialized (反序列化)數據並存儲在 Spark 中.若是數據接收成爲系統的瓶頸, 那麼考慮一下 parallelizing the data receiving (並行化數據接收).注意每一個 input DStream 建立接收 single stream of data (單個數據流)的 single receiver (單個接收器)(在 work machine 上運行). 所以, 能夠經過建立多個 input DStreams 來實現 Receiving multiple data streams (接收多個數據流)並配置它們以從 source(s) 接收 data stream (數據流)的 different partitions (不一樣分區).例如, 接收 two topics of data (兩個數據主題)的單個Kafka input DStream 能夠分爲兩個 Kafka input streams (輸入流), 每一個只接收一個 topic (主題).這將運行兩個 receivers (接收器), 容許 in parallel (並行)接收數據, 從而提升 overall throughput (整體吞吐量).這些 multiple DStreams 能夠 unioned (聯合起來)建立一個 single DStream .而後 transformations (轉化)爲應用於 single input DStream 能夠應用於 unified stream .以下這樣作.
valnumStreams=5valkafkaStreams=(1tonumStreams).map{i=>KafkaUtils.createStream(...)}valunifiedStream=streamingContext.union(kafkaStreams)unifiedStream.print()
應考慮的另外一個參數是 receiver’s block interval (接收器的塊間隔), 這由configuration parameter (配置參數)的spark.streaming.blockInterval決定.對於大多數 receivers (接收器), 接收到的數據 coalesced (合併)在一塊兒存儲在 Spark 內存以前的 blocks of data (數據塊).每一個 batch (批次)中的 blocks (塊)數肯定將用於處理接收到的數據以 map-like (相似與 map 形式的) transformation (轉換)的 task (任務)的數量.每一個 receiver (接收器)每 batch (批次)的任務數量將是大約( batch interval (批間隔)/ block interval (塊間隔)).例如, 200 ms的 block interval (塊間隔)每 2 秒 batches (批次)建立 10 個 tasks (任務).若是 tasks (任務)數量太少(即少於每一個機器的內核數量), 那麼它將無效, 由於全部可用的內核都不會被使用處理數據.要增長 given batch interval (給定批間隔)的 tasks (任務)數量, 請減小 block interval (塊間隔).可是, 推薦的 block interval (塊間隔)最小值約爲 50ms , 低於此任務啓動開銷多是一個問題.
使用 multiple input streams (多個輸入流)/ receivers (接收器)接收數據的替代方法是明確 repartition (從新分配) input data stream (輸入數據流)(使用inputStream.repartition()). 這會在 further processing (進一步處理)以前將 received batches of data (收到的批次數據) distributes (分發)到集羣中指定數量的計算機.
Level of Parallelism in Data Processing (數據處理中的並行度水平)
若是在任何 computation (計算)階段中使用 number of parallel tasks (並行任務的數量), 則 Cluster resources (集羣資源)可能未獲得充分利用. 例如, 對於 distributed reduce (分佈式 reduce)操做, 如reduceByKey和reduceByKeyAndWindow, 默認並行任務的數量由spark.default.parallelismconfiguration property控制. 您 能夠經過 parallelism (並行度)做爲參數(見PairDStreamFunctions文檔 ), 或設置spark.default.parallelismconfiguration property更改默認值.
Data Serialization (數據序列化)
能夠經過調優 serialization formats (序列化格式)來減小數據 serialization (序列化)的開銷.在 streaming 的狀況下, 有兩種類型的數據被 serialized (序列化).
Input data (輸入數據): 默認狀況下, 經過 Receivers 接收的 input data (輸入數據)經過StorageLevel.MEMORY_AND_DISK_SER_2存儲在 executors 的內存中.也就是說, 將數據 serialized (序列化)爲 bytes (字節)以減小 GC 開銷, 並複製以容忍 executor failures (執行器故障).此外, 數據首先保留在內存中, 而且只有在內存不足以容納 streaming computation (流計算)所需的全部輸入數據時纔會 spilled over (溢出)到磁盤.這個 serialization (序列化)顯然具備開銷 - receiver (接收器)必須使接收的數據 deserialize (反序列化), 並使用 Spark 的 serialization format (序列化格式)從新序列化它.
Persisted RDDs generated by Streaming Operations (流式操做生成的持久 RDDs): 經過 streaming computations (流式計算)生成的 RDD 可能會持久存儲在內存中.例如, window operations (窗口操做)會將數據保留在內存中, 由於它們將被處理屢次.可是, 與StorageLevel.MEMORY_ONLY的 Spark Core 默認狀況不一樣, 經過流式計算生成的持久化 RDD 將以StorageLevel.MEMORY_ONLY_SER(即序列化), 以最小化 GC 開銷.
在這兩種狀況下, 使用 Kryo serialization (Kryo 序列化)能夠減小 CPU 和內存開銷.有關詳細信息, 請參閱Spark Tuning Guide.對於 Kryo , 請考慮 registering custom classes , 並禁用對象引用跟蹤(請參閱Configuration Guide中的 Kryo 相關配置).
在 streaming application 須要保留的數據量不大的特定狀況下, 能夠將數據(兩種類型)做爲 deserialized objects (反序列化對象)持久化, 而不會致使過多的 GC 開銷.例如, 若是您使用幾秒鐘的 batch intervals (批次間隔)而且沒有 window operations (窗口操做), 那麼能夠經過明確地相應地設置 storage level (存儲級別)來嘗試禁用 serialization in persisted data (持久化數據中的序列化).這將減小因爲序列化形成的 CPU 開銷, 潛在地提升性能, 而不須要太多的 GC 開銷.
Task Launching Overheads (任務啓動開銷)
若是每秒啓動的任務數量很高(好比每秒 50 個或更多), 那麼這個開銷向 slaves 發送任務多是重要的, 而且將難以實現 sub-second latencies (次要的延遲).能夠經過如下更改減小開銷:
Execution mode (執行模式): 以 Standalone mode (獨立模式)或 coarse-grained Mesos 模式運行 Spark 比 fine-grained Mesos 模式更好的任務啓動時間.有關詳細信息, 請參閱Running on Mesos guide.
這些更改可能會將 batch processing time (批處理時間)縮短 100 毫秒, 從而容許 sub-second batch size (次秒批次大小)是可行的.
Setting the Right Batch Interval (設置正確的批次間隔)
對於在集羣上穩定地運行的 Spark Streaming application, 該系統應該可以處理數據儘量快地被接收.換句話說, 應該處理批次的數據就像生成它們同樣快.這是否適用於 application 能夠在monitoringstreaming web UI 中的 processing times 中被找到, processing time (批處理處理時間)應小於 batch interval (批間隔).
取決於 streaming computation (流式計算)的性質, 使用的 batch interval (批次間隔)可能對處理由應用程序持續一組固定的 cluster resources (集羣資源)的數據速率有重大的影響.例如, 讓咱們考慮早期的 WordCountNetwork 示例.對於特定的 data rate (數據速率), 系統可能可以跟蹤每 2 秒報告 word counts (即 2 秒的 batch interval (批次間隔)), 但不能每 500 毫秒.所以, 須要設置 batch interval (批次間隔), 使預期的數據速率在生產能夠持續.
爲您的應用程序找出正確的 batch size (批量大小)的一個好方法是使用進行測試 conservative batch interval (保守的批次間隔)(例如 5-10 秒)和 low data rate (低數據速率).驗證是否系統可以跟上 data rate (數據速率), 能夠檢查遇到的 end-to-end delay (端到端延遲)的值經過每一個 processed batch (處理的批次)(在 Spark driver log4j 日誌中查找 「Total delay」 , 或使用StreamingListener接口). 若是 delay (延遲)保持與 batch size (批量大小)至關, 那麼系統是穩定的.除此之外, 若是延遲不斷增長, 則意味着系統沒法跟上, 所以不穩定.一旦你有一個 stable configuration (穩定的配置)的想法, 你能夠嘗試增長 data rate and/or 減小 batch size .請注意, momentary increase (瞬時增長)因爲延遲暫時增長只要延遲下降到 low value (低值), 臨時數據速率增長就能夠很好(即, 小於 batch size (批量大小)).
Memory Tuning (內存調優)
調整 Spark 應用程序的內存使用狀況和 GC behavior 已經有不少的討論在Tuning Guide中.咱們強烈建議您閱讀一下.在本節中, 咱們將在 Spark Streaming applications 的上下文中討論一些 tuning parameters (調優參數).
Spark Streaming application 所需的集羣內存量在很大程度上取決於所使用的 transformations 類型.例如, 若是要在最近 10 分鐘的數據中使用 window operation (窗口操做), 那麼您的集羣應該有足夠的內存來容納內存中 10 分鐘的數據.或者若是要使用大量 keys 的updateStateByKey, 那麼必要的內存將會很高.相反, 若是你想作一個簡單的 map-filter-store 操做, 那麼所需的內存就會很低.
通常來講, 因爲經過 receivers (接收器)接收的數據與 StorageLevel.MEMORY_AND_DISK_SER_2 一塊兒存儲, 因此不適合內存的數據將會 spill over (溢出)到磁盤上.這可能會下降 streaming application (流式應用程序)的性能, 所以建議您提供足夠的 streaming application (流量應用程序)所需的內存.最好仔細查看內存使用量並相應地進行估算.
memory tuning (內存調優)的另外一個方面是 garbage collection (垃圾收集).對於須要低延遲的 streaming application , 由 JVM Garbage Collection 引發的大量暫停是不但願的.
有幾個 parameters (參數)能夠幫助您調整 memory usage (內存使用量)和 GC 開銷:
Persistence Level of DStreams (DStreams 的持久性級別): 如前面在Data Serialization部分中所述, input data 和 RDD 默認保持爲 serialized bytes (序列化字節).與 deserialized persistence (反序列化持久性)相比, 這減小了內存使用量和 GC 開銷.啓用 Kryo serialization 進一步減小了 serialized sizes (序列化大小)和 memory usage (內存使用).能夠經過 compression (壓縮)來實現內存使用的進一步減小(參見Spark配置spark.rdd.compress), 代價是 CPU 時間.
Clearing old data (清除舊數據): 默認狀況下, DStream 轉換生成的全部 input data 和 persisted RDDs 將自動清除. Spark Streaming 決定什麼時候根據所使用的 transformations (轉換)來清除數據.例如, 若是您使用 10 分鐘的 window operation (窗口操做), 則 Spark Streaming 將保留最近 10 分鐘的數據, 並主動丟棄舊數據. 數據能夠經過設置streamingContext.remember保持更長的持續時間(例如交互式查詢舊數據).
CMS Garbage Collector (CMS垃圾收集器): 強烈建議使用 concurrent mark-and-sweep GC , 以保持 GC 相關的暫停始終如一.即便 concurrent GC 已知能夠減小 系統的總體處理吞吐量, 其使用仍然建議實現更多一致的 batch processing times (批處理時間).確保在 driver (使用--driver-java-options在spark-submit中 )和 executors (使用Spark configurationspark.executor.extraJavaOptions)中設置 CMS GC.
Other tips (其餘提示): 爲了進一步下降 GC 開銷, 如下是一些更多的提示.
使用OFF_HEAP存儲級別的保持 RDDs .在Spark Programming Guide中查看更多詳細信息.
使用更小的 heap sizes 的 executors.這將下降每一個 JVM heap 內的 GC 壓力.
Important points to remember(要記住的要點):
DStream 與 single receiver (單個接收器)相關聯.爲了得到讀取並行性, 須要建立多個 receivers , 即 multiple DStreams .receiver 在一個 executor 中運行.它佔據一個 core (內核).確保在 receiver slots are booked 後有足夠的內核進行處理, 即spark.cores.max應該考慮 receiver slots . receivers 以循環方式分配給 executors .
當從 stream source 接收到數據時, receiver 建立數據 blocks (塊).每一個 blockInterval 毫秒生成一個新的數據塊.在 N = batchInterval/blockInterval 的 batchInterval 期間建立 N 個數據塊.這些塊由當前 executor 的 BlockManager 分發給其餘執行程序的 block managers .以後, 在驅動程序上運行的 Network Input Tracker (網絡輸入跟蹤器)通知有關進一步處理的塊位置
在驅動程序中爲在 batchInterval 期間建立的塊建立一個 RDD .在 batchInterval 期間生成的塊是 RDD 的 partitions .每一個分區都是一個 spark 中的 task. blockInterval == batchinterval 意味着建立 single partition (單個分區), 而且可能在本地進行處理.
除非 non-local scheduling (非本地調度)進行, 不然塊上的 map tasks (映射任務)將在 executors (接收 block, 複製塊的另外一個塊)中進行處理.具備更大的 block interval (塊間隔)意味着更大的塊.spark.locality.wait的高值增長了處理 local node (本地節點)上的塊的機會.須要在這兩個參數之間找到平衡, 以確保在本地處理較大的塊.
而不是依賴於 batchInterval 和 blockInterval , 您能夠經過調用inputDstream.repartition(n)來定義 number of partitions (分區數).這樣能夠隨機從新組合 RDD 中的數據, 建立 n 個分區.是的, 爲了更大的 parallelism (並行性).雖然是 shuffle 的代價. RDD 的處理由 driver’s jobscheduler 做爲一項工做安排.在給定的時間點, 只有一個 job 是 active 的.所以, 若是一個做業正在執行, 則其餘做業將排隊.
若是您有兩個 dstream , 將會有兩個 RDD 造成, 而且將建立兩個將被安排在另外一個以後的做業.爲了不這種狀況, 你能夠聯合兩個 dstream .這將確保爲 dstream 的兩個 RDD 造成一個 unionRDD .這個 unionRDD 而後被認爲是一個 single job (單一的工做).但 RDD 的 partitioning (分區)不受影響.
若是 batch processing time (批處理時間)超過 batchinterval (批次間隔), 那麼顯然 receiver 的內存將會開始填滿, 最終會拋出 exceptions (最多是 BlockNotFoundException ).目前沒有辦法暫停 receiver .使用 SparkConf 配置spark.streaming.receiver.maxRate, receiver 的 rate 能夠受到限制.
Fault-tolerance Semantics (容錯語義)
在本節中, 咱們將討論 Spark Streaming applications 在該 event 中的行爲的失敗.
Background(背景)
要了解 Spark Streaming 提供的語義, 請記住 Spark 的 RDD 的基本 fault-tolerance semantics (容錯語義).
RDD 是一個不可變的, 肯定性地可從新計算的分佈式數據集.每一個RDD 記住在容錯輸入中使用的肯定性操做的 lineage 數據集建立它.
若是 RDD 的任何 partition 因爲工做節點故障而丟失, 則該分區能夠是 從 original fault-tolerant dataset (原始容錯數據集)中使用業務流程從新計算.
假設全部的 RDD transformations 都是肯定性的, 最後的數據被轉換, 不管 Spark 集羣中的故障如何, RDD 始終是同樣的.
Spark 運行在容錯文件系統(如 HDFS 或 S3 )中的數據上.所以, 從容錯數據生成的全部 RDD 也都是容錯的.可是, 這不是在大多數狀況下, Spark Streaming 做爲數據的狀況經過網絡接收(除非fileStream被使用).爲了爲全部生成的 RDD 實現相同的 fault-tolerance properties (容錯屬性), 接收的數據在集羣中的工做節點中的多個 Spark executors 之間進行復制(默認 replication factor (備份因子)爲 2).這致使了發生故障時須要恢復的系統中的兩種數據:
Data received and replicated (數據接收和複製)- 這個數據在單個工做節點做爲副本的故障中倖存下來, 它存在於其餘節點之一上.
Data received but buffered for replication (接收數據但緩衝進行復制)- 因爲不復制, 恢復此數據的惟一方法是從 source 從新獲取.
此外, 咱們應該關注的有兩種 failures:
Failure of a Worker Node (工做節點的故障)- 運行 executors 的任何工做節點均可能會故障, 而且這些節點上的全部內存中數據將丟失.若是任何 receivers 運行在失敗節點, 則它們的 buffered (緩衝)數據將丟失.
Failure of the Driver Node (Driver 節點的故障)- 若是運行 Spark Streaming application 的 driver node 發生了故障, 那麼顯然 SparkContext 丟失了, 全部的 executors 和其內存中的數據也一塊兒丟失了.
有了這個基礎知識, 讓咱們瞭解 Spark Streaming 的 fault-tolerance semantics (容錯語義).
Definitions (定義)
streaming systems (流系統)的語義一般是經過系統能夠處理每一個記錄的次數來捕獲的.系統能夠在全部可能的操做條件下提供三種類型的保證(儘管有故障等).
At most once (最多一次): 每一個 record (記錄)將被處理一次或根本不處理.
At least once (至少一次): 每一個 record (記錄)將被處理一次或屢次.這比at-most once, 由於它確保沒有數據將丟失.但可能有重複.
Exactly once(有且僅一次): 每一個 record (記錄) 將被精確處理一次 - 沒有數據丟失, 數據不會被屢次處理.這顯然是三者的最強保證.
Basic Semantics (基本語義)
在任何 stream processing system (流處理系統)中, 廣義上說, 處理數據有三個步驟.
Receiving the data (接收數據): 使用 Receivers 或其餘方式從數據源接收數據.
Transforming the data (轉換數據): 使用 DStream 和 RDD transformations 來 transformed (轉換)接收到的數據.
Pushing out the data (推出數據): 最終的轉換數據被推出到 external systems (外部系統), 如 file systems (文件系統), databases (數據庫), dashboards (儀表板)等.
若是 streaming application 必須實現 end-to-end exactly-once guarantees (端到端的一次且僅一次性保證), 那麼每一個步驟都必須提供 exactly-once guarantee .也就是說, 每一個記錄必須被精確地接收一次, 轉換完成一次, 並被推送到下游系統一次.讓咱們在 Spark Streaming 的上下文中瞭解這些步驟的語義.
Receiving the data (接收數據): 不一樣的 input sources 提供不一樣的保證.這將在下一小節中詳細討論.
Transforming the data (轉換數據): 全部已收到的數據都將被處理exactly once, 這得益於 RDD 提供的保證.即便存在故障, 只要接收到的輸入數據可訪問, 最終變換的 RDD 將始終具備相同的內容.
Pushing out the data (推出數據): 默認狀況下的輸出操做確保at-least once語義, 由於它取決於輸出操做的類型( idempotent (冪等))或 downstream system (下游系統)的語義(是否支持 transactions (事務)).但用戶能夠實現本身的事務機制來實現exactly-once語義.這將在本節後面的更多細節中討論.
Semantics of Received Data (接收數據的語義)
不一樣的 input sources (輸入源)提供不一樣的保證, 範圍從at-least once到exactly once.
With Files
若是全部的 input data (輸入數據)都已經存在於 fault-tolerant file system (容錯文件系統)中 HDFS , Spark Streaming 能夠隨時從任何故障中恢復並處理全部數據.這給了exactly-once語義, 意味着不管什麼故障, 全部的數據將被精確處理一次.
With Receiver-based Sources (使用基於接收器的數據源)
對於基於 receivers (接收器)的 input sources (輸入源), 容錯語義取決於故障場景和接收器的類型. 正如咱們earlier討論的, 有兩種類型的 receivers (接收器):
Reliable Receiver (可靠的接收器)- 這些 receivers (接收機)只有在確認收到的數據已被複制以後確認 reliable sources (可靠的源).若是這樣的接收器出現故障, source 將不會被接收對於 buffered (unreplicated) data (緩衝(未複製)數據)的確認.所以, 若是 receiver 是從新啓動, source 將從新發送數據, 而且不會因爲故障而丟失數據.
Unreliable Receiver (不可靠的接收器)- 這樣的接收器不會發送確認, 所以可能丟失數據, 因爲 worker 或 driver 故障.
根據使用的 receivers 類型, 咱們實現如下語義. 若是 worker node 出現故障, 則 reliable receivers 沒有數據丟失.unreliable receivers , 收到但未複製的數據可能會丟失.若是 driver node 失敗, 那麼除了這些損失以外, 在內存中接收和複製的全部過去的數據將丟失.這將影響 stateful transformations (有狀態轉換)的結果.
爲避免過去收到的數據丟失, Spark 1.2 引入了_write ahead logs_ 將接收到的數據保存到 fault-tolerant storage (容錯存儲).用write ahead logs enabled和 reliable receivers, 數據沒有丟失.在語義方面, 它提供 at-least once guarantee (至少一次保證).
下表總結了失敗的語義:
Deployment Scenario (部署場景)Worker Failure (Worker 故障)Driver Failure (Driver 故障)
Spark 1.1 或更早版本,或者
Spark 1.2 或者沒有 write ahead logs 的更高的版本Buffered data lost with unreliable receivers(unreliable receivers 的緩衝數據丟失)
Zero data loss with reliable receivers (reliable receivers 的零數據丟失)
At-least once semantics (至少一次性語義)Buffered data lost with unreliable receivers (unreliable receivers 的緩衝數據丟失)
Past data lost with all receivers (全部的 receivers 的過去的數據丟失)
Undefined semantics (未定義語義)
Spark 1.2 或者帶有 write ahead logs 的更高版本Zero data loss with reliable receivers(reliable receivers 的零數據丟失)
At-least once semantics (至少一次性語義)Zero data loss with reliable receivers and files (reliable receivers 和 files 的零數據丟失)
At-least once semantics (至少一次性語義)
With Kafka Direct API (使用 Kafka Direct API)
在 Spark 1.3 中, 咱們引入了一個新的 Kafka Direct API , 能夠確保全部的 Kafka 數據都被 Spark Streaming exactly once (一次)接收.與此同時, 若是您實現 exactly-once output operation (一次性輸出操做), 您能夠實現 end-to-end exactly-once guarantees (端到端的一次且僅一次性保證).在Kafka Integration Guide中進一步討論了這種方法.
Semantics of output operations (輸出操做的語義)
Output operations (輸出操做)(如foreachRDD)具備at-least once語義, 也就是說, transformed data (變換後的數據)可能會不止一次寫入 external entity (外部實體)在一個 worker 故障事件中.雖然這是能夠接受的使用saveAs***Files操做(由於文件將被相同的數據簡單地覆蓋) 保存到文件系統, 可能須要額外的努力來實現 exactly-once (一次且僅一次)語義.有兩種方法.
Idempotent updates (冪等更新): 屢次嘗試老是寫入相同的數據.例如,saveAs***Files老是將相同的數據寫入生成的文件.
Transactional updates (事務更新): 全部更新都是事務性的, 以便更新徹底按原子進行.這樣作的一個方法以下.
使用批處理時間(在foreachRDD中可用)和 RDD 的 partition index (分區索引)來建立 identifier (標識符).該標識符惟一地標識 streaming application 中的 blob 數據.
使用該 identifier (標識符)blob transactionally (blob 事務地)更新 external system (外部系統)(即, exactly once, atomically (一次且僅一次, 原子性地)).也就是說, 若是 identifier (標識符)還沒有提交, 則以 atomically (原子方式)提交 partition data (分區數據)和 identifier (標識符).不然, 若是已經提交, 請跳過更新.
dstream.foreachRDD { (rdd, time) =>
rdd.foreachPartition { partitionIterator =>
val partitionId = TaskContext.get.partitionId()
val uniqueId = generateUniqueId(time.milliseconds, partitionId)
// use this uniqueId to transactionally commit the data in partitionIterator
}
}
快速連接
附加指南
第三方 DStream 數據源能夠在第三方項目上查看.
API 文檔
Scala 文檔
KafkaUtils,FlumeUtils,KinesisUtils,
Java 文檔
JavaStreamingContext,JavaDStream和JavaPairDStream
KafkaUtils,FlumeUtils,KinesisUtils
Python 文檔
描述 Spark Streaming 的Paper和video.
原文地址: http://spark.apachecn.org/docs/cn/2.2.0/streaming-programming-guide.html
網頁地址: http://spark.apachecn.org/
github: https://github.com/apachecn/spark-doc-zh(以爲不錯麻煩給個 Star,謝謝!~)
Joyyx 關注
2017.09.27 10:48* 字數 10941 閱讀 33評論 0喜歡 0
建立 streaming DataFrames 和 streaming Datasets
streaming DataFrames/Datasets 的模式接口和分區
streaming DataFrames/Datasets 上的操做
基礎操做 - Selection, Projection, Aggregation
Window Operations on Event Time (事件時間窗口操做)
處理 Late Data (遲到數據)和 Watermarking (水印)
Streaming Deduplication (Streaming 去重)
Arbitrary Stateful Operations (任意有狀態的操做)
Recovering from Failures with Checkpointing (從檢查點恢復故障)
概述
Structured Streaming (結構化流)是一種基於 Spark SQL 引擎構建的可擴展且容錯的 stream processing engine (流處理引擎)。您能夠以靜態數據表示批量計算的方式來表達 streaming computation (流式計算)。 Spark SQL 引擎將隨着 streaming data 持續到達而增量地持續地運行,並更新最終結果。您能夠使用 Scala , Java , Python 或 R 中的Dataset/DataFrame API來表示 streaming aggregations (流聚合), event-time windows (事件時間窗口), stream-to-batch joins (流到批處理鏈接) 等。在同一個 optimized Spark SQL engine (優化的 Spark SQL 引擎)上執行計算。最後,系統經過 checkpointing (檢查點) 和 Write Ahead Logs (預寫日誌)來確保 end-to-end exactly-once (端到端的徹底一次性) 容錯保證。簡而言之,Structured Streaming 提供快速,可擴展,容錯,end-to-end exactly-once stream processing (端到端的徹底一次性流處理),而無需用戶理解 streaming 。
在本指南中,咱們將向您介紹 programming model (編程模型) 和 APIs 。首先,咱們從一個簡單的例子開始 - 一個 streaming word count 。
快速示例
假設您想要保持從監聽 TCP socket 的 data server (數據服務器) 接收的 text data (文本數據)的運行的 word count 。 讓咱們看看如何使用 Structured Streaming 表達這一點。你能夠在Scala/Java/Python/R之中看到完整的代碼。 Let’s say you want to maintain a running word count of text data received from a data server listening on a TCP socket. Let’s see how you can express this using Structured Streaming. You can see the full code inScala/Java/Python/R。 而且若是您下載 Spark,您能夠直接運行這個例子。在任何狀況下,讓咱們逐步瞭解示例並瞭解它的工做原理。首先,咱們必須導入必要的 classes 並建立一個本地的 SparkSession ,這是與 Spark 相關的全部功能的起點。
importorg.apache.spark.sql.functions._importorg.apache.spark.sql.SparkSessionvalspark=SparkSession.builder.appName("StructuredNetworkWordCount").getOrCreate()importspark.implicits._
接下來,咱們建立一個 streaming DataFrame ,它表示從監聽 localhost:9999 的服務器上接收的 text data (文本數據),而且將 DataFrame 轉換以計算 word counts 。
// 建立表示從鏈接到 localhost:9999 的輸入行 stream 的 DataFramevallines=spark.readStream.format("socket").option("host","localhost").option("port",9999).load()// 將 lines 切分爲 wordsvalwords=lines.as[String].flatMap(_.split(" "))// 生成正在運行的 word countvalwordCounts=words.groupBy("value").count()
這個linesDataFrame 表示一個包含包含 streaming text data (流文本數據) 的無邊界表。此表包含了一列名爲 「value」 的 strings ,而且 streaming text data 中的每一 line (行)都將成爲表中的一 row (行)。請注意,這並非正在接收的任何數據,由於咱們只是設置 transformation (轉換),尚未開始。接下來,咱們使用.as[String]將 DataFrame 轉換爲 String 的 Dataset ,以便咱們能夠應用flatMap操做將每 line (行)切分紅多個 words 。所獲得的wordsDataset 包含全部的 words 。最後,咱們經過將 Dataset 中 unique values (惟一的值)進行分組並對它們進行計數來定義wordCountsDataFrame 。請注意,這是一個 streaming DataFrame ,它表示 stream 的正在運行的 word counts 。
咱們如今已經設置了關於 streaming data (流數據)的 query (查詢)。剩下的就是實際開始接收數據並計算 counts (計數)。爲此,咱們將其設置爲在每次更新時將完整地計數(由outputMode("complete")指定)發送到控制檯。而後使用start()啓動 streaming computation (流式計算)。
// 開始運行將 running counts 打印到控制檯的查詢valquery=wordCounts.writeStream.outputMode("complete").format("console").start()query.awaitTermination()
執行此代碼以後, streaming computation (流式計算) 將在後臺啓動。query對象是該 active streaming query (活動流查詢)的 handle (句柄),而且咱們決定使用awaitTermination()來等待查詢的終止,以防止查詢處於 active (活動)狀態時退出。
要實際執行此示例代碼,您能夠在您本身的Spark 應用程序編譯代碼,或者簡單地運行示例一旦您下載了 Spark 。咱們正在展現的是後者。您將首先須要運行 Netcat (大多數類 Unix 系統中的一個小型應用程序)做爲 data server 經過使用
$ nc -lk 9999
而後,在一個不一樣的終端,您能夠啓動示例經過使用
$ ./bin/run-example org.apache.spark.examples.sql.streaming.StructuredNetworkWordCount localhost9999
而後,在運行 netcat 服務器的終端中輸入的任何 lines 將每秒計數並打印在屏幕上。它看起來像下面這樣。
# 終端 1:# 運行 Netcat$ nc -lk9999apache sparkapache hadoop...
# 終端 2: 運行 StructuredNetworkWordCount$ ./bin/run-example org.apache.spark.examples.sql.streaming.StructuredNetworkWordCount localhost9999-------------------------------------------Batch:0-------------------------------------------+------+-----+|value|count|+------+-----+|apache|1||spark|1|+------+-----+-------------------------------------------Batch:1-------------------------------------------+------+-----+|value|count|+------+-----+|apache|2||spark|1||hadoop|1|+------+-----+...
Programming Model (編程模型)
Structured Streaming 的關鍵思想是將 live data stream (實時數據流)視爲一種正在不斷 appended (附加)的表。這造成了一個與 batch processing model (批處理模型)很是類似的新的 stream processing model (流處理模型)。您會將您的 streaming computation (流式計算)表示爲在一個靜態表上的 standard batch-like query (標準類批次查詢),而且 Spark 在unbounded(無界)輸入表上運行它做爲incremental(增量)查詢。讓咱們更加詳細地瞭解這個模型。
基本概念
將 input data stream (輸入數據流) 視爲 「Input Table」(輸入表)。每一個在 stream 上到達的 data item (數據項)就像是一個被 appended 到 Input Table 的新的 row 。
對輸入的查詢將生成 「Result Table」 (結果表)。每一個 trigger interval (觸發間隔)(例如,每 1 秒),新 row (行)將附加到 Input Table ,最終更新 Result Table 。不管什麼時候更新 result table ,咱們都但願將 changed result rows (更改的結果行)寫入 external sink (外部接收器)。
「Output(輸出)」 被定義爲寫入 external storage (外部存儲器)的內容。能夠以不一樣的模式定義 output :
Complete Mode(徹底模式)- 整個更新的 Result Table 將被寫入外部存儲。由 storage connector (存儲鏈接器)決定如何處理整個表的寫入。
Append Mode(附加模式)- 只有 Result Table 中自上次觸發後附加的新 rows(行) 將被寫入 external storage (外部存儲)。這僅適用於不指望更改 Result Table 中現有行的查詢。
Update Mode(更新模式)- 只有自上次觸發後 Result Table 中更新的 rows (行)將被寫入 external storage (外部存儲)(從 Spark 2.1.1 以後可用)。請注意,這與 Complete Mode (徹底模式),由於此模式僅輸出自上次觸發以來更改的 rows (行)。若是查詢不包含 aggregations (聚合),它將等同於 Append mode 。
請注意,每種模式適用於特定模型的查詢。這將在later詳細討論。
爲了說明這個模型的使用,咱們來了解一下上面章節的快速示例。第一個linesDataFrame 是 input table ,而且最後的wordCountsDataFrame 是 result table 。請注意,streaminglinesDataFrame 上的查詢生成wordCounts是exactly the same(徹底同樣的)由於它將是一個 static DataFrame (靜態 DataFrame )。可是,當這個查詢啓動時, Spark 將從 socket 鏈接中持續檢查新數據。若是有新數據,Spark 將運行一個 「incremental(增量)」 查詢,它會結合之前的 running counts (運行計數)與新數據計算更新的 counts ,以下所示。
這種模式與許多其餘 stream processing engines (流處理引擎)有着顯著不一樣。許多 streaming systems (流系統)要求用戶自己保持運行 aggregations (聚合),所以必需要考慮容錯,和數據一致性(at-least-once(至少一次), at-most-once (最多一次),exactly-once (徹底一次))。在這個模型中,當有新數據時, Spark 負責更新 Result Table ,從而減輕用戶對它的考慮。舉個例子,咱們來看一下這個模型如何處理對於基於 event-time 的處理和 late arriving (遲到)的數據。
處理 Event-time 和延遲數據
Event-time 是數據自己 embedded (嵌入)的時間。對於不少應用程序,您可能須要在此 event-time 進行操做。例如,若是要每分鐘獲取 IoT devices (設備)生成的 events 數,則可能但願使用數據生成的時間(即數據中的 event-time ),而不是 Spark 接收到它們的時間。這個 event-time 在這個模型中很是天然地表現出來 – 來自 devices (設備)的每一個 event 都是表中的一 row(行),而且 event-time 是 row (行)中的 column value (列值)。這容許 window-based aggregations (基於窗口的聚合)(例如每分鐘的 events 數)僅僅是 event-time 列上的特殊類型的 group (分組)和 aggregation (聚合) – 每一個 time window 是一個組,而且每一 row (行)能夠屬於多個 windows/groups 。所以,能夠在 static dataset (靜態數據集)(例如來自 collected device events logs (收集的設備事件日誌))以及 data stream 上一致地定義 event-time-window-based aggregation queries (基於事件時間窗口的聚合查詢),從而使用戶的使用壽命更加容易。
此外,這個模型天然地處理了比預計將根據它的 event-time 到達的數據晚到的數據。因爲 Spark 正在更新 Result Table , Spark 有完整的控制對當有遲到的數據時 updating old aggregates (更新舊的聚合),以及清理 old aggregates (舊聚合) 以限制 intermediate state data (中間體狀態數據)的大小。自 Spark 2.1 以來,咱們對於 watermarking 進行了支持,容許用戶指定 late data 的閾值,並容許引擎相應地清理舊狀態。這些將在後面的Window Operations部分解釋。
容錯語義
提供 end-to-end exactly-once semantics (端到端的徹底一次性語義)是 Structured Streaming 設計背後的關鍵目標之一。爲了實現這一點,咱們設計了 Structured Streaming sources , sinks 和 execution engine (執行引擎),以可靠的跟蹤處理確切進度,以便它能夠經過 restarting and/or reprocessing (從新啓動和/或從新處理)來處理任何類型的故障。假設每一個 streaming source 都具備 offsets (偏移量)(相似於 Kafka offsets 或 Kinesis sequence numbers (Kafka 偏移量或 Kinesis 序列號))來跟蹤 stream 中的 read position (讀取位置)。引擎使用 checkpointing (檢查點)並 write ahead logs (預寫日誌)記錄每一個 trigger (觸發器)中正在處理的數據的 offset range (偏移範圍)。 streaming sinks 設計爲處理後處理的 idempotent (冪等)。一塊兒使用 replayable sources (可重放源)和 idempotent sinks (冪等接收器), Structured Streaming 能夠確保在任何故障下end-to-end exactly-once semantics(端對端徹底一次性語義)。
API 使用 Datasets 和 DataFrames
自從 Spark 2.0 , DataFrame 和 Datasets 能夠表示 static (靜態), bounded data(有界數據),以及 streaming , unbounded data (無界數據)。相似於 static Datasets/DataFrames ,您能夠使用經常使用的 entry point (入口點)SparkSession(Scala/Java/Python/R文檔) 來從 streaming sources 中建立 streaming DataFrames/Datasets ,並將其做爲 static DataFrames/Datasets 應用相同的操做。若是您不熟悉 Datasets/DataFrames ,強烈建議您使用DataFrame/Dataset 編程指南來熟悉它們。
建立 streaming DataFrames 和 streaming Datasets
能夠經過DataStreamReader的接口 (Scala/Java/Python文檔 )來建立 Streaming DataFrames 並由SparkSession.readStream()返回。在R中,使用read.stream()方法。與建立 static DataFrame 的 read interface (讀取接口)相似,您能夠指定 source - data format (數據格式), schema (模式), options (選項)等的詳細信息。
Input Sources (輸入源)
在 Spark 2.0 中,有一些內置的 sources 。
File source(文件源)- 以文件流的形式讀取目錄中寫入的文件。支持的文件格式爲 text , csv , json , parquet 。有關更多的 up-to-date 列表,以及每種文件格式的支持選項,請參閱 DataStreamReader interface 的文檔。請注意,文件必須以 atomically (原子方式)放置在給定的目錄中,這在大多數文件系統中能夠經過文件移動操做實現。
Kafka source(Kafka 源)- 來自 Kafka 的 Poll 數據。它與 Kafka broker 的 0.10.0 或者更高的版本兼容。有關詳細信息,請參閱Kafka Integration 指南。
Socket source (for testing) (Socket 源(用於測試))- 從一個 socket 鏈接中讀取 UTF8 文本數據。 listening server socket (監聽服務器 socket)位於 driver 。請注意,這隻能用於測試,由於它不提供 end-to-end fault-tolerance (端到端的容錯)保證。
某些 sources 是不容錯的,由於它們不能保證數據在使用 checkpointed offsets (檢查點偏移量)故障以後能夠被從新使用。參見前面的部分fault-tolerance semantics。如下是 Spark 中全部 sources 的詳細信息。
SourceOptions(選項)Fault-tolerant(容錯)Notes(說明)
File source(文件源)path: 輸入路徑的目錄,而且與全部文件格式通用。
maxFilesPerTrigger: 每一個 trigger (觸發器)中要考慮的最大新文件數(默認是: 無最大值)
latestFirst: 是否先處理最新的新文件,當有大量積壓的文件時有用(默認: false)
fileNameOnly: 是否僅根據文件名而不是完整路徑檢查新文件(默認值: false)。將此設置爲 `true` ,如下文件將被視爲相同的文件,由於它們的文件名 "dataset.txt" 是相同的:
· "file:///dataset.txt"
· "s3://a/dataset.txt"
· "s3n://a/b/dataset.txt"
· "s3a://a/b/c/dataset.txt"
有關特定於 file-format-specific (文件格式)的選項,請參閱DataStreamReader(Scala/Java/Python/R) 中的相關方法。例如,對於 "parquet" 格式選項請參閱DataStreamReader.parquet()Yes支持 glob 路徑,可是不支持多個逗號分隔的 paths/globs 。
Socket Source(Socket 源)host: 鏈接到的 host ,必須指定
port: 鏈接的 port (端口),必須指定No
Kafka Source(Kafka 源)請查看Kafka Integration 指南.Yes
這裏有一些例子。
valspark:SparkSession=...// 從 socket 讀取 textvalsocketDF=spark.readStream.format("socket").option("host","localhost").option("port",9999).load()socketDF.isStreaming// 對於有 streaming sources 的 DataFrame 返回 TruesocketDF.printSchema// 讀取目錄內原子寫入的全部 csv 文件valuserSchema=newStructType().add("name","string").add("age","integer")valcsvDF=spark.readStream.option("sep",";").schema(userSchema)// 指定 csv 文件的模式.csv("/path/to/directory")// 等同於 format("csv").load("/path/to/directory")
這些示例生成無類型的 streaming DataFrames ,這意味着在編譯時不會檢查 DataFrame 的模式,僅在運行時在 query is submitted (查詢提交)的時候進行檢查。像map,flatMap等這樣的操做須要在編譯時知道這個類型。要作到這一點,您能夠使用與 static DataFrame 相同的方法將這些 untyped (無類型)的 streaming DataFrames 轉換爲 typed streaming Datasets (類型化的 streaming Datasets )。有關詳細信息,請參閱SQL 編程指南。此外,有關支持的 streaming sources 的更多詳細信息將在文檔後面討論。
streaming DataFrames/Datasets 的模式接口和分區
默認狀況下,基於文件的 sources 的 Structured Streaming 須要您指定 schema (模式),而不是依靠 Spark 自動 infer 。這種 restriction 確保了 consistent schema (一致的模式)將被用於 streaming query (流式查詢),即便在出現故障的狀況下也是如此。對於 ad-hoc use cases (特殊用例),您能夠經過將spark.sql.streaming.schemaInference設置爲true來從新啓用 schema inference (模式接口)。
當存在名爲/key=value/的子目錄而且列表將自動遞歸到這些目錄中時,會發生 Partition discovery (分區發現)。若是這些 columns (列)顯示在用戶提供的 schema 中,則它們將根據正在讀取的文件路徑由 Spark 進行填充。 構成 partitioning scheme (分區方案)的目錄 must be present when the query starts (必須在查詢開始時是存在的),而且必須保持 static 。例如,當/data/year=2015/存在時,能夠添加/data/year=2016/,可是更改 partitioning column (分區列)是無效的(即經過建立目錄/data/date=2016-04-17/)。
streaming DataFrames/Datasets 上的操做
您能夠對 streaming DataFrames/Datasets 應用各類操做 - 從 untyped (無類型), SQL-like operations (相似 SQL 的操做)(例如select,where,groupBy) 到 typed RDD-like operations (類型化的相似 RDD 的操做)(例如map,filter,flatMap)。有關詳細信息,請參閱SQL 編程指南。讓咱們來看看能夠使用的幾個示例操做。
基礎操做 - Selection, Projection, Aggregation
streaming 支持 DataFrame/Dataset 上的大多數常見操做。不支持的少數操做discussed later將在本節中討論(稍後討論)。
caseclassDeviceData(device:String,deviceType:String,signal:Double,time:DateTime)valdf:DataFrame=...// streaming DataFrame with IOT device data with schema { device: string, deviceType: string, signal: double, time: string }valds:Dataset[DeviceData]=df.as[DeviceData]// streaming Dataset with IOT device data// Select the devices which have signal more than 10df.select("device").where("signal > 10")// using untyped APIsds.filter(_.signal>10).map(_.device)// using typed APIs// Running count of the number of updates for each device typedf.groupBy("deviceType").count()// using untyped API// Running average signal for each device typeimportorg.apache.spark.sql.expressions.scalalang.typedds.groupByKey(_.deviceType).agg(typed.avg(_.signal))// using typed API
Window Operations on Event Time (事件時間窗口操做)
經過 Structured Streaming , sliding event-time window (滑動事件時間窗口)的 Aggregations (聚合)很簡單,與 grouped aggregations (分組聚合)很是類似。在 grouped aggregation (分組聚合)中,爲 user-specified grouping column (用戶指定的分組列)中的每一個惟一值維護 aggregate values (聚合值)(例如 counts )。在 window-based aggregations (基於窗口的聚合)的狀況下,針對每一個窗口的 event-time 維持 aggregate values (聚合值)。讓咱們用一個例子來理解這一點。
想象一下,咱們的快速示例被修改,而且 stream 如今包含生成 line 的時間的 line 。不運行 word counts ,咱們想 count words within 10 minute windows (在 10 分鐘內的窗口計數單詞),每 5 分鐘更新一次。也就是說,在 10 minute windows (10 分鐘的窗口之間)收到的 word counts 12:00 - 12:10, 12:05 - 12:15, 12:10 - 12:20 等。請注意, 12:00 - 12:10 表示數據在 12:00 以後但在 12:10 以前抵達。如今,考慮在 12:07 收到一個 word 。這個 word 應該增長對應於兩個窗口的計數 12:00 - 12:10 和 12:05 - 12:15 。所以, counts 將被兩者分組, grouping key (分組祕鑰)(即 word)和 window (窗口)(能夠從 event-time 計算)來 indexed (索引)。
result tables 將以下所示。
因爲這個 windowing (窗口)相似於 grouping (分組),在代碼中,您能夠使用groupBy()和window()操做來表示 windowed aggregations (窗口化的聚合)。您能夠看到如下示例Scala/Java/Python的完整代碼。
importspark.implicits._valwords=...// streaming DataFrame of schema { timestamp: Timestamp, word: String }// Group the data by window and word and compute the count of each groupvalwindowedCounts=words.groupBy(window($"timestamp","10 minutes","5 minutes"),$"word").count()
處理 Late Data (遲到數據)和 Watermarking (水印)
如今考慮如下若是其中一個 event 遲到應用程序會發生什麼。例如,想象一下,在 12:04 (即 event time )生成的 word 能夠在 12:11 被接收申請。應用程序應該使用 12:04 而不是 12:11 來更新 window12:00 - 12:10的較舊 counts 。發生這種狀況天然就是在咱們 window-based grouping (基於窗口的分組中) - Structured Streaming 能夠保持intermediate state 對於部分 aggregates (聚合)長時間,以便後期數據能夠 update aggregates of old windows correctly (更新聚合)舊窗口正確,以下圖所示。
可是,要運行此查詢幾天,系統必須綁定 the amount of intermediate in-memory state it accumulates (中間狀態累積的數量)。這意味着系統須要知道何時 old aggregate (老聚合)能夠從內存中的狀態丟失,由於這個應用程序不會在繼續接收 aggregate (該聚合)的更多late data (後期的數據)。爲了實現這一點,在 Spark 2.1 中,咱們介紹了watermarking(水印),讓引擎自動跟蹤數據中的 current event time (當前事件時間)並試圖相應地清理舊狀態。您能夠定義查詢的 watermark 指定 event time column (事件時間列)和數據預期的延遲閾值 event time (事件時間)。對於從T時間開始的特定窗口,引擎將保持狀態並容許 late data (延遲數據)更新狀態直到(max event time seen by the engine - late threshold > T)。換句話說, threshold (閾值)內的 late data (晚期數據)將被 aggregated ,但數據晚於閾值將被丟棄。讓咱們以一個例子來理解這一點。咱們能夠使用withWatermark()能夠輕鬆地定義上一個例子的 watermarking (水印),以下所示。
importspark.implicits._valwords=...// streaming DataFrame of schema { timestamp: Timestamp, word: String }// Group the data by window and word and compute the count of each groupvalwindowedCounts=words.withWatermark("timestamp","10 minutes").groupBy(window($"timestamp","10 minutes","5 minutes"),$"word").count()
在這個例子中,咱們正在定義查詢的 watermark 對 「timestamp」 列的值,並將 「10 minutes」 定義爲容許數據延遲的閾值。若是這個查詢以 Update output mode (更新輸出模式)運行(稍後在Output Modes部分中討論),引擎將不斷更新 Result Table 中窗口的 counts ,直到 window is older than the watermark (窗口比水印較舊),它滯後於 current event time (當前事件時間)列 「timestamp」 10分鐘。這是一個例子。
如圖所示,maximum event time tracked (引擎跟蹤的最大事件時間)是藍色虛線,watermark 設置爲(max event time - '10 mins')在每一個觸發的開始處是紅線。例如,當引擎觀察數據(12:14, dog)時,它爲下一個觸發器設置 watermark 爲12:04。該 watermark 容許 engine 保持 intermediate state (中間狀態)另外 10 分鐘以容許延遲 late data to be counted (要計數的數據)。例如,數據(12:09, cat)是 out of order and late (不正常的,並且延遲了),它落在了 windows12:05 - 12:15和12:10 - 12:20。由於它仍然在 watermark12:04以前的觸發器,引擎仍然將 intermediate counts (中間計數)保持爲狀態並正確 updates the counts of the related windows (更新相關窗口的計數)。然而,當 watermark 更新爲12:11時,window(12:00 - 12:10)的中間狀態被清除,全部 subsequent data (後續數據)(例如(12:04, donkey))被認爲是 「too late」 ,所以被忽視。請注意,每次觸發後,寫入 updated counts (更新的計數)(即紫色行)做爲 trigger output 進行 sink ,以下 Update mode 所示。
某些 sinks (接收器)(例如 文件)可能不支持更新模式所需的 fine-grained updates (細粒度更新)。 與他們一塊兒工做,咱們也支持 Append Mode (附加模式),只有final counts(最終計數)被寫入 sink 。這以下所示。
請注意,在 non-streaming Dataset (非流數據集)上使用withWatermark是不可行的。 因爲 watermark 不該該以任何方式影響任何批處理查詢,咱們將直接忽略它。
與以前的 Update Mode 相似,引擎維護 intermediate counts for each window (每一個窗口的中間計數)。可是,partial counts (部分計數)不會更新到 Result Table ,也不是寫入 sink 。 引擎等待遲到的 「10 mins」 計數,而後刪除 window < watermark 的 intermediate state (中間狀態),並追加最終 計數到 Result Table/sink 。 例如, window12:00 - 12:10的最終計數是僅在水印更新爲12:11以後附加到 Result Table 。
Conditions for watermarking to clean aggregation state(watermarking 清理聚合狀態的條件)重要的是要注意,watermarking 必須知足如下清理聚合查詢中的狀態的條件(從 Spark 2.1.1 開始,未來會更改)。
Output mode must be Append or Update.(輸出模式必須是追加或者更新)Complete mode 要求保留全部 aggregate data (聚合數據),所以不能使用 watermarking 去掉 intermediate state (中間狀態)。參見Output Modes部分,詳細說明每種輸出模式的語義。
aggregation (聚合)必須具備 event-time column (事件時間列)或 event-time column 上的window。
withWatermark必須被調用與聚合中使用的 timestamp column (時間戳列)相同的列。例如,df.withWatermark("time", "1 min").groupBy("time2").count()在 Append output mode 是無效的,由於 watermark 是從聚合列在不一樣的列上定義的。
在使用 watermark details 的 aggregation (聚合)以前必須調用withWatermark。例如,df.groupBy("time").count().withWatermark("time", "1 min")在 Append output mode 中是無效的。
Join 操做
Streaming DataFrames 能夠與 static DataFrames 鏈接,以建立新的 streaming DataFrames 。 這裏有幾個例子。
valstaticDf=spark.read....valstreamingDf=spark.readStream....streamingDf.join(staticDf,"type")// inner equi-join with a static DFstreamingDf.join(staticDf,"type","right_join")// right outer join with a static DF
Streaming Deduplication (Streaming 去重)
您能夠使用 events 中的 unique identifier (惟一標識符)對 data streams 中的記錄進行重複數據刪除。 這與使用惟一標識符列的 static 重複數據消除徹底相同。 該查詢將存儲先前記錄所需的數據量,以即可以過濾重複的記錄。 與 aggregations (聚合)相似,您能夠使用帶有或不帶有 watermarking 的重複數據刪除功能。
With watermark(使用 watermark )- 若是重複記錄可能到達的時間有上限,則能夠在 event time column (事件時間列)上定義 watermark ,並使用 guid 和 event time columns 進行重複數據刪除。 該查詢將使用 watermark 從之前的記錄中刪除舊的狀態數據,這些記錄不會再受到任何重複。 這界定了查詢必須維護的狀態量。
Without watermark (不適用 watermark )- 因爲當重複記錄可能到達時沒有界限,查詢未來自全部過去記錄的數據存儲爲狀態。
valstreamingDf=spark.readStream....// columns: guid, eventTime, ...// Without watermark using guid columnstreamingDf.dropDuplicates("guid")// With watermark using guid and eventTime columnsstreamingDf.withWatermark("eventTime","10 seconds").dropDuplicates("guid","eventTime")
Arbitrary Stateful Operations (任意有狀態的操做)
許多用例須要比 aggregations 更高級的狀態操做。例如,在許多用例中,您必須 track (跟蹤) data streams of events (事件數據流)中的 sessions (會話)。對於進行此類 sessionization (會話),您必須將 arbitrary types of data (任意類型的數據)保存爲 state (狀態),並在每一個 trigger 中使用 state using the data stream events (數據流事件對狀態)執行 arbitrary operations 。自從 Spark 2.2 ,能夠使用mapGroupsWithState操做和更強大的操做flatMapGroupsWithState來完成。這兩個操做都容許您在 grouped Datasets (分組的數據集)上應用用戶定義的代碼來更新用戶定義的狀態。有關更具體的細節,請查看 API文檔(Scala/Java) 和例子 (Scala/Java)。
不支持的操做
streaming DataFrames/Datasets 不支持一些 DataFrame/Dataset 操做。其中一些以下。
streaming Datasets 不支持 Multiple streaming aggregations (多個流聚合) (i.e. a chain of aggregations on a streaming DF)(即 streaming DF 上的聚合鏈)
streaming Datasets 不支持 Limit and take first N rows 。
streaming Datasets 上的 Distinct operations 不支持。
只有在 aggregation 和 Complete Output Mode 下,streaming Datasets 才支持排序操做。
有條件地支持 streaming 和 static Datasets 之間的 Outer joins 。
不支持使用 streaming Dataset 的 Full outer join
不支持在右側使用 streaming Dataset 的 Left outer join
不支持在左側使用 streaming Dataset 的 Right outer join
不支持兩種 streaming Datasets 之間的任何種類的 joins 。
此外,還有一些 Dataset 方法將不適用於 streaming Datasets 。他們是當即運行查詢並返回結果的操做,這在 streaming Dataset 上沒有意義。相反,這些功能能夠經過顯式啓動 streaming query 來完成(參見下一節)。
count()- 沒法從 streaming Dataset 返回 single count 。 而是使用ds.groupBy().count()返回一個包含 running count 的 streaming Dataset 。
foreach()- 而是使用ds.writeStream.foreach(...)(參見下一節).
show()- 而是使用 console sink (參見下一節).
若是您嘗試任何這些操做,您將看到一個AnalysisException,如 「operation XYZ is not supported with streaming DataFrames/Datasets」 。雖然其中一些可能在將來版本的 Spark 中獲得支持,還有其餘一些從根本上難以有效地實現 streaming data 。例如, input stream 的排序不受支持,由於它須要保留 track of all the data received in the stream (跟蹤流中接收到的全部數據)。 所以從根本上難以有效率地執行。
開始 Streaming Queries
一旦定義了 final result DataFrame/Dataset ,剩下的就是讓你開始 streaming computation 。 爲此,您必須使用DataStreamWriter(Scala/Java/Python文檔)經過Dataset.writeStream()返回。您將必須在此 interface 中指定如下一個或多個。
Details of the output sink ( output sink 的詳細信息):Data format, location, etc.
Output mode (輸出模式):指定寫入 output sink 的內容。
Query name (查詢名稱):可選,指定用於標識的查詢的惟一名稱。
Trigger interval (觸發間隔):可選,指定觸發間隔。 若是未指定,則系統將在上一次處理完成後當即檢查新數據的可用性。 若是因爲先前的處理還沒有完成而致使觸發時間錯誤,則系統將嘗試在下一個觸發點觸發,而不是在處理完成後當即觸發。
Checkpoint location (檢查點位置):對於能夠保證 end-to-end fault-tolerance (端對端容錯)能力的某些 output sinks ,請指定系統將寫入全部 checkpoint (檢查點)信息的位置。 這應該是與 HDFS 兼容的容錯文件系統中的目錄。 檢查點的語義將在下一節中進行更詳細的討論。
Output Modes (輸出模式)
有幾種類型的輸出模式。
Append mode (default) (附加模式(默認))- 這是默認模式,其中只有 自從 last trigger (上一次觸發)以來,添加到 Result Table 的新行將會是 outputted to the sink 。 只有添加到 Result Table 的行將永遠不會改變那些查詢才支持這一點。 所以,這種模式 保證每行只能輸出一次(假設 fault-tolerant sink )。例如,只有select,where,map,flatMap,filter,join等查詢支持 Append mode 。
Complete mode (徹底模式)- 每次觸發後,整個 Result Table 將被輸出到 sink 。 aggregation queries (聚合查詢)支持這一點。
Update mode (更新模式)- (自 Spark 2.1.1 可用) 只有 Result Table rows 自上次觸發後更新將被輸出到 sink 。更多信息將在之後的版本中添加。
不一樣類型的 streaming queries 支持不一樣的 output modes 。 如下是兼容性矩陣。
Query Type(查詢類型)Supported Output Modes(支持的輸出模式)Notes(說明)
Queries with aggregation (使用聚合的查詢)Aggregation on event-time with watermark (使用 watermark 的 event-time 聚合 )Append, Update, Complete (附加,更新,徹底)Append mode 使用 watermark 來下降 old aggregation state (舊聚合狀態)。 但輸出 windowed aggregation (窗口聚合)延遲在 `withWatermark()` 中指定的 late threshold (晚期閾值)模式語義,rows 只能在 Result Table 中添加一次在 finalized (最終肯定)以後(即 watermark is crossed (水印交叉)後)。 有關詳細信息,請參閱Late Data部分。
Update mode 使用 watermark 刪除 old aggregation state (舊的聚合狀態)。
Complete mode (徹底模式)不會刪除舊的聚合狀態,由於從定義這個模式 保留 Result Table 中的全部數據。
Other aggregations (其餘聚合)Complete, Update (徹底,更新)因爲沒有定義 watermark(僅在其餘 category 中定義),舊的聚合狀態不會刪除。
不支持 Append mode ,由於 aggregates (聚合)能夠更新,從而違反了這種模式的語義。
Queries withmapGroupsWithStateUpdate (更新)
Queries withflatMapGroupsWithStateAppend operation mode (附加操做模式)Append (附加)flatMapGroupsWithState以後容許 Aggregations (聚合)。
Update operation mode (更新操做模式)Update(更新)flatMapGroupsWithState以後不容許 Aggregations (聚合)。
Other queries (其餘查詢)Append, Update (附加,更新)不支持 Complete mode ,由於將全部未分組數據保存在 Result Table 中是不可行的 。
Output Sinks (輸出接收器)
有幾種類型的內置輸出接收器。
File sink (文件接收器)- 將輸出存儲到目錄中。
writeStream.format("parquet")// can be "orc", "json", "csv", etc..option("path","path/to/destination/dir").start()
Foreach sink- 對 output 中的記錄運行 arbitrary computation 。 有關詳細信息,請參閱本節後面部分。
writeStream.foreach(...).start()
Console sink (for debugging) (控制檯接收器(用於調試))- 每次觸發時,將輸出打印到 console/stdout 。 都支持 Append 和 Complete 輸出模式。 這應該用於低數據量的調試目的,由於在每次觸發後,整個輸出被收集並存儲在驅動程序的內存中。
writeStream.format("console").start()
Memory sink (for debugging) (內存 sink (用於調試))- 輸出做爲 in-memory table (內存表)存儲在內存中。都支持 Append 和 Complete 輸出模式。 這應該用於調試目的在低數據量下,整個輸出被收集並存儲在驅動程序的存儲器中。所以,請謹慎使用。
writeStream.format("memory").queryName("tableName").start()
某些 sinks 是不容錯的,由於它們不能保證輸出的持久性而且僅用於調試目的。參見前面的部分容錯語義。如下是 Spark 中全部接收器的詳細信息。
Sink (接收器)Supported Output Modes (支持的輸出模式)Options (選項)Fault-tolerant (容錯)Notes (說明)
File Sink (文件接收器)Append (附加)path: 必須指定輸出目錄的路徑。
有關特定於文件格式的選項,請參閱 DataFrameWriter (Scala/Java/Python/R) 中的相關方法。 例如,對於 "parquet" 格式選項,請參閱DataFrameWriter.parquet()Yes支持對 partitioned tables (分區表)的寫入。按時間 Partitioning (劃分)多是有用的。
Foreach SinkAppend, Update, Compelete (附加,更新,徹底)None取決於 ForeachWriter 的實現。更多詳細信息在下一節
Console Sink (控制檯接收器)Append, Update, Complete (附加,更新,徹底)numRows: 每一個觸發器須要打印的行數(默認:20)
truncate: 若是輸出太長是否截斷(默認: true)No
Memory Sink (內存接收器)Append, Complete (附加,徹底)None否。可是在 Complete Mode 模式下,從新啓動的查詢將從新建立完整的表。Table name is the query name.(表名是查詢的名稱)
請注意,您必須調用start()來實際啓動查詢的執行。 這將返回一個 StreamingQuery 對象,它是連續運行的執行的句柄。 您能夠使用此對象來管理查詢,咱們將在下一小節中討論。 如今,讓咱們經過幾個例子瞭解全部這些。
// ========== DF with no aggregations ==========valnoAggDF=deviceDataDf.select("device").where("signal > 10")// Print new data to consolenoAggDF.writeStream.format("console").start()// Write new data to Parquet filesnoAggDF.writeStream.format("parquet").option("checkpointLocation","path/to/checkpoint/dir").option("path","path/to/destination/dir").start()// ========== DF with aggregation ==========valaggDF=df.groupBy("device").count()// Print updated aggregations to consoleaggDF.writeStream.outputMode("complete").format("console").start()// Have all the aggregates in an in-memory tableaggDF.writeStream.queryName("aggregates")// this query name will be the table name.outputMode("complete").format("memory").start()spark.sql("select * from aggregates").show()// interactively query in-memory table
使用 Foreach
foreach操做容許在輸出數據上計算 arbitrary operations 。從 Spark 2.1 開始,這隻適用於 Scala 和 Java 。爲了使用這個,你必須實現接口ForeachWriter(Scala/Java文檔) 其具備在 trigger (觸發器)以後生成 sequence of rows generated as output (做爲輸出的行的序列)時被調用的方法。請注意如下要點。
writer 必須是 serializable (可序列化)的,由於它將被序列化併發送給 executors 執行。
全部這三個方法,open,process和close都會在執行器上被調用。
只有當調用open方法時,writer 才能執行全部的初始化(例如打開鏈接,啓動事務等)。請注意,若是在建立對象時當即在類中進行任何初始化,那麼該初始化將在 driver 中發生(由於這是正在建立的實例),這可能不是您打算的。
version和partition是open中的兩個參數,它們獨特意表示一組須要被 pushed out 的行。version是每一個觸發器增長的單調遞增的 id 。partition是一個表示輸出分區的 id ,由於輸出是分佈式的,將在多個執行器上處理。
open能夠使用version和partition來選擇是否須要寫入行的順序。所以,它能夠返回true(繼續寫入)或false( 不須要寫入 )。若是返回false,那麼process不會在任何行上被調用。例如,在 partial failure (部分失敗)以後,失敗的觸發器的一些輸出分區可能已經被提交到數據庫。基於存儲在數據庫中的 metadata (元數據), writer 能夠識別已經提交的分區,所以返回 false 以跳過再次提交它們。
當open被調用時,close也將被調用(除非 JVM 因爲某些錯誤而退出)。即便open返回 false 也是如此。若是在處理和寫入數據時出現任何錯誤,那麼close將被錯誤地調用。您有責任清理以open建立的狀態(例如,鏈接,事務等),以避免資源泄漏。
管理 Streaming Queries
在啓動查詢時建立的StreamingQuery對象可用於 monitor and manage the query (監視和管理查詢)。
valquery=df.writeStream.format("console").start()// get the query objectquery.id// get the unique identifier of the running query that persists across restarts from checkpoint dataquery.runId// get the unique id of this run of the query, which will be generated at every start/restartquery.name// get the name of the auto-generated or user-specified namequery.explain()// print detailed explanations of the queryquery.stop()// stop the queryquery.awaitTermination()// block until query is terminated, with stop() or with errorquery.exception// the exception if the query has been terminated with errorquery.recentProgress// an array of the most recent progress updates for this queryquery.lastProgress// the most recent progress update of this streaming query
您能夠在單個 SparkSession 中啓動任意數量的查詢。 他們都將同時運行共享集羣資源。 您能夠使用sparkSession.streams()獲取StreamingQueryManager(Scala/Java/Python文檔) 可用於管理 currently active queries (當前活動的查詢)。
valspark:SparkSession=...spark.streams.active// get the list of currently active streaming queriesspark.streams.get(id)// get a query object by its unique idspark.streams.awaitAnyTermination()// block until any one of them terminates
監控 Streaming Queries
有兩個用於 monitoring and debugging active queries (監視和調試活動查詢) 的 API - interactively 和 asynchronously 。
Interactive APIs
您能夠直接獲取活動查詢的當前狀態和指標使用streamingQuery.lastProgress()和streamingQuery.status()。lastProgress()返回一個StreamingQueryProgress對象 在Scala和Java和 Python 中具備相同字段的字典。它有全部的信息在 stream 的最後一個觸發器中取得的 progress - 處理了哪些數據,處理率是多少,延遲等等。streamingQuery.recentProgress返回最後幾個進度的 array 。
另外,streamingQuery.status()返回一個StreamingQueryStatus對象在Scala和Java和 Python 中具備相同字段的字典。它提供有關的信息當即執行的查詢 - 觸發器是否 active ,數據是否正在處理等。
這裏有幾個例子。
valquery:StreamingQuery=...println(query.lastProgress)/* Will print something like the following.{"id" : "ce011fdc-8762-4dcb-84eb-a77333e28109","runId" : "88e2ff94-ede0-45a8-b687-6316fbef529a","name" : "MyQuery","timestamp" : "2016-12-14T18:45:24.873Z","numInputRows" : 10,"inputRowsPerSecond" : 120.0,"processedRowsPerSecond" : 200.0,"durationMs" : {"triggerExecution" : 3,"getOffset" : 2},"eventTime" : {"watermark" : "2016-12-14T18:45:24.873Z"},"stateOperators" : [ ],"sources" : [ {"description" : "KafkaSource[Subscribe[topic-0]]","startOffset" : {"topic-0" : {"2" : 0,"4" : 1,"1" : 1,"3" : 1,"0" : 1}},"endOffset" : {"topic-0" : {"2" : 0,"4" : 115,"1" : 134,"3" : 21,"0" : 534}},"numInputRows" : 10,"inputRowsPerSecond" : 120.0,"processedRowsPerSecond" : 200.0} ],"sink" : {"description" : "MemorySink"}}*/println(query.status)/* Will print something like the following.{"message" : "Waiting for data to arrive","isDataAvailable" : false,"isTriggerActive" : false}*/
Asynchronous API
您還能夠 asynchronously monitor (異步監視)與SparkSession相關聯的全部查詢 經過附加一個StreamingQueryListener(Scala/Javadocs) 。一旦你使用sparkSession.streams.attachListener()附加你的自定義StreamingQueryListener對象,當您啓動查詢和當有活動查詢有進度時中止時,您將收到 callbacks (回調)。 這是一個例子,
valspark:SparkSession=...spark.streams.addListener(newStreamingQueryListener(){overridedefonQueryStarted(queryStarted:QueryStartedEvent):Unit={println("Query started: "+queryStarted.id)}overridedefonQueryTerminated(queryTerminated:QueryTerminatedEvent):Unit={println("Query terminated: "+queryTerminated.id)}overridedefonQueryProgress(queryProgress:QueryProgressEvent):Unit={println("Query made progress: "+queryProgress.progress)}})
Recovering from Failures with Checkpointing (從檢查點恢復故障)
若是發生 failure or intentional shutdown (故障或故意關機),您能夠恢復以前的查詢的進度和狀態,並繼續中止的位置。 這是使用 checkpointing and write ahead logs (檢查點和預寫入日誌)來完成的。 您能夠使用 checkpoint location (檢查點位置)配置查詢,而且查詢將保存全部進度信息(即,每一個觸發器中處理的偏移範圍)和正在運行的 aggregates (聚合)(例如quick example中的 woed counts ) 到 checkpoint location (檢查點位置)。 此檢查點位置必須是 HDFS 兼容文件系統中的路徑,而且能夠在starting a query時將其設置爲DataStreamWriter 中的選項。
aggDF.writeStream.outputMode("complete").option("checkpointLocation","path/to/HDFS/dir").format("memory").start()
從這裏去哪兒
示例: 查看並運行Scala/Java/Python/R示例。
Spark Summit 2016 Talk -深刻 Structured Streaming
原文地址: http://spark.apachecn.org/docs/cn/2.2.0/structured-streaming-programming-guide.html
網頁地址: http://spark.apachecn.org/
github: https://github.com/apachecn/spark-doc-zh(以爲不錯麻煩給個 Star,謝謝!~)
Geekhoo 關注
2017.09.27 17:44* 字數 2564 閱讀 15評論 0喜歡 1
在 script in Spark的bin目錄中的spark-submit腳本用與在集羣上啓動應用程序。它能夠經過一個統一的接口使用全部 Spark 支持的cluster managers,因此您不須要專門的爲每一個cluster managers配置您的應用程序。
打包應用依賴
若是您的代碼依賴了其它的項目,爲了分發代碼到 Spark 集羣中您將須要將它們和您的應用程序一塊兒打包。爲此,建立一個包含您的代碼以及依賴的 assembly jar(或者 「uber」 jar)。不管是sbt仍是Maven都有 assembly 插件。在建立 assembly jar 時,列出 Spark 和 Hadoop的依賴爲provided。它們不須要被打包,由於在運行時它們已經被 Cluster Manager 提供了。若是您有一個 assembled jar 您就能夠調用bin/spark-submit腳本(以下所示)來傳遞您的 jar。
對於 Python 來講,您能夠使用spark-submit的--py-files參數來添加.py,.zip和.egg文件以與您的應用程序一塊兒分發。若是您依賴了多個 Python 文件咱們推薦將它們打包成一個.zip或者.egg文件。
用 spark-submit 啓動應用
若是用戶的應用程序被打包好了,它能夠使用bin/spark-submit腳原本啓動。這個腳本負責設置 Spark 和它的依賴的 classpath,而且能夠支持 Spark 所支持的不一樣的 Cluster Manager 以及 deploy mode(部署模式):
./bin/spark-submit\--class \--master \--deploy-mode \--conf =\...# other options\[application-arguments]
一些經常使用的 options(選項)有 :
--class: 您的應用程序的入口點(例如。org.apache.spark.examples.SparkPi)
--master: 集羣的master URL(例如spark://23.195.26.187:7077)
--deploy-mode: 是在 worker 節點(cluster) 上仍是在本地做爲一個外部的客戶端(client) 部署您的 driver(默認:client)†
--conf: 按照 key=value 格式任意的 Spark 配置屬性。對於包含空格的 value(值)使用引號包 「key=value」 起來。
application-jar: 包括您的應用以及全部依賴的一個打包的 Jar 的路徑。該 URL 在您的集羣上必須是全局可見的,例如,一個hdfs://path 或者一個file://在全部節點是可見的。
application-arguments: 傳遞到您的 main class 的 main 方法的參數,若是有的話。
†常見的部署策略是從一臺 gateway 機器物理位置與您 worker 在一塊兒的機器(好比,在 standalone EC2 集羣中的 Master 節點上)來提交您的應用。在這種設置中,client模式是合適的。在client模式中,driver 直接運行在一個充當集羣 client 的spark-submit進程內。應用程序的輸入和輸出直接連到控制檯。所以,這個模式特別適合那些設計 REPL(例如,Spark shell)的應用程序。
另外,若是您從一臺遠離 worker 機器的機器(例如,本地的筆記本電腦上)提交應用程序,一般使用cluster模式來下降 driver 和 executor 之間的延遲。目前,Standalone 模式不支持 Cluster 模式的 Python 應用。
對於 Python 應用,在的位置簡單的傳遞一個.py文件而不是一個 JAR,而且能夠用--py-files添加 Python.zip,.egg或者.py文件到 search path(搜索路徑)。
這裏有一些選項可用於特定的cluster manager中。例如,Spark standalone cluster用cluster部署模式, 您也能夠指定--supervise來確保 driver 在 non-zero exit code 失敗時能夠自動重啓。爲了列出全部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-cores100\/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-cores100\/path/to/examples.jar\1000# Run on a YARN clusterexportHADOOP_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-executors50\/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-cores100\http://path/to/examples.jar\1000
Master URLs
傳遞給 Spark 的 master URL 能夠使用下列格式中的一種 :
Master URLMeaning
local使用一個線程本地運行 Spark(即,沒有並行性)。
local[K]使用 K 個 worker 線程本地運行 Spark(理想狀況下,設置這個值的數量爲您機器的 core 數量)。
local[K,F]使用 K 個 worker 線程本地運行 Spark並容許最多失敗 F次 (查閱spark.task.maxFailures以獲取對該變量的解釋)
local[*]使用更多的 worker 線程做爲邏輯的 core 在您的機器上來本地的運行 Spark。
local[*,F]使用更多的 worker 線程做爲邏輯的 core 在您的機器上來本地的運行 Spark並容許最多失敗 F次。
spark://HOST:PORT鏈接至給定的Spark standalone clustermaster. master。該 port(端口)必須有一個做爲您的 master 配置來使用,默認是 7077。
spark://HOST1:PORT1,HOST2:PORT2鏈接至給定的Spark standalone cluster with standby masters with Zookeeper. 該列表必須包含由zookeeper設置的高可用集羣中的全部master主機。該 port(端口)必須有一個做爲您的 master 配置來使用,默認是 7077。
mesos://HOST:PORT鏈接至給定的Mesos集羣. 該 port(端口)必須有一個做爲您的配置來使用,默認是 5050。或者,對於使用了 ZooKeeper 的 Mesos cluster 來講,使用mesos://zk://.... 。使用--deploy-mode cluster, 來提交,該 HOST:PORT 應該被配置以鏈接到MesosClusterDispatcher.
yarn鏈接至一個YARNcluster inclientorclustermode 取決於--deploy-mode. 的值在 client 或者 cluster 模式中。該 cluster 的位置將根據HADOOP_CONF_DIR或者YARN_CONF_DIR變量來找到。
從文件中加載配置
spark-submit腳本能夠從一個 properties 文件加載默認的Spark configuration values而且傳遞它們到您的應用中去。默認狀況下,它將從 Spark 目錄下的conf/spark-defaults.conf讀取配置。更多詳細信息,請看加載默認配置.
加載默認的 Spark 配置,這種方式能夠消除某些標記到spark-submit. 的必要性。例如,若是spark.master屬性被設置了,您能夠在spark-submit中安全的省略--master配置 . 通常狀況下,明確設置在SparkConf上的配置值的優先級最高,而後是傳遞給spark-submit的值, 最後纔是 default value(默認文件)中的值。
若是您不是很清楚其中的配置設置來自哪裏,您能夠經過使用--verbose選項來運行spark-submit打印出細粒度的調試信息。
高級的依賴管理
在使用spark-submit時,使用--jars選項包括的應用程序的 jar 和任何其它的 jar 都將被自動的傳輸到集羣。在--jars後面提供的 URL 必須用逗號分隔。該列表會被包含到 driver 和 executor 的 classpath 中。--jars不支持目錄的形式。
Spark 使用下面的 URL 格式以容許傳播 jar 時使用不一樣的策略 :
file:- 絕對路徑和file:/URI 經過 driver 的 HTTP file server 提供服務,而且每一個 executor 會從 driver 的 HTTP server 拉取這些文件。
hdfs:,http:,https:,ftp:- 如預期的同樣拉取下載文件和 JAR
local:- 一個用 local:/ 開頭的 URL 預期做在每一個 worker 節點上做爲一個本地文件存在。這樣意味着沒有網絡 IO 發生,而且很是適用於那些已經被推送到每一個 worker 或經過 NFS,GlusterFS等共享的大型的 file/JAR。
N注意,那些 JAR 和文件被複制到 working directory(工做目錄)用於在 executor 節點上的每一個 SparkContext。這能夠使用最多的空間顯著量隨着時間的推移,將須要清理。在 Spark On YARN 模式中,自動執行清理操做。在 Spark standalone 模式中,能夠經過配置spark.worker.cleanup.appDataTtl屬性來執行自動清理。
用戶也能夠經過使用--packages來提供一個逗號分隔的 maven coordinates(maven 座標)以包含任何其它的依賴。在使用這個命令時全部可傳遞的依賴將被處理。其它的 repository(或者在 SBT 中被解析的)能夠使用--repositories該標記添加到一個逗號分隔的樣式中。 (注意,對於那些設置了密碼保護的庫,在一些狀況下能夠在庫URL中提供驗證信息,例如https://user:password@host/....以這種方式提供驗證信息須要當心。) 這些命令能夠與pyspark,spark-shell和spark-submit配置會使用以包含 Spark Packages(Spark 包)。 對於 Python 來講,也能夠使用--py-files選項用於分發.egg,.zip和.pylibraries 到 executor 中。
# 更多信息
若是您已經部署了您的應用程序,集羣模式概述描述了在分佈式執行中涉及到的組件,以及如何去監控和調試應用程序。
原文地址: http://spark.apachecn.org/docs/cn/2.2.0/submitting-applications.html
網頁地址: http://spark.apachecn.org/
github: https://github.com/apachecn/spark-doc-zh(以爲不錯麻煩給個 Star,謝謝!~)