Apache Spark入門級摘要

Apache Spark是一個圍繞速度、易用性和複雜分析構建的大數據處理框架,Spark有如下優勢:

  • Spark提供了一個全面、統一的框架用於管理各種有着不同性質(文本數據、圖表數據等)的數據集和數據源(批量數據或實時的流數據)的大數據處理的需求。
  • 官方資料介紹Spark可以將Hadoop集羣中的應用在內存中的運行速度提升100倍,甚至能夠將應用在磁盤上的運行速度提升10倍。

1. 架構及生態

其架構示意圖如下:

這裏寫圖片描述

  • Spark Core:包含Spark的基本功能;尤其是定義RDD的API、操作以及這兩者上的動作。其他Spark的庫都是構建在RDD和Spark Core之上的。
  • Spark SQL:用於處理結構化數據的模塊,每個數據庫表被當做一個RDD,Spark SQL查詢被轉換爲Spark操作。
  • Spark Streaming:對實時數據流進行處理和控制。Spark Streaming允許程序能夠像普通RDD一樣處理實時數據。
  • MLlib:一個常用機器學習算法庫,算法被實現爲對RDD的Spark操作。這個庫包含可擴展的學習算法,比如分類、迴歸等需要對大量數據集進行迭代的操作。
  • GraphX:控制圖、並行圖操作和計算的一組算法和工具的集合。GraphX擴展了RDD API,包含控制圖、創建子圖、訪問路徑上所有頂點的操作

Spark架構的組成圖如下:

這裏寫圖片描述

  • Cluster Manager:standalone模式中即爲Master主節點,控制整個集羣,監控Worker。在Yarn模式中爲資源管理器。
  • Worker節點:從節點,負責控制計算節點,啓動Executor或者Driver
  • Driver: 運行Application 的main()函數。
  • Executor:執行器,是爲某個Application運行在Worker node上的一個進程。

2. Spark與Hadoop

Hadoop的侷限和不足
但是,MapRecue存在以下侷限,使用起來比較困難。

  • 抽象層次低,需要手工編寫代碼來完成,使用上難以上手。
  • 只提供兩個操作,Map和Reduce,表達力欠缺。
  • 一個Job只有Map和Reduce兩個階段(Phase),複雜的計算需要大量的Job完成,Job之間的依賴關係是由開發者自己管理的。
  • 處理邏輯隱藏在代碼細節中,沒有整體邏輯。
  • 中間結果也放在HDFS文件系統中。
  • ReduceTask需要等待所有MapTask都完成後纔可以開始。
  • 時延高,只適用Batch數據處理,對於交互式數據處理,實時數據處理的支持不夠。
  • 對於迭代式數據處理性能比較差

(1)Hadoop有兩個核心模塊,分佈式存儲模塊HDFS和分佈式計算模塊MapReduce,Spark本身並沒有提供分佈式文件系統,因此Spark的磁盤存儲大多依賴於Hadoop的分佈式文件系統HDFS。

(2)Hadoop的MapReduce與Spark都可以進行數據計算。Spark與MapReduce最大的不同在於計算模型:

  • MapReduce,分爲兩個階段,map和reduce,兩個階段完了,就結束了,所以我們在一個job裏能做的處理很有限;
  • Spark,迭代計算模型,可以分爲n個階段,因爲它是內存迭代式的。我們在處理完一個階段以後,可以繼續往下處理很多個階段,而不只是兩個階段。

所以,Spark相較於MapReduce來說,計算模型可以提供更強大的功能。


3 Yarn框架介紹

Yarn是一個資源管理、任務調度的框架,主要包含三大模塊:ResourceManager(RM)、NodeManager(NM)、ApplicationMaster(AM)。

  • ResourceManager負責所有資源的監控、分配和管理;
  • ApplicationMaster負責每一個具體應用程序的調度和協調;
  • NodeManager負責每一個節點的維護。

對於所有的applications,RM擁有絕對的控制權和對資源的分配權。而每個AM則會和RM協商資源,同時和NodeManager通信來執行和監控task。幾個模塊之間的關係如圖所示:

這裏寫圖片描述

