from: http://www.linuxidc.com/Linux/2016-03/129506.htm
目前按照大數據處理類型來分大致可以分為:批量數據處理、交互式數據查詢、實時數據流處理,這三種數據處理方式對應的業務場景也都不一樣;
關注大數據處理的應該都知道Hadoop,而Hadoop的核心為HDFS與MapReduce,HDFS分布式文件系統在Hadop中是用來存儲數據的;MapReduce為Hadoop處理數據的核心,接觸過函數式編程的都知道函數式語言中也存在著Map、Reduce函數其實這兩者的思想是一致的;也正是因為Hadoop數據處理核心為MapReduce奠定了它注定不是適用場景廣泛的大數據框架;
可以這么說Hadoop適用于Map、Reduce存在的任何場景,具體場景比如:WordCount、排序、PageRank、用戶行為分析、數據統計等,而這些場景都算是批量數據處理,而Hadoop并不適用于交互式數據查詢、實時數據流處理;
這時候就出現了各種數據處理模型下的專用框架如:Storm、Impala、GraphLab等;
1、Storm:針對實時數據流處理的分布式框架;
2、Impala:適用于交互式大數據查詢的分布式框架;
3、GraphLab:基于圖模型的機器學習框架;
1、MapReduce簡單模型
這時候如果一個團隊或一個公司中同時都有設計到大數據批量處理、交互式查詢、實時數據流處理這三個場景;這時候就會有一些問題:
1、學習成本很高,每個框架都是不同的實現語言、不同的團隊開發的;
2、各個場景組合起來代價必然會很大;
3、各個框架中共享的中間數據共享與移動成本高;
就在這時候UC Berkeley AMP推出了全新的大數據處理框架:Spark提供了全面、統一適用與不同場景的大數據處理需求(批量數據處理、交互式數據查詢、實時數據流處理、機器學習);Spark不僅性能遠勝于Hadoop而卻還兼容Hadoop生態系統,Spark可以運行在Hadoop HDFS之上提供爭強 功能,可以說Spark替代了Hadoop MapReduce,但Spark依然兼容Hadoop中的YARN與Apache Mesos組件,現有Hadoop用戶可以很容易就遷移到Spark;
Spark提出了RDD(Resilient Distributed Datasets)這么一個全新的概念,RDD彈性分布式數據集是并行、容錯的分布式數據結構;RDD可以持久化到硬盤或內存當中,為一個分區的數據集,分區的多少決定了并行計算的粒度;并且提供了一系列的操作RDD中的數據:
1、創建操作(Creation Operation):RDD由SparkContext通過內存數據或外部文件系統創建;
2、轉換操作(Transformation Operation):將RDD通過轉換操作變為另一個RDD,Spark提供了map、flatMap、filter等一系列的轉換操作;
3、控制操作(Control Operation):將RDD持久化到內存或硬盤當中,如cache將filterRDD緩存到內存;
4、行動操作:(Action Operation):Spark采用了惰性計算,對于任何行動操作都會產生Spark Job運行產生最終結果;提供了join、groupBy、count等操作,Spark中存在兩種操作產生的結果為Scala集合或者標量與RDD保存到文件或數據庫;
1、Spark結構圖
Spark RDD:Spark RDD提供了一系列的操作接口,為不變的數據存儲結構并存儲與內存中使用DAG進行任務規劃使更好的處理MapReduce類似的批處理;
Shark/Spark SQL:分布式SQL引擎,兼容Hive性能遠比Hive高很多;
Spark Streaming:將數據流分解為一系列批處理作業使用Spark調度框架更好的支持數據流操作,支持的數據輸入源有:Kafka、Flume等;
GraphX:兼容Pregel、GraphLab接口為基于Spark的圖計算框架;
MLlib:為Spark的機器學習算法庫,支持常用的算法有:分類算法、推薦算法、聚類算法等等;
性能卓越、支持多種大數據處理模型、支持多種編程語言接口:Java、Scala、Python,許多大公司如IBM等大力支持推廣Spark的發展;
前面簡單的介紹了Spark的一些概念還有Spark生態圈的一些情況,這里主要是介紹Spark運行模式與Spark Standalone模式的部署;
在Spark中存在著多種運行模式,可使用本地模式運行、可使用偽分布式模式運行、使用分布式模式也存在多種模式如:Spark Mesos模式、Spark YARN模式;
Spark Mesos模式:官方推薦模式,通用集群管理,有兩種調度模式:粗粒度模式(Coarse-grained Mode)與細粒度模式(Fine-grained Mode);
Spark YARN模式:Hadoop?YARN資源管理模式;
Standalone模式:?簡單模式或稱獨立模式,可以單獨部署到一個集群中,無依賴任何其他資源管理系統。不使用其他調度工具時會存在單點故障,使用Zookeeper等可以解決;
Local模式:本地模式,可以啟動本地一個線程來運行job,可以啟動N個線程或者使用系統所有核運行job;
Standalone模式需要將Spark復制到集群中的每個節點,然后分別啟動每個節點即可;Spark Standalone模式的集群由Master與Worker節點組成,程序通過與Master節點交互申請資源,Worker節點啟動Executor運行;
這里使用了兩節點部署Spark集群:192.168.2.131、192.168.2.133,下面簡稱為:133與131節點;其中133節點既是Master節點同時又是Worker節點,131節點為Worker節點;
節點結構圖
部署步驟:
一、
首先在133節點上下載Java、Scala與Spark并解壓到/usr/local目錄下,這里使用的Spark是帶有Hadoop的版本;
下載解壓到local
二、配置Java、Scala與Spark環境變量,這里把環境變量配置到/etc/profile文件中,請忽略Hadoop環境變量;
環境變量配置
三、測試Java、Scala是否配置成功,在終端輸入:java -version與scala -version
四、配置Spark環境變量,進入Spark目錄下的conf目錄把slaves.template重命名為slaves,接著把spark-env.sh.template重命名為:spark-env.sh;
重命名
修改spark-env.sh文件,添加環境變量;
spark-env修改
五、
在133節點使用scp把下載好的Java、Scala、Spark發送到131節點,并在131節點上重復以上所有步驟;
六、在兩個節點都完成以上所有步驟后開始啟動Spark,133節點既是Master又是Worker;
1、首先在133啟動Spark,進入Spark目錄的sbin目錄執行./start-all.sh:
Master啟動
使用jps命令發現存在Master與Worker進程,說明Spark已啟動成功;
2、啟動131節點的Spark,進入Spark目錄的sbin目錄執行:./start-slave.sh spark://192.168.2.133:7077
start-slave.sh后面的地址為Master節點的通信地址,指定當前slave節點連接到的Master;
slave啟動:
使用jps命令,存在Worker進程則說明當前的Spark Worker節點啟動成功;
七、?Spark Web頁面
可以通過http://192.168.2.133:8080/ 地址查看到當前Spark集群的信息,這地址為Master節點的地址;
SparkWeb:
參考資料:
http://spark.apache.org/docs/latest/spark-standalone.html
Spark中最核心的概念為RDD(Resilient Distributed DataSets)中文為:彈性分布式數據集,RDD為對分布式內存對象的 抽象它表示一個被分區不可變且能并行操作的數據集;RDD為可序列化的、可緩存到內存對RDD進行操作過后還可以存到內存中,下次操作直接把內存中RDD作為輸入,避免了Hadoop?MapReduce的大IO操作;
Spark所要處理的任何數據都是存儲在RDD之中,目前兩種方式可以生成一個RDD:
1、從RDD進行轉換操作
2、使用外部存儲系統創建,如:HDFS;
RDD支持兩種操作:
轉換(transformation operation)
轉換操作將一個RDD經過操作后返回一個全新的RDD,轉換操是lazy(惰性)的這期間不會產生任何數據的計算;
轉換函數有:distinct、filter、map、flatMap、union、groupByKey等;
行動(action operation)
每一個行動操作都會觸發Spark Job進行計算并返回最終的結果,行動操作有這么幾類:返回標量,count返回元素的個數;返回Scala集合,task(n)返回0到n-1組成的集合;寫入外部存儲,saveAsHadoopFile(path)存儲到HDFS;
行動函數有:count、top、task、saveAsHadoopFile等;
RDD為不可變的數據集,可以使用轉換操作“修改”一個RDD,但這操作過后返回的是一個全新的RDD 原本RDD并沒有改變;
RDD狀態轉換圖
Spark RDD只支持粗粒度的操作,對一個RDD的操作都會被作用于該RDD的所有數據;為了保證RDD的高可用性RDD通過使用Lineage(血統)記錄了RDD演變流程(從其他RDD到當前RDD所做的操作) 當RDD分區數據丟失時可以通過Lineage的信息重新計算與恢復分區數據,或進行RDD的重建;
RDD的依賴關系(dependencies):
由于對RDD的操作都是粗粒度的一個轉換操作過后都會產生一個新的RDD,RDD之間會形成一個前后依賴關系;Spark中存在兩種依賴:窄依賴(Narrow Dependencies)、寬依賴(Wide Dependencies);
窄依賴(Narrow Dependencies):一個父RDD的分區只能被一個子RDD的一個分區使用;
寬依賴(Wide Dependencies):多個子RDD的分區依賴于一個父RDD的同一個分區;
窄依賴的節點(RDD)關系如果流水一般,所以當節點失敗后只需重新計算父節點的分區即可,寬依賴需要重新計算父節點的多個分區代價是非常昂貴的;
窄依賴Narrow
寬依賴Wide
參考資料:
http://www.cs.berkeley.edu/~matei/papers/2012/nsdi_spark.pdf
http://spark.apache.org/docs/latest/programming-guide.html
Spark支持Maven與SBT兩種編譯工具,這里使用了Maven進行編譯打包;
在執行make-distribution腳本時它會檢查本地是否已經存在Maven還有當前Spark所依賴的Scala版本,如果不存在它會自動幫你下載到build目錄中并解壓使用;Maven源最好配置成OSChina的中央庫,這下載依賴包比較快;
耐心等待,我編譯過多次所以沒有下載依賴包,大概半個小時左右編譯完成;注意:如果使用的是Java 1.8需要給JVM配置堆與非堆內存,如:export MAVEN_OPTS="-Xmx1.5g -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=512m";
進入Spark根目錄下,執行:
./make-distribution.sh--tgz--tgz 參數是指編譯后生成tgz包? - PHadoop支持Hadoop -Pyarn :支持yarn -Phive :支持hive--with-tachyon:支持tachyon內存文件系統-name:與--tgz一起用時,name代替Hadoop版本號./make-distribution.sh--tgz --name 2.6.0 -Pyarn -Phadoop-2.6 -Phive
開始編譯檢查本地環境,如不存在合適的Scala與Maven就在后臺下載;
編譯中:
編譯完成并打包生成tgz:
編譯完成后把生成的文件拷貝到當前Spark的dist目錄中并且打包生成spark-1.5.3-SNAPSHOT-bin-2.2.0.tgz文件;
Spark執行不少操作時都依賴于閉包函數的調用,此時如果閉包函數使用到了外部變量驅動程序在使用行動操作時傳遞到集群中各worker節點任務時就會進行一系列操作:
1、驅動程序使將閉包中使用變量封裝成對象,驅動程序序列化對象,傳給worker節點任務;
2、worker節點任務接收到對象,執行閉包函數;
由于使用外部變量勢必會通過網絡、序列化、反序列化,如外部變量過大或過多使用外部變量將會影響Spark程序的性能;
Spark提供了兩種類型的共享變量(Shared Variables):廣播變量(Broadcast Variables)、累加器(Accumulators );
廣播變量(Broadcast Variables)
Spark提供的廣播變量可以解決閉包函數引用外部大變量引起的性能問題;廣播變量將只讀變量緩存在每個worker節點中,Spark使用了高效廣播算法分發變量從而提高通信性能;如直接在閉包函數中使用外部 變量該變量會緩存在每個任務(jobTask)中如果多個任務同時使用了一個大變量勢必會影響到程序性能;
廣播變量:每個worker節點中緩存一個副本,通過高效廣播算法提高傳輸效率,廣播變量是只讀的;
Spark Scala Api與Java Api默認使用了Jdk自帶序列化庫,通過使用第三方或使用自定義的序列化庫還可以進一步提高廣播變量的性能;
廣播變量使用示例:
valsc = SparkContext("");valeigenValue = sc.bradcast(loadEigenValue())valeigen = computer.map{x =>valtemp = eigenValue.value? ? ...? ? ...}
左節點不使用廣播變量,右使用廣播變量
累加器(Accumulators)
累加器可以使得worker節點中指定的值聚合到驅動程序中,如統計Spark程序執行過程中的事件總數等;
valsc =newSparkContext(...)valfile = sc.textFile("xxx.txt")valeventCount = sc.accumulator(0,"EventAccumulator")//累加器初始值為0valformatEvent = file.flatMap(line => {if(line.contains("error")){? ? ? ? eventCount +=1}? ? })formatEvent.saveAsTextFile("eventData.txt")println("error event count : "+ eventCount);
在使用累加器(Accumulators)時需要注意,只有在行動操作中才會觸發累加器,也就是說上述代碼中由于flatMap()為轉換操作因為Spark惰性特征所以只用當saveAsTextFile() 執行時累加器才會被觸發;累加器只有在驅動程序中才可訪問,worker節點中的任務不可訪問累加器中的值;
Spark原生支持了數字類型的的累加器如:Int、Double、Long、Float等;此外Spark還支持自定義累加器用戶可以通過繼承AccumulableParam特征來實現自定義的累加器此外Spark還提供了accumulableCollection()累加集合用于;創建累加器時可以使用名字也可以不是用名字,當使用了名字時在Spark UI中可看到當中程序中定義的累加器, 廣播變量存儲級別為MEMORY_AND_DISK;
Spark作為分布式的大數據處理框架必然或涉及到大量的作業調度,如果能夠理解Spark中的調度對我們編寫或優化Spark程序都是有很大幫助的;
在Spark中存在轉換操作(Transformation Operation)與?行動操作(Action Operation)兩種;而轉換操作只是會從一個RDD中生成另一個RDD且是lazy的,Spark中只有行動操作(Action Operation)才會觸發作業的提交,從而引發作業調度;在一個計算任務中可能會多次調用 轉換操作這些操作生成的RDD可能存在著依賴關系,而由于轉換都是lazy所以當行動操作(Action Operation )觸發時才會有真正的RDD生成,這一系列的RDD中就存在著依賴關系形成一個DAG(Directed Acyclc Graph),在Spark中DAGScheuler是基于DAG的頂層調度模塊;
Application:使用Spark編寫的應用程序,通常需要提交一個或多個作業;
Job:在觸發RDD Action操作時產生的計算作業
Task:一個分區數據集中最小處理單元也就是真正執行作業的地方
TaskSet:由多個Task所組成沒有Shuffle依賴關系的任務集
Stage:一個任務集對應的調度階段 ,每個Job會被拆分成諾干個Stage
1.1 作業調度關系圖
這里根據Spark源碼跟蹤觸發Action操作時觸發的Job提交流程,Count()是RDD中的一個Action操作所以調用Count時會觸發Job提交;
在RDD源碼count()調用SparkContext的runJob,在runJob方法中根據partitions(分區)大小創建Arrays存放返回結果;
RDD.scala/**
* Return the number of elements in the RDD.
*/defcount():Long= sc.runJob(this,Utils.getIteratorSize_).sumSparkContext.scaladef runJob[T,U:ClassTag](? rdd:RDD[T],func:(TaskContext, Iterator[T])=>U,? partitions:Seq[Int],? resultHandler: (Int,U) =>Unit):Unit= {? val callSite = getCallSite? val cleanedFunc = clean(func)logInfo("Starting job: "+ callSite.shortForm)if(conf.getBoolean("spark.logLineage",false)) {? ? logInfo("RDD's recursive dependencies:\n"+ rdd.toDebugString)? }? dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, resultHandler, localProperties.get)}
在SparkContext中將調用DAGScheduler的runJob方法提交作業,DAGScheduler主要任務是計算作業與任務依賴關系,處理調用邏輯;DAGScheduler提供了submitJob與runJob方法用于 提交作業,runJob方法會一直等待作業完成,submitJob則返回JobWaiter對象可以用于判斷作業執行結果;
在runJob方法中將調用submitJob,在submitJob中把提交操作放入到事件循環隊列(DAGSchedulerEventProcessLoop)中;
def submitJob[T, U](? rdd:RDD[T],? func:(TaskContext, Iterator[T]) => U,? partitions:Seq[Int],? callSite:CallSite,? resultHandler:(Int, U) => Unit,? properties:Properties): JobWaiter[U] = {? ? ? ......? ? ? ? eventProcessLoop.post(JobSubmitted(? ? ? jobId, rdd, func2, partitions.toArray, callSite, waiter,? ? ? SerializationUtils.clone(properties)))? ? ? ......? }
在事件循環隊列中將調用eventprocessLoop的onReceive方法;
提交作業時DAGScheduler會從RDD依賴鏈尾部開始,遍歷整個依賴鏈劃分調度階段;劃分階段以ShuffleDependency為依據,當沒有ShuffleDependency時整個Job 只會有一個Stage;在事件循環隊列中將會調用DAGScheduler的handleJobSubmitted方法,此方法會拆分Stage、提交Stage;
private[scheduler] def handleJobSubmitted(jobId:Int,? finalRDD:RDD[_],func:(TaskContext, Iterator[_])=>_,? partitions:Array[Int],? callSite:CallSite,? listener:JobListener,? properties:Properties) {varfinalStage:ResultStage= null......? finalStage = newResultStage(finalRDD,func,partitions,jobId,callSite)......valjob=newActiveJob(jobId, finalStage, callSite, listener, properties)......val jobSubmissionTime = clock.getTimeMillis()jobIdToActiveJob(jobId) = jobactiveJobs += jobfinalStage.setActiveJob(job)val stageIds = jobIdToStageIds(jobId).toArrayval stageInfos = stageIds.flatMap(id => stageIdToStage.get(id).map(_.latestInfo))listenerBus.post(SparkListenerJobStart(job.jobId, jobSubmissionTime, stageInfos, properties))submitStage(finalStage)submitWaitingStages()}
在提交Stage時會先調用getMissingParentStages獲取父階段Stage,迭代該階段所依賴的父調度階段如果存在則先提交該父階段的Stage 當不存在父Stage或父Stage執行完成時會對當前Stage進行提交;
privatedefsubmitStage(stage:Stage) {valjobId = activeJobForStage(stage)if(jobId.isDefined) {if(!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {valmissing = getMissingParentStages(stage).sortBy(_.id)if(missing.isEmpty) {? ? ? ? submitMissingTasks(stage, jobId.get)? ? ? }else{for(parent <- missing) {? ? ? ? ? submitStage(parent)? ? ? ? }? ? ? ? waitingStages += stage? ? ? }? ? }? }? ......}
參考資料:
http://spark.apache.org/docs/latest/
Scala 的詳細介紹:請點這里
Scala 的下載地址:請點這里