Apache Flink 進階(一):Runtime 核心機制剖析

吐泡泡ooO發表於2019-09-16

1. 綜述

本文主要介紹 Flink Runtime 的作業執行的核心機制。首先介紹 Flink Runtime 的整體架構以及 Job 的基本執行流程,然後介紹在這個過程,Flink 是怎麼進行資源管理、作業排程以及錯誤恢復的。最後,本文還將簡要介紹 Flink Runtime 層當前正在進行的一些工作。

2. Flink Runtime 整體架構

Flink 的整體架構如圖 1 所示。Flink 是可以執行在多種不同的環境中的,例如,它可以通過單程式多執行緒的方式直接執行,從而提供除錯的能力。它也可以執行在 Yarn 或者 K8S 這種資源管理系統上面,也可以在各種雲環境中執行。


圖1. Flink 的整體架構,其中 Runtime 層對不同的執行環境提供了一套統一的分散式執行引擎。

針對不同的執行環境,Flink 提供了一套統一的分散式作業執行引擎,也就是 Flink Runtime 這層。Flink 在 Runtime 層之上提供了 DataStream 和 DataSet 兩套 API,分別用來編寫流作業與批作業,以及一組更高階的 API 來簡化特定作業的編寫。本文主要介紹 Flink Runtime 層的整體架構。

Flink Runtime 層的主要架構如圖 2 所示,它展示了一個 Flink 叢集的基本結構。Flink Runtime 層的整個架構主要是在 FLIP-6 中實現的,整體來說,它採用了標準 master-slave 的結構,其中左側白色圈中的部分即是 master,它負責管理整個叢集中的資源和作業;而右側的兩個 TaskExecutor 則是 Slave,負責提供具體的資源並實際執行作業。


圖2. Flink 叢集的基本結構。Flink Runtime 層採用了標準的 master-slave 架構。

其中,Master 部分又包含了三個元件,即 Dispatcher、ResourceManager 和 JobManager。其中,Dispatcher 負責接收使用者提供的作業,並且負責為這個新提交的作業拉起一個新的 JobManager 元件。ResourceManager 負責資源的管理,在整個 Flink 叢集中只有一個 ResourceManager。JobManager 負責管理作業的執行,在一個 Flink 叢集中可能有多個作業同時執行,每個作業都有自己的 JobManager 元件。這三個元件都包含在 AppMaster 程式中。

基於上述結構,當使用者提交作業的時候,提交指令碼會首先啟動一個 Client程式負責作業的編譯與提交。它首先將使用者編寫的程式碼編譯為一個 JobGraph,在這個過程,它還會進行一些檢查或優化等工作,例如判斷哪些 Operator 可以 Chain 到同一個 Task 中。然後,Client 將產生的 JobGraph 提交到叢集中執行。此時有兩種情況,一種是類似於 Standalone 這種 Session 模式,AM 會預先啟動,此時 Client 直接與 Dispatcher 建立連線並提交作業即可。另一種是 Per-Job 模式,AM 不會預先啟動,此時 Client 將首先向資源管理系統 (如Yarn、K8S)申請資源來啟動 AM,然後再向 AM 中的 Dispatcher 提交作業。

當作業到 Dispatcher 後,Dispatcher 會首先啟動一個 JobManager 元件,然後 JobManager 會向 ResourceManager 申請資源來啟動作業中具體的任務。這時根據 Session 和 Per-Job 模式的區別, TaskExecutor 可能已經啟動或者尚未啟動。如果是前者,此時 ResourceManager 中已有記錄了 TaskExecutor 註冊的資源,可以直接選取空閒資源進行分配。否則,ResourceManager 也需要首先向外部資源管理系統申請資源來啟動 TaskExecutor,然後等待 TaskExecutor 註冊相應資源後再繼續選擇空閒資源程式分配。目前 Flink 中 TaskExecutor 的資源是通過 Slot 來描述的,一個 Slot 一般可以執行一個具體的 Task,但在一些情況下也可以執行多個相關聯的 Task,這部分內容將在下文進行詳述。ResourceManager 選擇到空閒的 Slot 之後,就會通知相應的 TM “將該 Slot 分配分 JobManager XX ”,然後 TaskExecutor 進行相應的記錄後,會向 JobManager 進行註冊。JobManager 收到 TaskExecutor 註冊上來的 Slot 後,就可以實際提交 Task 了。

TaskExecutor 收到 JobManager 提交的 Task 之後,會啟動一個新的執行緒來執行該 Task。Task 啟動後就會開始進行預先指定的計算,並通過資料 Shuffle 模組互相交換資料。

