Spark權威指南(中文版)----第15章 Spark如何在集羣環境運行

Spark The Definitive Guide(Spark權威指南) 中文版。本書詳細介紹了Spark2.x版本的各個模塊,目前市面上最好的Spark2.x學習書籍!!!

掃碼關注公衆號:登峯大數據,閱讀中文Spark權威指南(完整版),系統學習Spark大數據框架!

如果您覺得作者翻譯的內容有幫助,請分享給更多人。您的分享,是作者翻譯的動力

到目前爲止,在本書中,我們主要關注Spark作爲編程接口的特性。我們已經討論了結構化api如何接受邏輯操作,將其分解爲邏輯計劃,並將其轉換爲物理計劃,該物理計劃實際上由跨機器集羣執行的彈性分佈式數據集(RDD)操作組成。本章主要討論Spark執行代碼時會發生什麼。我們以一種與實現無關的方式討論這個問題----這既不依賴於您正在使用的集羣管理器,也不依賴於您正在運行的代碼。最終,所有Spark代碼以相同的方式運行。

本章涵蓋了幾個關鍵的主題:

  • Spark應用程序的體系結構和組件

  • Spark應用程序在Spark內外的生命週期

  • 重要的底層執行屬性,如pipelining

  • 運行一個Spark應用程序需要什麼。

讓我們從體系結構開始。

 

      15.1.   Spark應用程序的體系結構

在第2章中,我們討論了Spark應用程序的一些高級組件。讓我們再複習一遍:

Spark Driver:

Driver程序是您的Spark應用程序的“駕駛座”中的進程。它是Spark應用程序執行的控制器,維護Spark集羣的所有狀態(執行器的狀態和其上運行的任務)。其必須與集羣管理器接口配合,以獲得可用的物理資源,並運行executors。最終,這只是一個物理機器上的進程,負責維護集羣上運行的應用程序的狀態。

Spark executors:

Spark  executor是執行Spark  driver程序分配的任務的進程。executor有一個核心職責:接受driver程序分配的任務,運行它們,並報告它們的狀態(成功或失敗)和結果給driver。每個Spark應用程序都有自己獨立的executor進程。

Cluster Manager集羣管理器:

Spark Driver程序和Executor不會脫離Spark應用程序而存在,這就是集羣管理器的作用所在。集羣管理器負責維護運行Spark應用程序的集羣。有點令人困惑的是,集羣管理器有自己的“Driver程序”(有時稱爲master)和“worker”抽象。其核心區別在於,它們與物理機器相關聯,而不是與進程相關聯(如Spark中的進程)。圖15-1顯示了一個基本的集羣設置。圖左邊的機器是集羣管理器driver程序節點。圓圈表示運行在每個工作節點上並管理每個工作節點的守護進程。到目前爲止還沒有運行Spark應用程序—這些只是來自集羣管理器的進程。

當實際運行Spark應用程序時,我們從集羣管理器請求資源來運行它。根據應用程序的配置方式,這可以包括一個運行Spark driver程序的資源,也可以只是Spark應用程序執行器的資源。在Spark應用程序執行過程中,集羣管理器將負責管理運行應用程序的底層機器。

Spark目前支持三個集羣管理器:一個簡單的內置standalone集羣管理器、Apache Mesos和Hadoop Yarn。但是,這個列表將繼續增長,所以一定要檢查您最喜歡的集羣管理器的文檔(譯者注:目前已經支持第四個集羣管理器:Kubernetes)。

現在我們已經介紹了應用程序的基本組件,讓我們來看看運行應用程序時需要做的第一項選擇:選擇執行模式。

15.1.1.   執行模式

執行模式使您能夠在運行應用程序時確定上述資源的物理位置。您有三種模式可供選擇:

  • Cluster mode

  • Client mode

  • Local mode

我們將使用圖15-1作爲模板,詳細介紹其中的每一個。在下一節中,帶實邊框的矩形表示Spark Driver程序進程,而帶點邊框的矩形表示executor進程。

