返回文章列表

剖析Spark分佈式運算的核心架構

本文深入探討 Apache Spark 的分佈式運算架構。文章詳細解析了驅動節點(Driver)與執行器(Executor)之間的分工協作模式,並說明應用程式如何透過工作(Job)、階段(Stage)與任務(Task)的層次結構,在叢集上高效處理數據分區,從而實現大規模數據處理的擴展性。

數據工程 大數據技術

在大數據時代,分佈式運算已是數據工程師的基礎技能。Apache Spark 作為主流處理框架,其核心在於精巧的叢集運算架構。本文將從底層原理出發,系統性拆解 Spark 應用程式的運行機制,從驅動節點的規劃調度,到執行器如何並行處理數據分區,深入闡述其工作、階段與任務的生命週期,為後續性能調校與開發奠定堅實的理論基礎。

數據工程高科技養成:從理論到實踐的玄貓指引

本地環境建置:掌控開發細節

MinIO

與密碼來登入。成功登入後,導航欄將提供對Bucket管理物件瀏覽IAM監控配置等核心功能的存取。儘管操作系統不同,MinIO控制台在Mac上的使用者體驗與Windows版本保持一致,提供了一個統一的介面來管理物件儲存。

  1. 安裝MinIO客戶端:MinIO客戶端允許讀者從命令列與MinIO伺服器互動。以下是安裝方式:
brew install minio/stable/mc

使用mc alias set創建一個與本地部署相關的新別名。讀者可以對此別名運行mc命令:

mc alias set local http://localhost:9000 minioadmin minioadmin
mc admin info local

讀者可以使用MinIO伺服器minio server ~/minio --console-address :9090命令輸出中API部分的任何URL。

請注意,如果讀者停止並重新啟動MinIO伺服器,API URL可能會更改。這將需要運行mc alias set以使用新的API端點。

數據工程核心理論與實踐

第三章:Apache Spark與其核心API:DataFrame、Dataset與Spark SQL

Apache Spark以Scala語言編寫,因其能夠大規模地攝取、豐富和準備數據以供分析用途,已成為分佈式數據處理領域的主導框架。作為一名數據工程師,玄貓終將面對單機無法處理的龐大數據量。本章將引導讀者如何利用Spark及其多樣化的API,在機器叢集上高效地完成這些數據處理任務。

本章將涵蓋以下主要主題:

  • Apache Spark的運作原理
  • 使用Scala建立Spark應用程式
  • 深入理解Spark Dataset API
  • 深入理解Spark DataFrame API
技術前瞻與實踐資源

本章所有程式碼範例將存放於玄貓的GitHub軟體庫中。

Apache Spark的運作原理

Spark被廣泛定義為一個用於大規模數據處理的統一分析引擎。它提供了Java、Scala、Python、R等高階API,並擁有經過優化的執行引擎。Spark的應用範疇廣泛,涵蓋了數據工程、機器學習 (ML) 和數據科學等多個領域。玄貓的重點將放在如何利用Spark在Scala中進行數據工程。

Spark的設計宗旨是處理海量數據,這主要透過其分佈式計算架構實現。接下來,玄貓將深入探討Spark應用程式的運作機制。

Spark應用程式如何運作?

數據工程高科技養成:從理論到實踐的玄貓指引

數據工程核心理論與實踐

第三章:Apache Spark與其核心API:DataFrame、Dataset與Spark SQL

Spark應用程式如何運作?

一個Spark應用程式運行在一個Spark叢集上,這個叢集是由相互連接的節點群組構成的。這些節點可以是虛擬機器 (VMs) 或實體伺服器。在Spark的架構中,存在一個驅動節點 (Driver Node) 和一個到多個執行器 (Executors),它們都在Spark叢集上運行。驅動節點負責控制執行器,並向執行器提供指令(這些指令定義在Spark應用程式中)。通常,驅動節點本身並不會直接處理數據。數據的實際操作發生在執行器上,它們根據驅動節點的指令來處理數據。這可以用以下圖示來表示:

此圖示:Spark驅動節點與執行器架構

@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

cloud "Spark 叢集" as SparkCluster {
node "驅動節點 (Driver Node)" as Driver {
component "SparkContext" as SC
component "DAGScheduler" as DAGS
component "TaskScheduler" as TS
}

node "執行器 (Executor 1)" as Executor1 {
component "任務 (Task 1.1)" as Task1_1
component "任務 (Task 1.2)" as Task1_2
component "緩存儲存" as Cache1
}

node "執行器 (Executor 2)" as Executor2 {
component "任務 (Task 2.1)" as Task2_1
component "任務 (Task 2.2)" as Task2_2
component "緩存儲存" as Cache2
}

node "執行器 (Executor N)" as ExecutorN {
component "任務 (Task N.1)" as TaskN_1
component "任務 (Task N.2)" as TaskN_2
component "緩存儲存" as CacheN
}
}