Yarn總體上仍然是master/slave結構,在整個資源管理框架中,ResourceManager爲master,NodeManager是slave。ResourceManager負責對各個NodeManger上資源進行統一管理和調度。當用戶提交一個應用程序時,需要提供一個用以跟蹤和管理這個程序的ApplicationMaster,它負責向ResourceManager申請資源,並要求NodeManger啓動可以佔用一定資源的任務。由於不同的ApplicationMaster被分佈到不同的節點上,因此它們之間不會相互影響。


3.1 ResourceManager

ResourceManager是Master上一個獨立運行的進程,負責集羣統一的資源管理、調度、分配等等;NodeManager是Slave上一個獨立運行的進程,負責上報節點的狀態;App Master和Container是運行在Slave上的組件,Container是Yarn中分配資源的一個單位,包涵內存、CPU等等資源,Yarn以Container爲單位分配資源。RM是一個全局的資源管理器,集羣只有一個,負責整個系統的資源管理和分配,包括處理客戶端請求、啓動/監控APP master、監控NodeManager、資源的分配與調度。它主要由兩個組件構成:調度器(Scheduler)和應用程序管理器(Applications Manager,ASM)。

(1) 調度器Scheduler

調度器根據容量、隊列等限制條件(如每個隊列分配一定的資源,最多執行一定數量的作業等),將系統中的資源分配給各個正在運行的應用程序。需要注意的是,該調度器是一個“純調度器”,它不再從事任何與具體應用程序相關的工作,比如不負責監控或者跟蹤應用的執行狀態等,也不負責重新啓動因應用執行失敗或者硬件故障而產生的失敗任務,這些均交由應用程序相關的ApplicationMaster完成。調度器僅根據各個應用程序的資源需求進行資源分配,而資源分配單位用一個抽象概念“資源容器”(Resource Container,簡稱Container)表示,Container是一個動態資源分配單位,它將內存、CPU、磁盤、網絡等資源封裝在一起,從而限定每個任務使用的資源量。此外,該調度器是一個可插拔的組件,用戶可根據自己的需要設計新的調度器,Yarn提供了多種直接可用的調度器,比如Fair Scheduler和Capacity Scheduler等。

(2) 應用程序管理器

應用程序管理器負責管理整個系統中所有應用程序,包括應用程序提交、與調度器協商資源以啓動ApplicationMaster、監控ApplicationMaster運行狀態並在失敗時重新啓動它等。


3.2 NodeManager

  • NodeManager是每個節點上的資源和任務管理器,它是管理這臺機器的代理,負責該節點程序的運行,以及該節點資源的管理和監控。Yarn集羣每個節點都運行一個NodeManager。
  • NodeManager定時向ResourceManager彙報本節點資源(CPU、內存)的使用情況和Container的運行狀態。當ResourceManager宕機時NodeManager自動連接RM備用節點。
  • NodeManager接收並處理來自ApplicationMaster的Container啓動、停止等各種請求。

3.3 ApplicationMaster

  • 用戶提交的每個應用程序均包含一個ApplicationMaster,它可以運行在ResourceManager以外的機器上。
  • 負責與RM調度器協商以獲取資源(用Container表示)。
  • 將得到的任務進一步分配給內部的任務(資源的二次分配)。
  • 與NM通信以啓動/停止任務。
  • 監控所有任務運行狀態,並在任務運行失敗時重新爲任務申請資源以重啓任務。
  • 當前Yarn自帶了兩個ApplicationMaster實現,一個是用於演示AM編寫方法的實例程序DistributedShell,它可以申請一定數目的Container以並行運行一個Shell命令或者Shell腳本;另一個是運行MapReduce應用程序的AM—MRAppMaster。
  • 注:RM只負責監控AM,並在AM運行失敗時候啓動它。RM不負責AM內部任務的容錯,任務的容錯由AM完成。