Cluster mode

集羣模式可能是運行Spark應用程序的最常見方式。在集羣模式下,用戶向集羣管理器提交預編譯的JAR、Python腳本或R腳本。然後,集羣管理器在集羣內的worker節點上啓動driver程序進程,以及executor進程。這意味着集羣管理器負責維護所有與Spark應用程序相關的進程。圖15-2顯示集羣管理器將我們的driver程序放在一個worker節點上,將executor放在其他worker節點上。

Client mode

client模式幾乎與集羣模式相同,只是Spark driver程序仍然位於提交應用程序的客戶機上。這意味着客戶機負責維護Spark Driver程序進程,集羣管理器負責維護executor進程。在圖15-3中,我們在一臺集羣外的機器上運行Spark應用程序。這些機器通常稱爲網關機器或邊緣節點。在圖15-3中,您可以看到Driver程序運行在集羣外部的機器上,但是worker位於集羣中的機器上。

Local mode

Local模式與前兩種模式有很大的不同:它在一臺機器上運行整個Spark應用程序。它通過單臺機器上的線程實現並行。這是學習Spark、測試應用程序或使用本地開發進行迭代試驗的常見方法。但是,我們不建議使用本地模式來運行生產應用程序。

 

15.2.   Spark應用程序的生命週期

到目前爲止,本章已經涵蓋了討論Spark應用程序所需的詞彙表。現在是時候從實際Spark代碼的“外部”討論Spark應用程序的整個生命週期了。我們將通過一個使用spark-submit(在第3章中介紹)運行應用程序的示例來實現這一點。我們假設一個集羣已經運行了四個節點,一個Driver程序(不是Spark Driver程序,而是集羣管理器Driver程序)和三個worker節點。此時,實際的集羣管理器我們並不關注:本節使用前一節中的詞彙表逐步介紹Spark應用程序的生命週期,從初始化到程序退出。

  •  
  •  
提示本節還使用了插圖,並遵循我們前面介紹的相同符號。此外,我們現在介紹表示網絡通信的標線。較暗的箭頭表示由Spark或與Spark相關的進程相關的通信,而虛線表示更一般的通信(如集羣管理通信)。

15.2.1.   Client Request

第一步是提交一個實際的應用程序。這將是一個預編譯的JAR或庫。此時,您正在本地機器上執行代碼,並將向集羣管理器Driver程序節點發出請求(圖15-4)。這裏,我們明確地要求僅爲Spark Driver程序進程提供資源。我們假設集羣管理器接受這個提議,並將Driver放在集羣中的一個節點上。提交原始作業的客戶機進程退出,應用程序開始在集羣上運行。

要做到這一點,需要在終端上運行如下命令:

15.2.2.   Launch運行

現在驅動程序進程已經放在集羣上,它開始運行用戶代碼(圖15-5)。這段代碼必須包含一個初始化Spark集羣(例如,driver + executor)的SparkSession。SparkSession隨後將與集羣管理器通信(深色線),要求它在集羣中啓動Spark executor進程(淺色線)。executor的數量及其相關配置由用戶通過原始spark-submit調用中的命令行參數設置。

集羣管理器的響應是啓動執行器進程(假設一切正常),並將有關它們位置的相關信息發送給Driver進程。在所有東西都正確地連接起來之後,我們有了一個“Spark Cluster”,正如您今天可能認爲的那樣。

15.2.3.   Execution執行

現在我們有了一個“Spark集羣”,Spark以其愉快的方式執行代碼,如圖15-6所示。Driver和worker彼此通信,執行代碼並移動數據。Driver將任務調度到每個worker上,每個worker用這些任務的狀態和成功或失敗來響應。(我們將很快介紹這些細節。)

15.2.4.   Completion完成

在Spark應用程序完成後,Driver進程退出,成功或失敗(圖15-7)。然後集羣管理器爲Driver關閉Spark集羣中的Executor。此時,您可以通過向集羣管理器詢問此信息來查看Spark應用程序的成功或失敗。