Driver --> Executor1 : 發送指令
Driver --> Executor2 : 發送指令
Driver --> ExecutorN : 發送指令

Executor1 --> Driver : 回報狀態/結果
Executor2 --> Driver : 回報狀態/結果
ExecutorN --> Driver : 回報狀態/結果

SC --right-> DAGS : 協調任務排程
DAGS --right-> TS : 提交任務
TS --down-> Executor1 : 分配任務
TS --down-> Executor2 : 分配任務
TS --down-> ExecutorN : 分配任務
@enduml

看圖說話:

此圖示清晰地描繪了Apache Spark應用程式在叢集中的運作架構。核心是Spark叢集,其中包含一個驅動節點 (Driver Node) 和多個執行器 (Executors)。驅動節點扮演著指揮官的角色,內部包含SparkContext(應用程式的入口點)、DAGScheduler(將操作轉換為有向無環圖)和TaskScheduler(將任務分配給執行器)。驅動節點負責解析應用程式邏輯、規劃執行計畫,並向各個執行器發送指令。執行器則是實際的勞動者,它們負責接收驅動節點分配的任務 (Tasks),並在本地處理數據分區,同時可能利用緩存儲存來提高效率。執行器會將其處理狀態和結果回報給驅動節點。這種分工合作的模式使得Spark能夠高效地處理大規模數據,實現分佈式計算。

需要注意的是,以下計算假設了線性擴展性,這在實際情況中並非總是如此。分佈式工作帶來的實際效益,很大程度上取決於數據的性質以及對數據應用的轉換操作。

在開源的Spark環境中,讀者可以根據處理數據的精確需求來配置執行器的數量和記憶體。在像Databricks這樣的平台上,讀者可以根據需求彈性地配置計算資源並自動擴展。無論在哪裡運行Spark,讀者都可以根據需要擴展或縮減處理能力。

但這到底意味著什麼呢?想像一下,玄貓有6,000,000條記錄需要處理,而一台伺服器處理60條記錄需要1秒。那麼處理所有記錄需要多長時間呢?玄貓來分解一下:

  • 6,000,000 條記錄 / 每秒 60 條記錄 = 100,000 秒
  • = 1,666.66 分鐘
  • = 27.77 小時

現在,如果玄貓有一個擁有100個執行器的Spark叢集呢?這將需要多長時間:

  • 6,000,000 條記錄 / (每秒 60 條記錄 * 100 個執行器) = 1,000 秒
  • = 16.67 分鐘

如果玄貓有一個擁有1,000個執行器的Spark叢集呢?這將需要以下時間:

  • 6,000,000 條記錄 / (每秒 60 條記錄 * 1,000 個執行器) = 100 秒
  • = 1.67 分鐘

在雲端平台上運行Spark,讀者可以根據工作負載精確配置所需的計算量。讀者可能對16.67分鐘的處理時間感到滿意。在這種情況下,創建一個包含100個執行器的工作來處理數據即可。或者,讀者可能希望在2分鐘內完成處理,那麼就創建一個包含1,000個執行器的工作。現在,讓玄貓探討一下執行器上實際發生了什麼。

執行器上發生了什麼?

如前所述,執行器可以被視為總計算能力的一個子集,其中數據的一個子集(稱為分區 (partition))作為工作 (job) 的一個任務 (task) 進行處理。一個任務是由驅動節點提供的一組特定指令,而一個工作可以包含一個或多個階段 (stages),每個階段又包含一個或多個任務。這可以用以下圖示來表示:

此圖示:Spark應用程式內部運作概覽

@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

rectangle "Spark 應用程式" as SparkApp {
rectangle "工作 (Job)" as Job {
rectangle "階段 1 (Stage 1)" as Stage1 {
component "任務 1.1 (Task 1.1)" as Task1_1
component "任務 1.2 (Task 1.2)" as Task1_2
component "..." as Task1_N
}
rectangle "階段 2 (Stage 2)" as Stage2 {
component "任務 2.1 (Task 2.1)" as Task2_1
component "任務 2.2 (Task 2.2)" as Task2_2
component "..." as Task2_M
}
component "..." as StageK
}
}

Job --> Stage1
Job --> Stage2
Job --> StageK

Stage1 --> Task1_1
Stage1 --> Task1_2
Stage1 --> Task1_N