以上就是 Flink Runtime 層執行作業的基本流程。可以看出,Flink 支援兩種不同的模式,即 Per-job 模式與 Session 模式。如圖 3 所示,Per-job 模式下整個 Flink 叢集只執行單個作業,即每個作業會獨享 Dispatcher 和 ResourceManager 元件。此外,Per-job 模式下 AppMaster 和 TaskExecutor 都是按需申請的。因此,Per-job 模式更適合執行執行時間較長的大作業,這些作業對穩定性要求較高,並且對申請資源的時間不敏感。與之對應,在 Session 模式下,Flink 預先啟動 AppMaster 以及一組 TaskExecutor,然後在整個叢集的生命週期中會執行多個作業。可以看出,Session 模式更適合規模小,執行時間短的作業。


圖3. Flink Runtime 支援兩種作業執行的模式。

3. 資源管理與作業排程

本節對 Flink 中資源管理與作業排程的功能進行更深入的說明。實際上,作業排程可以看做是對資源和任務進行匹配的過程。如上節所述,在 Flink 中,資源是通過 Slot 來表示的,每個 Slot 可以用來執行不同的 Task。而在另一端,任務即 Job 中實際的 Task,它包含了待執行的使用者邏輯。排程的主要目的就是為了給 Task 找到匹配的 Slot。邏輯上來說,每個 Slot 都應該有一個向量來描述它所能提供的各種資源的量,每個 Task 也需要相應的說明它所需要的各種資源的量。但是實際上在 1.9 之前,Flink 是不支援細粒度的資源描述的,而是統一的認為每個 Slot 提供的資源和 Task 需要的資源都是相同的。從 1.9 開始,Flink 開始增加對細粒度的資源匹配的支援的實現,但這部分功能目前仍在完善中。

作業排程的基礎是首先提供對資源的管理,因此我們首先來看下 Flink 中資源管理的實現。如上文所述,Flink 中的資源是由 TaskExecutor 上的 Slot 來表示的。如圖 4 所示,在 ResourceManager 中,有一個子元件叫做 SlotManager,它維護了當前叢集中所有 TaskExecutor 上的 Slot 的資訊與狀態,如該 Slot 在哪個 TaskExecutor 中,該 Slot 當前是否空閒等。當 JobManger 來為特定 Task 申請資源的時候,根據當前是 Per-job 還是 Session 模式,ResourceManager 可能會去申請資源來啟動新的 TaskExecutor。當 TaskExecutor 啟動之後,它會通過服務發現找到當前活躍的 ResourceManager 並進行註冊。在註冊資訊中,會包含該 TaskExecutor中所有 Slot 的資訊。 ResourceManager 收到註冊資訊後,其中的 SlotManager 就會記錄下相應的 Slot 資訊。當 JobManager 為某個 Task 來申請資源時, SlotManager 就會從當前空閒的 Slot 中按一定規則選擇一個空閒的 Slot 進行分配。當分配完成後,如第 2 節所述,RM 會首先向 TaskManager 傳送 RPC 要求將選定的 Slot 分配給特定的 JobManager。TaskManager 如果還沒有執行過該 JobManager 的 Task 的話,它需要首先向相應的 JobManager 建立連線,然後傳送提供 Slot 的 RPC 請求。在 JobManager 中,所有 Task 的請求會快取到 SlotPool 中。當有 Slot 被提供之後,SlotPool 會從快取的請求中選擇相應的請求並結束相應的請求過程。


圖4. Flink 中資源管理功能各模組互動關係。

當 Task 結束之後,無論是正常結束還是異常結束,都會通知 JobManager 相應的結束狀態,然後在 TaskManager 端將 Slot 標記為已佔用但未執行任務的狀態。JobManager 會首先將相應的 Slot 快取到 SlotPool 中,但不會立即釋放。這種方式避免瞭如果將 Slot 直接還給 ResourceManager,在任務異常結束之後需要重啟時,需要立刻重新申請 Slot 的問題。通過延時釋放,Failover 的 Task 可以儘快排程回原來的 TaskManager,從而加快 Failover 的速度。當 SlotPool 中快取的 Slot 超過指定的時間仍未使用時,SlotPool 就會發起釋放該 Slot 的過程。與申請 Slot 的過程對應,SlotPool 會首先通知 TaskManager 來釋放該 Slot,然後 TaskExecutor 通知 ResourceManager 該 Slot 已經被釋放,從而最終完成釋放的邏輯。

