Flink運行時架構主要包括四個不同的組件,它們會在運行流處理應用程序時協同工作:作業管理器(JobManager)、資源管理器(ResourceManager)、任務管理器(TaskManager),以及分發器(Dispatcher)。因爲Flink是用Java和Scala實現的,所以所有組件都會運行在Java虛擬機上。每個組件的職責如下:
作業管理器(JobManager)
- 控制一個應用程序執行的主進程,也就是說,每個應用程序都會被一個不同的JobManager 所控制執行。
- JobManager 會先接收到要執行的應用程序,這個應用程序會包括:作業圖(JobGraph)、邏輯數據流圖(logical dataflow graph)和打包了所有的類、庫和其它資源的JAR包。
- JobManager 會把JobGraph轉換成一個物理層面的數據流圖,這個圖被叫做“執行圖”(ExecutionGraph),包含了所有可以併發執行的任務。
- JobManager 會向資源管理器(ResourceManager)請求執行任務必要的資源,也就是任務管理器(TaskManager)上的插槽(slot)。一旦它獲取到了足夠的資源,就會將執行圖分發到真正運行它們的TaskManager上。而在運行過程中,JobManager會負責所有需要中央協調的操作,比如說檢查點(checkpoints)的協調。
任務管理器(TaskManager)
- Flink中的工作進程。通常在Flink中會有多個TaskManager運行,每一個TaskManager都包含了一定數量的插槽(slots)。插槽的數量限制了TaskManager能夠執行的任務數量。
- 啓動之後,TaskManager會向資源管理器註冊它的插槽;收到資源管理器的指令後,TaskManager就會將一個或者多個插槽提供給JobManager調用。JobManager就可以向插槽分配任務(tasks)來執行了。
- 在執行過程中,一個TaskManager可以跟其它運行同一應用程序的TaskManager交換數據。
資源管理器(ResourceManager)
- 主要負責管理任務管理器(TaskManager)的插槽(slot),TaskManger 插槽是Flink中定義的處理資源單元。
- Flink爲不同的環境和資源管理工具提供了不同資源管理器,比如YARN、Mesos、K8s,以及standalone部署。
- 當JobManager申請插槽資源時,ResourceManager會將有空閒插槽的TaskManager分配給JobManager。如果ResourceManager沒有足夠的插槽來滿足JobManager的請求,它還可以向資源提供平臺發起會話,以提供啓動TaskManager進程的容器。
分發器(Dispatcher)
- 可以跨作業運行,它爲應用提交提供了REST接口。
- 當一個應用被提交執行時,分發器就會啓動並將應用移交給一個JobManager。
- Dispatcher也會啓動一個Web UI,用來方便地展示和監控作業執行的信息。
- Dispatcher在架構中可能並不是必需的,這取決於應用提交運行的方式。
任務提交流程
這是從一個較爲高層級的視角,來看應用中各組件的交互協作。如果部署的集羣環境不同(例如YARN,Mesos,Kubernetes,standalone等),其中一些步驟可以被省略,或是有些組件會運行在同一個JVM進程中。
Yarn模式任務提交流程
- Flink任務提交後,Client向HDFS上傳Flink的Jar包和配置
- 之後客戶端向Yarn ResourceManager提交任務,ResourceManager分配Container資源並通知對應的NodeManager啓動ApplicationMaster
- ApplicationMaster啓動後加載Flink的Jar包和配置構建環境,去啓動JobManager,之後JobManager向Flink自身的RM進行申請資源,自身的RM向Yarn 的ResourceManager申請資源(因爲是yarn模式,所有資源歸yarn RM管理)啓動TaskManager
- Yarn ResourceManager分配Container資源後,由ApplicationMaster通知資源所在節點的NodeManager啓動TaskManager
- NodeManager加載Flink的Jar包和配置構建環境並啓動TaskManager,TaskManager啓動後向JobManager發送心跳包,並等待JobManager向其分配任務。
任務調度原理
- 客戶端不是運行時和程序執行的一部分,但它用於準備併發送dataflow(JobGraph)給Master(JobManager),然後,客戶端斷開連接或者維持連接以等待接收計算結果。而Job Manager會產生一個執行圖(Dataflow Graph)
- 當 Flink 集羣啓動後,首先會啓動一個 JobManger 和一個或多個的 TaskManager。由 Client 提交任務給 JobManager,JobManager 再調度任務到各個 TaskManager 去執行,然後 TaskManager 將心跳和統計信息彙報給 JobManager。TaskManager 之間以流的形式進行數據的傳輸。上述三者均爲獨立的 JVM 進程。
- Client 爲提交 Job 的客戶端,可以是運行在任何機器上(與 JobManager 環境連通即可)。提交 Job 後,Client 可以結束進程(Streaming的任務),也可以不結束並等待結果返回。
- JobManager 主要負責調度 Job 並協調 Task 做 checkpoint,職責上很像 Storm 的 Nimbus。從 Client 處接收到 Job 和 JAR 包等資源後,會生成優化後的執行計劃,並以 Task 的單元調度到各個 TaskManager 去執行。
- TaskManager 在啓動的時候就設置好了槽位數(Slot),每個 slot 能啓動一個 Task,Task 爲線程。從 JobManager 處接收需要部署的 Task,部署啓動後,與自己的上游建立 Netty 連接,接收數據並處理。
TaskManger與Slots與parallelism
- Flink 中每一個 TaskManager 都是一個JVM進程,它可能會在獨立的線程上執行一個或多個子任務
- 爲了控制一個 TaskManager 能接收多少個 task, TaskManager 通過 task slot 來進行控制(一個 TaskManager 至少有一個 slot)
- 圖中每個Task Manager中的Slot爲3個,那麼兩個Task Manager一共有六個Slot, 而這6個Slot代表着Task Manager最大的併發執行能力,一共能可以執行6個task進行同時執行
- Slot是靜態概念,代表着Task Manager具有的併發執行能力,可以通過參數taskmanager.numberOfTaskSlots進行配置
- 爲了控制一個 TaskManager 能接收多少個 task, TaskManager 通過 task slot 來進行控制(一個 TaskManager 至少有一個 slot)
- 圖中Source和Map是一個Task,且並行度(我們設置的setParallelism())都爲1,指這個task任務的並行能力爲1,只佔用一個Slot資源
7. 在第二張圖中爲Flink的共享子任務,如果一個TaskManager一個slot,那將意味着每個task group運行在獨立的JVM中(該JVM可能是通過一個特定的容器啓動的),而一個TaskManager多個slot意味着更多的subtask可以共享同一個JVM。而在同一個JVM進程中的task將共享TCP連接(基於多路複用)和心跳消息。它們也可能共享數據集和數據結構,因此這減少了每個task的負載。
8. 並行度parallelism是動態概念,即TaskManager運行程序時實際使用的併發能力,可以通過參數parallelism.default進行配置。
- 也就是說,假設一共有3個TaskManager,每一個TaskManager中的分配3個TaskSlot,也就是每個TaskManager可以接收3個task,一共9個TaskSlot,如果我們設置parallelism.default=1,即運行程序默認的並行度爲1,9個TaskSlot只用了1個,有8個空閒,因此,設置合適的並行度才能提高效率。
- 一個特定算子的 子任務(subtask)的個數被稱之爲其並行度(parallelism),我們可以對單獨的每個算子進行設置並行度,也可以直接用env設置全局的並行度,更可以在頁面中去指定並行度。
- 最後,由於並行度是實際Task Manager處理task 的能力,而一般情況下,一個 stream 的並行度,可以認爲就是其所有算子中最大的並行度,則可以得出在設置Slot時,在所有設置中的最大設置的並行度大小則就是所需要設置的Slot的數量。
程序與數據流
- 所有的Flink程序都是由三部分組成的: Source 、Transformation 和 Sink。
- Source 負責讀取數據源,Transformation 利用各種算子進行處理加工,Sink 負責輸出
- 在運行時,Flink上運行的程序會被映射成“邏輯數據流”(dataflows),它包含了這三部分
- 每一個dataflow以一個或多個sources開始以一個或多個sinks結束。dataflow類似於任意的有向無環圖(DAG)
- 在大部分情況下,程序中的轉換運算(transformations)跟dataflow中的算子(operator)是一一對應的關係
執行圖
- Flink 中的執行圖可以分成四層:StreamGraph -> JobGraph -> ExecutionGraph -> 物理執行圖
- StreamGraph:是根據用戶通過 Stream API 編寫的代碼生成的最初的圖。用來表示程序的拓撲結構。
- JobGraph:StreamGraph經過優化後生成了 JobGraph,提交給 JobManager 的數據結構。主要的優化爲,將多個符合條件的節點 chain 在一起作爲一個節點
- ExecutionGraph:JobManager 根據 JobGraph 生成ExecutionGraph。ExecutionGraph是JobGraph的並行化版本,是調度層最核心的數據結構。
- 物理執行圖:JobManager 根據 ExecutionGraph 對 Job 進行調度後,在各個TaskManager 上部署 Task 後形成的“圖”,並不是一個具體的數據結構。
數據傳輸形式
- 一個程序中,不同的算子可能具有不同的並行度
- 算子之間傳輸數據的形式可以是 one-to-one (forwarding) 的模式也可以是redistributing 的模式,具體是哪一種形式,取決於算子的種類
- One-to-one:stream維護着分區以及元素的順序(比如source和map之間)。這意味着map 算子的子任務看到的元素的個數以及順序跟 source 算子的子任務生產的元素的個數、順序相同。map、fliter、flatMap等算子都是one-to-one的對應關係。
- Redistributing:stream的分區會發生改變。每一個算子的子任務依據所選擇的transformation發送數據到不同的目標任務。例如,keyBy 基於 hashCode 重分區、而 broadcast 和 rebalance 會隨機重新分區,這些算子都會引起redistribute過程,而 redistribute 過程就類似於 Spark 中的 shuffle 過程。
任務鏈(Operator Chains)
- Flink 採用了一種稱爲任務鏈的優化技術,可以在特定條件下減少本地通信的開銷。爲了滿足任務鏈的要求,必須將兩個或多個算子設爲相同的並行度,並通過本地轉發(local forward)的方式進行連接
- 相同並行度的 one-to-one 操作,Flink 這樣相連的算子鏈接在一起形成一個 task,原來的算子成爲裏面的 subtask
- 並行度相同、並且是 one-to-one 操作,兩個條件缺一不可
- 而爲什麼需要並行度相同,因爲若flatMap並行度爲1,到了之後的map並行度爲2,從flatMap到map的數據涉及到數據由於並行度map爲2會往兩個slot處理,數據會分散,所產生的元素個數和順序發生的改變所以有2個單獨的task,不能成爲任務鏈