15.3.   Spark應用程序的生命週期(insidespark)

我們剛剛研究了用戶代碼之外的Spark應用程序的生命週期(基本上是支持Spark的基礎設施),但是更重要的是討論在運行應用程序時在Spark中發生了什麼。這是“用戶代碼”(定義Spark應用程序的實際代碼)。每個應用程序由一個或多個Spark作業組成。應用程序中的Spark作業是串行執行的(除非使用線程並行啓動多個操作)。

15.3.1.   SparkSession

任何Spark應用程序的第一步都是創建一個SparkSession。在許多交互模式中,默認會爲你創建這樣一個對象,但在應用程序中,您必須手動完成對象的創建。您的一些遺留代碼可能使用new SparkContext模式。應該避免這種情況,採用SparkSession上的builder方法,它更健壯地實例化Spark和SQL上下文,並確保沒有上下文衝突,因爲可能有多個庫試圖在同一個Spark應用程序中創建會話:

在您擁有一個SparkSession之後,您應該能夠運行您的Spark代碼。從SparkSession中,您還可以相應地訪問所有底層和遺留上下文和配置。注意,SparkSession類只是在Spark 2.X中添加的。您可能發現的舊代碼將直接爲結構化api創建SparkContext和SQLContext。

SparkContext

SparkSession中的SparkContext對象表示到Spark集羣的連接。這個類是您與Spark的一些低層api(如RDDs)通信的方式。在舊的示例和文檔中,它通常存儲爲變量sc。通過SparkContext,您可以創建RDDs、累加器和廣播變量,並且可以在集羣上運行代碼。

在大多數情況下,不需要顯式地初始化SparkContext;您應該能夠通過SparkSession訪問它。如果你想,你應該用最通用的方法創建它,通過getOrCreate方法:

  •  
  •  
  •  
  •  
SparkSession, SQLContext, 和 HiveContext在Spark的早期版本中,SQLContext和HiveContext提供了處理DataFrames和Spark SQL的能力,並且通常在示例、文檔和遺留代碼中作爲變量sqlContext存儲。作爲一個歷史點,Spark 1.X實際上有兩個上下文:SparkContext和SQLContext。這兩個上下文的表現各不相同。前者側重於對Spark的核心抽象進行更細粒度的控制,而後者側重於像Spark SQL這樣的高級工具。在Spark2.0中,開源社區將這兩個api合併到我們今天使用的集中式SparkSession中。但是,這兩個api仍然存在,您可以通過SparkSession訪問它們。需要注意的是,您不應該使用SQLContext,也很少需要使用SparkContext。

初始化SparkSession之後,就可以執行一些代碼了。正如我們在前幾章中所知道的,所有Spark代碼都編譯爲RDDs。因此,在下一節中,我們將使用一些邏輯說明(DataFrame作業),並逐步瞭解隨着時間的推移會發生什麼。

15.3.2.   Logical Instructions邏輯指令

正如您在本書的開頭所看到的,Spark代碼本質上由Transformation和action組成。如何構建這些取決於您——無論是通過SQL、低層RDD操作,還是機器學習算法。理解我們如何使用像DataFrames這樣的聲明性指令,並將它們轉換爲物理執行計劃,這是理解Spark如何在集羣上運行的一個重要步驟。在本節中,請確保在一個新的環境(一個新的Spark shell)中運行此操作,以跟蹤job、stage和task編號。

邏輯指令到物理執行

我們在第2部分中提到了這一點,但是值得重申一遍,以便更好地理解Spark如何獲取代碼並在集羣上實際運行命令。我們將逐行介紹更多的代碼,解釋幕後發生的事情,以便您能夠更好地理解Spark應用程序。在後面的章節中,當我們討論監控,我們將通過Spark UI對Spark作業執行更詳細的跟蹤。在當前的示例中,我們將採用更簡單的方法。我們將做一個包含三個步驟的job:使用一個簡單的DataFrame,我們將對它進行重新分區,執行一個值對一個值的操作,然後聚合一些值並收集最終結果。

  •  
  •  