3.4 Yarn運行流程

  1. Client向RM提交應用程序,其中包括啓動該應用的ApplicationMaster的必須信息,例如ApplicationMaster程序、啓動ApplicationMaster的命令、用戶程序等。
  2. ResourceManager啓動一個Container用於運行ApplicationMaster
  3. 啓動中的ApplicationMasterResourceManager註冊自己,啓動成功後與RM保持心跳。
  4. ApplicationMasterResourceManager發送請求,申請相應數目的Container
  5. ResourceManager返回ApplicationMaster的申請的Containers信息。申請成功的Container,由ApplicationMaster進行初始化。Container的啓動信息初始化後,AM與對應的NodeManager通信,要求NM啓動Container。AM與NM保持心跳,從而對NM上運行的任務進行監控和管理。
  6. Container運行期間,ApplicationMasterContainer進行監控Container通過RPC協議向對應的AM彙報自己的進度和狀態等信息。
  7. 應用運行期間,Client直接與AM通信獲取應用的狀態、進度更新等信息。
  8. 應用運行結束後,ApplicationMasterResourceManager註銷自己,並允許屬於它的Container被收回

4. 運行流程及特點

4.1 基本概念

(1)Application: Appliction都是指用戶編寫的Spark應用程序,其中包括一個Driver功能的代碼和分佈在集羣中多個節點上運行的Executor代碼。

(2)Driver: Spark中的Driver即運行上述Application的main函數並創建SparkContext,創建SparkContext的目的是爲了準備Spark應用程序的運行環境,在Spark中有SparkContext負責與ClusterManager通信,進行資源申請、任務的分配和監控等,當Executor部分運行完畢後,Driver同時負責將SparkContext關閉,通常用SparkContext代表Driver。

(3)Executor: 某個Application運行在Worker節點上的一個進程, 該進程負責運行某些Task, 並且負責將數據存到內存或磁盤上,每個Application都有各自獨立的一批Executor。

(4)Cluter Manager:指的是在集羣上管理資源的外部服務。目前有三種類型

  • Standalon : Spark原生的資源管理,由Master負責資源的分配
  • Apache Mesos: 與hadoop MR兼容性良好的一種資源調度框架
  • Hadoop Yarn: 主要是指Yarn中的ResourceManager

(5)Worker: 集羣中任何可以運行Application代碼的節點,在Standalone模式中指的是通過slave文件配置的Worker節點,在Spark on Yarn模式下就是NoteManager節點。

(6)Task: 被送到某個Executor上的工作單元,但Hadoop MR中的MapTask和ReduceTask概念一樣,是運行Application的基本單位,多個Task組成一個Stage,而Task的調度和管理等是由TaskScheduler負責。

(7)Job: 包含多個Stage組成的並行計算,往往由Spark Action觸發生成。

(8)Stage: 每個Job會被拆分成多組Task, 作爲一個TaskSet, 其名稱爲Stage,Stage的劃分和調度是由DAGScheduler來負責的,Stage有非最終的Stage(Shuffle Map Stage)和最終的Stage(Result Stage)兩種,Stage的邊界就是發生shuffle的地方

(9)DAGScheduler: 根據Job構建基於Stage的DAG(Directed Acyclic Graph有向無環圖),並提交Stage給TASkScheduler。 其劃分Stage的依據是RDD之間的依賴的關係找出開銷最小的調度方法,如下圖

(10)TaskSedulter: 將TaskSet提交給Worker運行,每個Executor運行什麼Task就是在此處分配的. TaskScheduler維護所有TaskSet,當Executor向Driver發生心跳時,TaskScheduler會根據資源剩餘情況分配相應的Task。另外TaskScheduler還維護着所有Task的運行標籤,重試失敗的Task。下圖展示了TaskScheduler的作用
這裏寫圖片描述

在不同運行模式中任務調度器具體爲:

  • Spark on Standalone模式爲TaskScheduler
  • Yarn-Client模式爲YarnClientClusterScheduler
  • Yarn-Cluster模式爲YarnClusterScheduler

將這些術語串起來的運行層次圖如下:
這裏寫圖片描述

Job=多個stage,Stage=多個同種task, Task分爲ShuffleMapTask和ResultTask,Dependency分爲ShuffleDependency和NarrowDependency。


4.2 Spark運行流程