Stage2 --> Task2_1
Stage2 --> Task2_2
Stage2 --> Task2_M
@enduml

看圖說話:

此圖示展示了Spark應用程式內部運作的層次結構。一個完整的Spark應用程式可以分解為一個或多個工作 (Job)。每個工作又進一步劃分為一個或多個階段 (Stage)。這些階段是Spark執行計畫中的邏輯分組,通常在需要數據洗牌(shuffle)操作時會劃分新的階段。在每個階段內部,又包含一個或多個任務 (Task)。任務是Spark執行器上執行的最小工作單元,每個任務負責處理一個數據分區。這種精細的層次結構允許Spark高效地管理和分發計算,實現大規模數據的並行處理。

每個任務都在一個執行器上運行,並被分組在一個階段內。在處理分區時,分區與執行器之間存在一對一的映射關係。如果分區數量多於執行器數量,那麼第(n+1)個分區將會等待,直到1...n個執行器中的任何一個完成其分區處理後才能被接管。

此圖示:數據分區與Spark執行器的關係

@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

cloud "Spark 叢集" as SparkCluster {
node "執行器 A" as ExA {
rectangle "任務 A1" as TaskA1
rectangle "任務 A2" as TaskA2
}
node "執行器 B" as ExB {
rectangle "任務 B1" as TaskB1
rectangle "任務 B2" as TaskB2
}
node "執行器 C" as ExC {
rectangle "任務 C1" as TaskC1
rectangle "任務 C2" as TaskC2
}
}

rectangle "數據分區" as DataPartitions {
component "分區 1" as P1
component "分區 2" as P2
component "分區 3" as P3
component "分區 4" as P4
component "分區 5" as P5
component "分區 6" as P6
}

P1 --> TaskA1 : 處理
P2 --> TaskB1 : 處理
P3 --> TaskC1 : 處理
P4 --> TaskA2 : 處理 (A1 完成後)
P5 --> TaskB2 : 處理 (B1 完成後)
P6 --> TaskC2 : 處理 (C1 完成後)
@enduml

看圖說話:

此圖示生動地展示了數據分區如何與Spark執行器協同工作。在Spark叢集中,每個執行器被分配來處理一個或多個任務。每個任務又會負責處理一個數據分區。圖中顯示了三個執行器(A、B、C)和六個數據分區(1到6)。初始時,分區1、2、3被分配給執行器A、B、C的第一個任務進行處理。一旦執行器上的第一個任務完成其分區的處理,該執行器便會準備好接收下一個分區(例如,當任務A1完成後,執行器A會處理分區4)。這種機制確保了數據能夠在多個執行器上並行處理,最大化了資源利用率,並在分區數量超過執行器數量時,實現了任務的排隊和順序執行。

讀者不需要完全理解Spark的架構才能建構和運行Spark應用程式,但讀者確實需要理解這些知識才能針對性能調整應用程式。在後續章節中,玄貓將更深入地探討Spark架構,以及如何利用對數據和應用程式的了解來進行性能調優。

玄貓已經學習了Apache Spark的基礎知識、其架構以及如何用於大規模分佈式數據處理。玄貓也介紹了Spark應用程式以及它們如何在Spark叢集上運行。現在,玄貓將繼續建構第一個使用Scala編寫的Spark應用程式。

深入剖析Apache Spark的核心運作架構後,我們清晰地看到,其強大的分佈式計算能力並非憑空而來,而是建立在一套精密的協同機制之上。從驅動節點的統籌帷幄,到執行器的並行處理,無不體現了化繁為簡、分而治之的工程哲學,這也是數據工程師必須掌握的核心思維。

然而,許多初階工程師常陷入僅熟悉DataFrame或Spark SQL語法的誤區,而忽略了其背後的執行計畫、任務分發與數據分區等底層邏輯。這種「知其然而不知其所以然」的學習路徑,正是從「能用」到「精通」的最大瓶頸。真正決定一位數據工程師價值上限的,並非API的熟練度,而是能否基於對架構的深刻理解,進行資源配置、數據傾斜處理與效能調優,將理論知識轉化為實質的成本效益與處理效率。

展望未來,隨著數據量級持續指數級增長,企業對數據處理效能的要求將愈發嚴苛。屆時,能夠洞悉Spark內部運作機制、並進行精準性能調校的工程師,將成為團隊中不可或缺的關鍵資產,其職涯價值也將遠超單純的應用開發者。

因此,玄貓認為,對於有志於成為頂尖數據工程師的專業人士而言,將理論與實踐深度結合,把理解Spark架構置於與掌握API同等重要的戰略高度,是通往高階職能的必經之路。