提示這段代碼是用Python Spark 2.2編寫並運行的(您將在Scala中得到相同的結果,因此我們省略了它)。作業的數量不太可能發生巨大的變化,但是Spark的底層優化可能會得到改進,從而改變物理執行策略。

當運行這段代碼時,我們可以看到您的操作觸發了一個完整的Spark作業。讓我們來看看explain計劃,以鞏固我們對物理執行計劃的理解。我們可以在Spark UI中的SQL選項卡(在實際運行查詢之後)訪問這些信息,以及:

當您調用collect(或任何action)時,您所擁有的是Spark作業的執行,該作業由各個stages和tasks組成。如果您正在本地機器上運行此命令,請轉到localhost:4040查看Spark UI。我們將跟隨“jobs”選項卡,隨着我們進一步深入細節,最終跳轉到各個stages和tasks。

15.3.3.   A Spark Job

一般來說,一個action操作出發一個spark job。Action操作總是返回一個結果值。每個job可以包含多個stages,stages的個數多少,有shuffle操作的transformation決定。

這項job分爲以下幾個stages和tasks:

我希望您至少對我們如何得到這些數字有些困惑,以便我們能夠花時間更好地理解發生了什麼!

15.3.4.   Stages

Spark中的stages表示可以一起執行的任務組,以便在多臺機器上計算相同的操作。一般來說,Spark會嘗試打包儘可能多的工作(如:儘可能多的transformation操作)到同一個stage,但是Spark引擎在shuffle操作之後產生新的stage。shuffle表示數據的物理重新分區——例如,對DataFrame進行排序,或按key對從文件中加載的數據進行分組(這需要將具有相同key的記錄發送到相同節點)。這種類型的重新分區需要跨Executor協調來移動數據。Spark在每次shuffle之後啓動一個新stage,並跟蹤各個stage必須以什麼順序運行才能計算最終結果。

在我們前面看到的job中,前兩個stages對應於創建DataFrames所執行的rang()方法。默認情況下,當您使用rang()創建一個DataFrame時,它有八個分區。下一步是重新分區。這通過shuffle數據來改變分區的數量。這些DataFrames被劃分爲6個分區和5個分區,對應於stage3和stages4中的task數量。

stage3和stage4對每個DataFrame執行操作,stage的末尾表示join(shuffle)。突然,我們有200個任務。這是因爲spark.sql.shuffle.partitions配置的默認值爲200,這意味着在執行過程中執行shuffle時,默認情況下輸出200個洗牌分區。您可以更改此值,並且輸出分區的數量也將發生更改。

  •  
  •  
  •  
提示我們將在第19章更詳細地討論分區的數量,因爲它是一個非常重要的參數。應該根據集羣中的內核數量設置此值,以確保高效執行。下面是設置方法:spark.conf.set("spark.sql.shuffle.partitions", 50)

一個好的經驗法則是分區的數量應該大於集羣中executor的數量,這可能是由多個因素造成的,具體取決於工作負載。如果您在本地機器上運行代碼,您應該將該值設置得更低,因爲本地機器不太可能並行執行那麼多任務。對於一個集羣,這更像是一個默認值,其中可能有更多的executors cores可供使用。無論分區的數量如何,整個stage都是並行計算的。最終的結果將單獨地聚合這些分區,在最終將最終結果發送到Driver之前,將它們都放到一個單獨的分區中。在本書的這一部分中,我們將多次看到這種配置。

15.3.5.  Tasks

Spark中的stage由task組成。每個task對應於將在單個executor上運行的數據塊和一組transformation的組合。如果數據集中有一個大分區,我們將有一個任務。如果有1000個小分區,那麼就有1000個任務可以並行執行。task只是應用於數據單元(分區)的計算單元。將數據劃分爲更多的分區意味着可以並行執行更多的分區。這不是萬靈藥,但它是一個開始優化的簡單地方。