這裏寫圖片描述

  1. 構建Spark Application的運行環境,啓動SparkContext。
  2. SparkContext向資源管理器(可以是Standalone,Mesos,Yarn)申請運行Executor資源。
  3. Executor向SparkContext申請Task。
  4. SparkContext將應用程序分發給Executor。
  5. SparkContext構建成DAG圖,將DAG圖分解成Stage、將Taskset發送給Task Scheduler,最後由Task Scheduler將Task發送給Executor運行。
  6. Task在Executor上運行,運行完釋放所有資源。

4.3 Spark運行特點

  1. 每個Application獲取專屬的Executor進程,該進程在Application期間一直駐留,並以多線程方式運行Task。這種Application隔離機制是有優勢的,無論是從調度角度看(每個Driver調度他自己的任務),還是從運行角度看(來自不同Application的Task運行在不同JVM中),當然這樣意味着Spark Application不能跨應用程序共享數據,除非將數據寫入外部存儲系統。
  2. Spark與資源管理器無關,只要能夠獲取Executor進程,並能保持相互通信就可以了。
  3. 提交SparkContext的Client應該靠近Worker節點(運行Executor的節點),最好是在同一個Rack裏,因爲Spark Application運行過程中SparkContext和Executor之間有大量的信息交換。
  4. Task採用了數據本地性和推測執行的優化機制。

5. Spark運行模式

5.1 standalone獨立集羣

Standalone模式使用Spark自帶的資源調度框架,採用Master/Slaves的典型架構,選用ZooKeeper來實現Master的HA,框架結構圖如下:

這裏寫圖片描述