除了正常的通訊邏輯外,在 ResourceManager 和 TaskExecutor 之間還存在定時的心跳訊息來同步 Slot 的狀態。在分散式系統中,訊息的丟失、錯亂不可避免,這些問題會在分散式系統的元件中引入不一致狀態,如果沒有定時訊息,那麼元件無法從這些不一致狀態中恢復。此外,當元件之間長時間未收到對方的心跳時,就會認為對應的元件已經失效,並進入到 Failover 的流程。

在 Slot 管理基礎上,Flink 可以將 Task 排程到相應的 Slot 當中。如上文所述,Flink 尚未完全引入細粒度的資源匹配,預設情況下,每個 Slot 可以分配給一個 Task。但是,這種方式在某些情況下會導致資源利用率不高。如圖 5 所示,假如 A、B、C 依次執行計算邏輯,那麼給 A、B、C 分配分配單獨的 Slot 就會導致資源利用率不高。為了解決這一問題,Flink 提供了 Share Slot 的機制。如圖 5 所示,基於 Share Slot,每個 Slot 中可以部署來自不同 JobVertex 的多個任務,但是不能部署來自同一個 JobVertex 的 Task。如圖5所示,每個 Slot 中最多可以部署同一個 A、B 或 C 的 Task,但是可以同時部署 A、B 和 C 的各一個 Task。當單個 Task 佔用資源較少時,Share Slot 可以提高資源利用率。 此外,Share Slot 也提供了一種簡單的保持負載均衡的方式。


圖5.Flink Share Slot 示例。使用 Share Slot 可以在每個 Slot 中部署來自不同 JobVertex 的多個 Task。

基於上述 Slot 管理和分配的邏輯,JobManager 負責維護作業中 Task執行的狀態。如上文所述,Client 端會向 JobManager 提交一個 JobGraph,它代表了作業的邏輯結構。JobManager 會根據 JobGraph 按並發展開,從而得到 JobManager 中關鍵的 ExecutionGraph。ExecutionGraph 的結構如圖 5 所示,與 JobGraph 相比,ExecutionGraph 中對於每個 Task 與中間結果等均建立了對應的物件,從而可以維護這些實體的資訊與狀態。


圖6.Flink 中的 JobGraph 與 ExecutionGraph。ExecutionGraph 是 JobGraph 按並發展開所形成的,它是 JobMaster 中的核心資料結構。

在一個 Flink Job 中是包含多個 Task 的,因此另一個關鍵的問題是在 Flink 中按什麼順序來排程 Task。如圖 7 所示,目前 Flink 提供了兩種基本的排程邏輯,即 Eager 排程與 Lazy From Source。Eager 排程如其名子所示,它會在作業啟動時申請資源將所有的 Task 排程起來。這種排程演算法主要用來排程可能沒有終止的流作業。與之對應,Lazy From Source 則是從 Source 開始,按拓撲順序來進行排程。簡單來說,Lazy From Source 會先排程沒有上游任務的 Source 任務,當這些任務執行完成時,它會將輸出資料快取到記憶體或者寫入到磁碟中。然後,對於後續的任務,當它的前驅任務全部執行完成後,Flink 就會將這些任務排程起來。這些任務會從讀取上游快取的輸出資料進行自己的計算。這一過程繼續進行直到所有的任務完成計算。


圖7. Flink 中兩種基本的排程策略。其中 Eager 排程適用於流作業,而Lazy From Source 適用於批作業。

4. 錯誤恢復

在 Flink 作業的執行過程中,除正常執行的流程外,還有可能由於環境等原因導致各種型別的錯誤。整體上來說,錯誤可能分為兩大類:Task 執行出現錯誤或 Flink 叢集的 Master 出現錯誤。由於錯誤不可避免,為了提高可用性,Flink 需要提供自動錯誤恢復機制來進行重試。

對於第一類 Task 執行錯誤,Flink 提供了多種不同的錯誤恢復策略。如圖 8 所示,第一種策略是 Restart-all,即直接重啟所有的 Task。對於 Flink 的流任務,由於 Flink 提供了 Checkpoint 機制,因此當任務重啟後可以直接從上次的 Checkpoint 開始繼續執行。因此這種方式更適合於流作業。第二類錯誤恢復策略是 Restart-individual,它只適用於 Task 之間沒有資料傳輸的情況。這種情況下,我們可以直接重啟出錯的任務。


圖8.Restart-all 錯誤恢復策略示例。該策略會直接重啟所有的 Task。


圖9.Restart-individual 錯誤恢復策略示例。該策略只適用於 Task之間不需要資料傳輸的作業,對於這種作業可以只重啟出現錯誤的 Task。