15.4.   執行細節

在我們結束本章之前,Spark中的task和stage有一些重要的特性值得回顧。首先,Spark自動將可以一起完成的task和stage串聯起來,比如一個map操作後面跟着另一個map操作。其次,對於所有的shuffle操作,Spark將數據寫入穩定的存儲(例如磁盤),並可以在多個作業之間重用數據。我們將依次討論這些概念,因爲它們將在您開始通過Spark UI檢查應用程序時出現。

15.4.1.   Pipelining

Spark之所以成爲“內存計算框架”的一個重要原因是,與之前的框架(例如MapReduce)不同,Spark在將數據寫入內存或磁盤之前,會在某個時間點執行儘可能多的步驟。Spark執行的一個關鍵優化是pipelining,它發生在RDD級別或以下。使用pipelining,任何直接相互提供數據的操作序列(不需要在節點之間移動數據)都被分解爲一個單獨的任務階段,這些任務一起完成所有的操作。例如,如果您編寫一個RDD-based程序,包括一個map,一個filter,然後另一個map,這些任務將存在於一個stage中,讀取每個輸入數據記錄,然後傳輸數據到第一個map,然後數據通過filter,如果需要通過最後一個map函數。這種流水線版本的計算比在每一步之後將中間結果寫入內存或磁盤要快得多。對於執行select、filter和select的DataFrame或SQL計算,也會發生類似的pipelining操作。

從實用的角度來看,pipelining對用戶是透明的,你寫一個應用,運行時將自動運行pipelining,但是你會發現如果你檢查您的應用程序通過SparkUI或通過其日誌文件,你會看到多個RDD或DataFrame操作被安排成一個單一的Stage。

15.4.2.  ShufflePersistence

有時會看到的第二個屬性是shuffle持久性。當Spark需要運行一個必須跨節點移動數據的操作時,例如reduce-by-key操作(每個key的輸入數據首先需要從多個節點收集到一起),此時Spark引擎就不能再執行流水線,而是執行跨網絡shuffle。Spark總是通過在執行階段首先讓“源”任務(發送數據的任務)將shuffle文件寫入本地磁盤來執行shuffle。然後,進行分組和合並數據的stage啓動並運行任務,這些任務從每個shuffle文件中獲取相應的記錄並執行計算(例如,獲取和處理特定範圍的key的數據)。將shuffle文件保存到磁盤中,可以讓Spark在比源階段更晚的時間運行這個階段(例如,如果沒有足夠的執行者同時運行這兩個階段),還可以讓引擎在失敗時重新啓動reduce任務,而無需重新運行所有輸入任務。

對於shuffle持久性,您將看到的一個副作用是,在已經shuffle的數據上運行一個新作業不會重新運行shuffle的“源”端。因爲shuffle文件已經在更早的時候被寫到磁盤上,Spark知道它可以使用它們來運行任務的後期,並且它不需要重做早期的那些。在Spark UI和日誌中,您將看到預shuffle階段標記爲“跳過”。這種自動優化可以在對相同數據運行多個作業的工作負載中節省時間,但是當然,爲了獲得更好的性能,您可以使用DataFrame或RDD緩存方法執行自己的緩存,該方法允許您精確地控制保存哪些數據以及保存在何處。在對聚合數據運行一些Spark操作並在UI中檢查它們之後,您將很快習慣這種行爲。

15.5.  結束語

在本章中,我們討論了在集羣上執行應用程序時,會發生什麼情況。這意味着集羣將如何實際運行代碼,以及在此過程中在Spark應用程序中發生了什麼。此時,您應該很容易理解Spark應用程序內外發生了什麼。這將爲調試應用程序提供一個起點。第16章將討論編寫Spark應用程序以及在編寫時應該考慮的事項。

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