該模式主要的節點有Client節點、Master節點和Worker節點。其中Driver既可以運行在Master節點上中,也可以運行在本地Client端。當用Spark-shell交互式工具提交Spark的Job時,Driver在Master節點上運行;當使用Spark-submit工具提交Job或者在Eclips、IDEA等開發平臺上使用”new SparkConf.setManager(“Spark://master:7077”)”方式運行Spark任務時,Driver是運行在本地Client端上的

運行過程如下圖:
這裏寫圖片描述

  1. SparkContext連接到Master,向Master註冊並申請資源(CPU Core 和Memory)
  2. Master根據SparkContext的資源申請要求和Worker心跳週期內報告的信息決定在哪個Worker上分配資源,然後在該Worker上獲取資源,然後啓動StandaloneExecutorBackend;
  3. StandaloneExecutorBackend向SparkContext註冊;
  4. SparkContext將Applicaiton代碼發送給StandaloneExecutorBackend;並且SparkContext解析Applicaiton代碼,構建DAG圖,並提交給DAG Scheduler分解成Stage(當碰到Action操作時,就會催生Job;每個Job中含有1個或多個Stage,Stage一般在獲取外部數據和shuffle之前產生),然後以Stage(或者稱爲TaskSet)提交給Task Scheduler,Task Scheduler負責將Task分配到相應的Worker,最後提交給StandaloneExecutorBackend執行;
  5. StandaloneExecutorBackend會建立Executor線程池,開始執行Task,並向SparkContext報告,直至Task完成
  6. 所有Task完成後,SparkContext向Master註銷,釋放資源

5.2 Spark on YARN

當在YARN上運行Spark作業,每個Spark Executor作爲一個YARN容器(container)運行。Spark可以使得多個Tasks在同一個容器(Container)裏面運行。Spark on YARN有兩種模式,yarn-cluster適用於生產環境;而yarn-client適用於交互和調試,也就是希望快速地看到application的輸出。

在我們介紹yarn-cluster和yarn-client的深層次的區別之前,我們先明白一個概念:Application Master。在YARN中,每個Application實例都有一個Application Master進程,它是Application啓動的第一個容器。它負責和ResourceManager打交道,並請求資源。獲取資源之後告訴NodeManager爲其啓動Container。

5.2.1 yarn-cluster

yarn-cluster模式下,Driver運行在AM中,它負責向YARN申請資源,並監督作業的運行狀況。當用戶提交了作業之後,就可以關掉Client,作業會繼續在YARN上運行。然而yarn-cluster模式不適合運行交互類型的作業。

這裏寫圖片描述

5.2.2 yarn-client

而yarn-client模式下,Application Master僅僅向YARN請求executor,client會和請求的container通信來調度他們工作,也就是說Client不能離開。

這裏寫圖片描述


6. Spark的核心優勢RDD

彈性分佈式數據集(RDD,Resilient Distributed Datasets)支持基於工作集的應用,同時具有數據流模型的特點:自動容錯位置感知調度可伸縮性。RDD允許用戶在執行多個查詢時顯式地將工作集緩存在內存中,後續的查詢能夠重用工作集,這極大地提升了查詢速度。

6.1 主要特性

6.1.1 RDD抽象

RDD提供了一種高度受限的共享內存模型,即RDD是隻讀的記錄分區的集合只能通過在其他RDD執行轉換操作(如mapjoingroup by)而創建,然而這些限制使得實現容錯的開銷很低。與分佈式共享內存系統需要付出高昂代價的檢查點和回滾機制不同,RDD通過Lineage來重建丟失的分區:一個RDD中包含了如何從其他RDD衍生所必需的相關信息,從而不需要檢查點操作就可以重構丟失的數據分區。儘管RDD不是一個通用的共享內存抽象,但卻具備了良好的描述能力、可伸縮性和可靠性,能夠廣泛適用於數據並行類應用。

RDD是隻讀的、分區記錄的集合。RDD只能基於在穩定物理存儲中的數據集和其他已有的RDD上執行確定性操作來創建。這些確定性操作稱之爲轉換,如map、filter、groupBy、join(轉換不是程開發人員在RDD上執行的操作)。RDD不需要物化。RDD含有如何從其他RDD衍生(即計算)出本RDD的相關信息(即Lineage),據此可以從物理存儲的數據計算出相應的RDD分區。

轉換 map(f : T ) U) : RDD[T] ) RDD[U]filter(f : T ) Bool) : RDD[T] ) RDD[T]flatMap(f : T ) Seq[U]) : RDD[T] ) RDD[U]sample(fraction : Float) : RDD[T] ) RDD[T] (Deterministic sampling)groupByKey() : RDD[(K, V)] ) RDD[(K, Seq[V])]reduceByKey(f : (V; V) ) V) : RDD[(K, V)] ) RDD[(K, V)]union() : (RDD[T]; RDD[T]) ) RDD[T]join() : (RDD[(K, V)]; RDD[(K, W)]) ) RDD[(K, (V, W))]cogroup() : (RDD[(K, V)]; RDD[(K, W)]) ) RDD[(K, (Seq[V], Seq[W]))]crossProduct() : (RDD[T]; RDD[U]) ) RDD[(T, U)]mapValues(f : V ) W) : RDD[(K, V)] ) RDD[(K, W)] (Preserves partitioning)sort(c : Comparator[K]) : RDD[(K, V)] ) RDD[(K, V)]partitionBy(p : Partitioner[K]) : RDD[(K, V)] ) RDD[(K, V)]
動作 count() : RDD[T] ) Longcollect() : RDD[T] ) Seq[T]reduce(f : (T; T) ) T) : RDD[T] ) Tlookup(k : K) : RDD[(K, V)] ) Seq[V] (On hash/range partitioned RDDs)save(path : String) : Outputs RDD to a storage system, e.g., HDFS

6.1.2 容錯性

一般來說,分佈式數據集的容錯性有兩種方式:即數據檢查點記錄數據的更新。我們面向的是大規模數據分析,數據檢查點操作成本很高:需要通過數據中心的網絡連接在機器之間複製龐大的數據集,而網絡帶寬往往比內存帶寬低得多,同時還需要消耗更多的存儲資源(在內存中複製數據可以減少需要緩存的數據量,而存儲到磁盤則會拖慢應用程序)。所以,RDD選擇記錄更新的方式。但是,如果更新太多,那麼記錄更新成本也不低。因此,RDD只支持粗粒度轉換,即在大量記錄上執行的單個操作。將創建RDD的一系列轉換記錄下來(即Lineage),以便恢復丟失的分區。