由於 Flink 的批作業沒有 Checkpoint 機制,因此對於需要資料傳輸的作業,直接重啟所有 Task 會導致作業從頭計算,從而導致一定的效能問題。為了增強對 Batch 作業,Flink 在1.9中引入了一種新的Region-Based的Failover策略。在一個 Flink 的 Batch 作業中 Task 之間存在兩種資料傳輸方式,一種是 Pipeline 型別的方式,這種方式上下游 Task 之間直接通過網路傳輸資料,因此需要上下游同時執行;另外一種是 Blocking 型別的試,如上節所述,這種方式下,上游的 Task 會首先將資料進行快取,因此上下游的 Task 可以單獨執行。基於這兩種型別的傳輸,Flink 將 ExecutionGraph 中使用 Pipeline 方式傳輸資料的 Task 的子圖叫做 Region,從而將整個 ExecutionGraph 劃分為多個子圖。可以看出,Region 內的 Task 必須同時重啟,而不同 Region 的 Task 由於在 Region 邊界存在 Blocking 的邊,因此,可以單獨重啟下游 Region 中的 Task。

基於這一思路,如果某個 Region 中的某個 Task 執行出現錯誤,可以分兩種情況進行考慮。如圖 8 所示,如果是由於 Task 本身的問題發生錯誤,那麼可以只重啟該 Task 所屬的 Region 中的 Task,這些 Task 重啟之後,可以直接拉取上游 Region 快取的輸出結果繼續進行計算。

另一方面,如圖如果錯誤是由於讀取上游結果出現問題,如網路連線中斷、快取上游輸出資料的 TaskExecutor 異常退出等,那麼還需要重啟上游 Region 來重新產生相應的資料。在這種情況下,如果上游 Region 輸出的資料分發方式不是確定性的(如 KeyBy、Broadcast 是確定性的分發方式,而 Rebalance、Random 則不是,因為每次執行會產生不同的分發結果),為保證結果正確性,還需要同時重啟上游 Region 所有的下游 Region。


圖10.Region-based 錯誤恢復策略示例一。如果是由於下游任務本身導致的錯誤,可以只重啟下游對應的 Region。


圖11.Region-based 錯誤恢復策略示例二。如果是由於上游失敗導致的錯誤,那麼需要同時重啟上游的 Region 和下游的 Region。實際上,如果下游的輸出使用了非確定的資料分割方式,為了保持資料一致性,還需要同時重啟所有上游 Region 的下游 Region。

除了 Task 本身執行的異常外,另一類異常是 Flink 叢集的 Master 進行發生異常。目前 Flink 支援啟動多個 Master 作為備份,這些 Master 可以通過 ZK 來進行選主,從而保證某一時刻只有一個 Master 在執行。當前活路的 Master 發生異常時,某個備份的 Master 可以接管協調的工作。為了保證 Master 可以準確維護作業的狀態,Flink 目前採用了一種最簡單的實現方式,即直接重啟整個作業。實際上,由於作業本身可能仍在正常執行,因此這種方式存在一定的改進空間。

5. 未來展望

Flink目前仍然在Runtime部分進行不斷的迭代和更新。目前來看,Flink未來可能會在以下幾個方式繼續進行優化和擴充套件:

  • 更完善的資源管理:從 1.9 開始 Flink 開始了對細粒度資源匹配的支援。基於細粒度的資源匹配,使用者可以為 TaskExecutor 和 Task 設定實際提供和使用的 CPU、記憶體等資源的數量,Flink 可以按照資源的使用情況進行排程。這一機制允許使用者更大範圍的控制作業的排程,從而為進一步提高資源利用率提供了基礎。
  • 統一的 Stream 與 Batch:Flink 目前為流和批分別提供了 DataStream 和 DataSet 兩套介面,在一些場景下會導致重複實現邏輯的問題。未來 Flink 會將流和批的介面都統一到 DataStream 之上。
  • 更靈活的排程策略:Flink 從 1.9 開始引入排程外掛的支援,從而允許使用者來擴充套件實現自己的排程邏輯。未來 Flink 也會提供更高效能的排程策略的實現。
  • Master Failover 的優化:如上節所述,目前 Flink 在 Master Failover 時需要重啟整個作業,而實際上重啟作業並不是必須的邏輯。Flink 未來會對 Master failover 進行進一步的優化來避免不必要的作業重啟。


本文作者:高贇(雲騫) 

原文連結

本文為雲棲社群原創內容,未經允許不得轉載。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69915408/viewspace-2657167/,如需轉載,請註明出處,否則將追究法律責任。

相關文章