一、RDD的特性
Spark之所以成為目前比較主流的大數據處理技術,其中RDD的特性和機制占到很大比重,沒有RDD的這些機制,Spark性能會大打折扣。總體而言,Spark采用RDD后能夠實現高效計算的主要原因有以下幾點:
1、高效的容錯機制。
現有的分布式共享內存、鍵值存儲、內存數據庫等,為了實現容錯,必須在集群節點之間進行數據復制(主從復制)或者記錄日志。
這就造成各個節點之間大量數據傳輸,這對于數據密集型應用而言會帶來很大的開銷。而在設計RDD的過程中,賦予RDD數據只讀的能力,不可被修改。如果需要修改數據,必須從父RDD轉換到子RDD從新進行計算,由此在不同RDD之間建立了血緣關系。
RDD是一種天生具有容錯機制的特殊集合,不需要通過數據冗余的方式實現容錯,而只需通過RDD父子依賴(血緣)關系重新計算得到丟失的分區來實現容錯。而不需要回滾整個系統,這樣就避免了數據復制的高開銷。而且重算過程可以在不同節點之間并行進行,實現了高效的容錯。
最主要的,RDD提供的轉換操作都是一些粗粒度的操作。比如map、filter和join這些轉換算子,并不觸發真正的計算,只記錄他們之間的邏輯關系,只有當遇到count、foreach這類輸出算子才觸發計算機制。
RDD依賴關系只需要記錄這種粗粒度的轉換操作,而不需要記錄具體的數據和各種細粒度操作的日志,比如對哪個數據項進行了修改,這就大大降低了數據密集型應用中的容錯開銷。
2、中間結果持久化落地到內存。
數據在內存中的多個RDD操作之間進行傳遞,不需要“落地”到磁盤上,避免了不必要的讀寫磁盤的網絡和IO開銷,有效地提升了數據之間的處理或轉換效率。
3、存放的數據可以是Java對象。
存放的數據可以是Java對象,避免了不必要的對象序列化和反序列化開銷。
二、RDD的窄依賴和寬依賴
不同的操作會使得不同RDD中的分區會產生不同的依賴。RDD中的依賴關系分為窄依賴與寬依賴。
1、什么是窄依賴和寬依賴?
窄依賴:一個父RDD的分區對應于一個子RDD的分區,或多個父RDD的分區對應于一個子RDD的分區。最終結果是一個或多個父RDD分區對于一個子RDD分區,是 一或多 對一 的關系。窄依賴典型的操作包括map、filter、union等算子。
寬依賴:一個父RDD的一個分區對應一個子RDD的多個分區。是一個分區對應多個分區的關系,一對多。寬依賴典型的操作包括groupByKey、sortByKey等算子。
總的來說,如果父RDD的一個分區只被一個子RDD的一個分區所使用就是窄依賴,否則就是寬依賴。
特別注意:對于連接Join操作的窄寬依賴的劃分可以分為兩種情況。
(1)、對輸入進行協同劃分,屬于窄依賴。所謂協同劃分是指多個父RDD的某一分區的所有“鍵”,落在子RDD的同一個分區內,不會產生同一個父RDD的某一分區,落在子RDD的兩個分區的情況。
(2)、對輸入做非協同劃分,屬于寬依賴。對于窄依賴的RDD,可以以流水線的方式計算所有父分區,不會造成網絡之間的數據混合。對于寬依賴的RDD,則通常伴隨著Shuffle操作,即首先需要計算好所有父分區數據,然后在節點之間進行Shuffle。
2、窄依賴與寬依賴的比較
RDD的這種窄依賴和寬依賴的關系設計,使其具有了天生的容錯性,大大加快了Spark的執行速度。
RDD數據集通過“血緣關系”記錄了它是如何從其它RDD中轉換過來的,血緣關系記錄的是粗數據粒度的轉換操作行為。當這個RDD的部分分區數據丟失時,可以通過血緣關系獲取足夠的信息來重新運算和恢復丟失的數據分區,由此帶來了性能的提升。
在兩種依賴關系中,窄依賴的失敗恢復更為高效,它只需要根據父RDD分區重新計算丟失的分區即可,而且可以并行地在不同節點進行重新計算。對于寬依賴而言,單個節點失效通常意味著重新計算過程會涉及多個父RDD分區,開銷較大。
Spark還提供了數據檢查點和記錄日志,用于持久化中間RDD,從而使得在進行失敗恢復時不需要追溯到最開始的階段。在進行故障恢復時,Spark會對數據檢查點開銷和重新計算RDD分區的開銷進行比較,從而自動選擇最優的恢復策略。
三、RDD的階段劃分
通過分析各個RDD的依賴關系生成了DAG,再通過分析各個RDD中的分區之間的依賴關系來決定如何劃分階段。
劃分方法:對DAG進行反向解析,遇到寬依賴就斷開,遇到窄依賴就把當前的RDD加入到當前的階段中;將窄依賴盡量劃分在同一個階段中,可以實現流水線計算。
如上圖,從HDFS中讀入數據生成3個不同的RDD,即A、C和E。通過一系列轉換操作后再將計算結果保存回HDFS。
對DAG進行解析時,在依賴圖中進行反向解析。從RDD A到RDD B的轉換,以及從RDD B和RDD F到RDD G的轉換,都屬于寬依賴。
因此,在寬依賴處斷開后可以得到三個階段,即階段1、階段2和階段3。在階段2中,從map到union都是窄依賴,這兩步操作可以形成一個流水線操作。如,分區7通過map操作生成的分區9,可以不用等待分區8到分區10這個轉換操作的計算結束,而是繼續進行union操作,轉換得到分區13,這樣流水線執行大大提高了計算的效率。
把一個DAG圖劃分成多個“階段”以后,每個階段都代表了一組關聯的、相互之間沒有Shuffle依賴關系的任務組成的任務集合。每個任務集合會被提交給任務調度器TaskScheduler進行處理,由任務調度器將任務分發給Executor運行。
四、Spark的RDD運行過程
通過對RDD概念、依賴關系和階段劃分的介紹,結合之前介紹的Spark運行基本流程,這里再總結一下RDD在Spark架構中的運行過程:
1、創建RDD對象
2、SparkContext負責計算RDD之間的依賴關系,構建DAG;
3、DAGScheduler負責把DAG圖分解成多個階段,每個階段中包含了多個任務,每個任務會被任務調度器分發給各個工作節點(Worker Node)上的Executor去執行。
一個完整的應用從提交到執行的完整流程:
1,通過spark-submit命令,提交spark應用程序。
2,在driver端會執行代碼,首先通過構造的sparkConf,把配置傳給sparkContext對象,用于創建sparkContext上下文環境。
3,在遇到action算子時,會生成一個Job。DAGScheduler會將Job劃分為多個Stage,每個Stage會創建一個任務集taskSet。
4,把任務集傳遞給TaskScheduler,TaskScheduler會執行以下過程:
4.1,TaskScheduler會去連接Master向其申請注冊Application。Master接收到App的注冊請求時,會為其在Worker節點上啟動多個Executor。
4.2,Executor啟動以后會反向注冊到TaskScheduler。Executor會啟動線程池,TaskRunner就是把函數反序列化之后通過線程去運行的。Task有兩個ShuffleMapTask和ResultTask,只有最后一個Stage是ResultTask,之前所有的都是ShuffleMapTask。
4.3,TaskScheduler接到Executor注冊請求后,會把任務集里的每一個任務都提交到Executor上執行。在這里有任務分配的算法,移動計算不移動數據。