雖然只支持粗粒度轉換限制了編程模型,但我們發現RDD仍然可以很好地適用於很多應用,特別是支持數據並行的批量分析應用,包括數據挖掘、機器學習、圖算法等,因爲這些程序通常都會在很多記錄上執行相同的操作。RDD不太適合那些異步更新共享狀態的應用,例如並行web爬行器。因此,我們的目標是爲大多數分析型應用提供有效的編程模型,而其他類型的應用交給專門的系統。


6.1.3 延遲結算

定義RDD之後,程序員就可以在動作中使用RDD了。動作是嚮應用程序返回值,或向存儲系統導出數據的那些操作,例如,count(返回RDD中的元素個數),collect(返回元素本身),save(將RDD輸出到存儲系統)。在Spark中,只有在動作第一次使用RDD時,纔會計算RDD(即延遲計算)。這樣在構建RDD的時候,運行時通過管道的方式傳輸多個轉換。


6.1.4 緩存

用戶可以請求將RDD緩存,這樣運行時將已經計算好的RDD分區存儲起來,以加速後期的重用。緩存的RDD一般存儲在內存中,但如果內存不夠,可以寫到磁盤上。


6.1.5 分區

RDD還允許用戶根據關鍵字(key)指定分區順序,這是一個可選的功能。目前支持哈希分區和範圍分區。例如,應用程序請求將兩個RDD按照同樣的哈希分區方式進行分區(將同一機器上具有相同關鍵字的記錄放在一個分區),以加速它們之間的join操作。


6.1.6 窄依賴/寬依賴

RDD之間的依賴關係可以分爲兩類,即:(1)窄依賴(narrow dependencies):子RDD的每個分區依賴於常數個父分區(即與數據規模無關);(2)寬依賴(wide dependencies):子RDD的每個分區依賴於所有父RDD分區。例如,map產生窄依賴,而join則是寬依賴(除非父RDD被哈希分區)。

這裏寫圖片描述

區分這兩種依賴很有用。首先,窄依賴允許在一個集羣節點上以流水線的方式(pipeline)計算所有父分區。例如,逐個元素地執行map、然後filter操作;而寬依賴則需要首先計算好所有父分區數據,然後在節點之間進行Shuffle,這與MapReduce類似。第二,窄依賴能夠更有效地進行失效節點的恢復,即只需重新計算丟失RDD分區的父分區,而且不同節點之間可以並行計算;而對於一個寬依賴關係的Lineage圖,單個節點失效可能導致這個RDD的所有祖先丟失部分分區,因而需要整體重新計算。


6.2 簡單示例

本部分我們通過一個具體示例來闡述RDD。假定有一個大型網站出錯,操作員想要檢查Hadoop文件系統(HDFS)中的日誌文件(TB級大小)來找出原因。通過使用Spark,操作員只需將日誌中的錯誤信息裝載到一組節點的內存中,然後執行交互式查詢。首先,需要在Spark解釋器中輸入如下Scala命令:

lines = spark.textFile("hdfs://...")
errors = lines.filter(_.startsWith("ERROR"))
errors.cache()

第1行從HDFS文件定義了一個RDD(即一個文本行集合),第2行獲得一個過濾後的RDD,第3行請求將errors緩存起來。注意在Scala語法中filter的參數是一個閉包。

這時集羣還沒有開始執行任何任務。但是,用戶已經可以在這個RDD上執行對應的動作,例如統計錯誤消息的數目:

errors.count()

用戶還可以在RDD上執行更多的轉換操作,並使用轉換結果,如:

// Count errors mentioning MySQL:
errors.filter(_.contains("MySQL")).count()
// Return the time fields of errors mentioning
// HDFS as an array (assuming time is field
// number 3 in a tab-separated format):
errors.filter(_.contains("HDFS"))
    .map(_.split('\t')(3))
    .collect()

使用errors的第一個action運行以後,Spark會把errors的分區緩存在內存中,極大地加快了後續計算速度。注意,最初的RDD lines不會被緩存。因爲錯誤信息可能只佔原數據集的很小一部分(小到足以放入內存)。
最後,爲了說明模型的容錯性,下圖給出了第3個查詢的Lineage圖。在lines RDD上執行filter操作,得到errors,然後再filter、map後得到新的RDD,在這個RDD上執行collect操作。Spark調度器以流水線的方式執行後兩個轉換,向擁有errors分區緩存的節點發送一組任務。此外,如果某個errors分區丟失,Spark只在相應的lines分區上執行filter操作來重建該errors分區。

這裏寫圖片描述


6.3 Spark任務調度器

調度器根據RDD的結構信息爲每個動作確定有效的執行計劃。調度器的接口是runJob函數,參數爲RDD及其分區集,和一個RDD分區上的函數。該接口足以表示Spark中的所有動作(即count、collect、save等)。

總的來說,我們的調度器跟Dryad類似,但我們還考慮了哪些RDD分區是緩存在內存中的。調度器根據目標RDD的Lineage圖創建一個由stage構成的無迴路有向圖(DAG)。每個stage內部儘可能多地包含一組具有窄依賴關係的轉換,並將它們流水線並行化(pipeline)。stage的邊界有兩種情況:一是寬依賴上的Shuffle操作;二是已緩存分區,它可以縮短父RDD的計算過程。例如圖6。父RDD完成計算後,可以在stage內啓動一組任務計算丟失的分區。

這裏寫圖片描述

Spark怎樣劃分任務階段(stage)的例子。實線方框表示RDD,實心矩形表示分區(黑色表示該分區被緩存)。要在RDD G上執行一個動作,調度器根據寬依賴創建一組stage,並在每個stage內部將具有窄依賴的轉換流水線化(pipeline)。 本例不用再執行stage 1,因爲B已經存在於緩存中了,所以只需要運行2和3。

調度器根據數據存放的位置分配任務,以最小化通信開銷。如果某個任務需要處理一個已緩存分區,則直接將任務分配給擁有這個分區的節點。否則,如果需要處理的分區位於多個可能的位置(例如,由HDFS的數據存放位置決定),則將任務分配給這一組節點。

對於寬依賴(例如需要Shuffle的依賴),目前的實現方式是,在擁有父分區的節點上將中間結果物化,簡化容錯處理,這跟MapReduce中物化map輸出很像。

如果某個任務失效,只要stage中的父RDD分區可用,則只需在另一個節點上重新運行這個任務即可。如果某些stage不可用(例如,Shuffle時某個map輸出丟失),則需要重新提交這個stage中的所有任務來計算丟失的分區。


7. 共享變量

多個task想要共享某個變量,Spark爲此提供了兩個共享變量,一種是Broadcast Variable(廣播變量),另一種是Accumulator(累加變量)。Broadcast Variable會將使用到的變量,僅僅爲每個節點拷貝一份,更大的用處是優化性能,減少網絡傳輸以及內存消耗。Accumulator則可以讓多個task共同操作一份變量,主要可以進行累加操作。

7.1 Broadcast Variable

Spark提供的Broadcast Variable,是隻讀的。並且在每個節點上只會有一份副本,而不會爲每個task都拷貝一份。因此其最大的作用,就是減少變量到各個節點的網絡傳輸消耗,以及在各個節點上的內存消耗。此外,spark內部也使用了高效的廣播算法來減少網絡消耗。

可以通過調用SparkContext的broadcast()方法,來針對某個變量創建廣播變量。然後在算子的函數內,使用到廣播變量時,每個節點只會拷貝一份副本,每個節點可以使用廣播變量的value()方法獲取值。

val factor = 3  
val factorBroadc ast = sc.broadcast(factor)  
val arr = Array(1,2,3,4,5)  
val rdd = sc.parallelize(arr)  
val multipleRdd = rdd.map(num => num*factorBroadcast.Value())  
multipleRdd.foreach(num => println(num)) 

7.2 Accumulator

Spark提供Accumulator,主要用於多個節點對一個變量進行共享性的操作。Accumulator只提供了累加的功能。但是卻給我們提供了多個task對一個變量並行操作的功能。但是task只能對Accumulator進行累加操作,不能讀取它的值。只有Driver程序可以讀取Accumulator的值。

al sumAccumulator = sc.accumulator(0)  
val arr = Array(1,2,3,4,5)  
val rdd = sc.parallelize(arr)  
rdd.foreach(num => sumAccumulator  += num)  
println(sumAccumulator.value)  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章