作者:京東物流 吳雲濤
前言
提交一個DataSteam 的 Flink應用,需要經過 StreamGraph、JobGraph、ExecutionGraph 三個階段的轉換生成可成執行的有向無環圖(DAG),並在 Flink 叢集上執行。而提交一個 Flink SQL 應用,其執行流程也類似,只是多了一步使用 flink-table-planer
模組從SQL轉換成 StreamGraph 的過程。以下是利用Flink的 StreamGraph 透過低程式碼的方式,來實現StreamGraph的生成,並最終實現 Flink 程式零程式碼開發的解決方案。
一、Flink 相關概念
在Flink程式中,每個運算元被稱作Operator,透過各個運算元的處理最終得到期望的加工後資料。比如下面這段程式中,增加了Source, Fiter, Map, Sink 4個運算元。
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream dataStream = env.addSource(new FlinkKafkaConsumer("topic"));
DataStream filteredStream = dataStream.filter(new FilterFunction() {
@Override
public boolean filter(Object value) throws Exception {return true;}
});
DataStream mapedStream = filteredStream.map(new MapFunction() {
@Override
public Object map(Object value) throws Exception {return value;}
});
mapedStream.addSink(new DiscardingSink());
env.execute("test-job");
StreamGraph
Flink的邏輯執行圖,描述了整個流處理任務的流程和資料流轉遞規則,包括了資料來源(Source)、轉換運算元(Transform)、資料目的端(Sink)等元素,以及它們之間的依賴關係和傳輸規則。StreamGraph是透過Flink的API或者DSL來構建的向無環圖(DAG),它與JobGraph之間是一一對應的關係。StreamGraph中的頂點稱為streamNode,是用來表示Operator運算元的類,包含了運算元uid、並行度,是否共享slot(SlotSharingGroup)等資訊。邊稱作streamEdge。透過StreamingJobGraphGenerator類生成JobGraph。
JobGraph
StreamGraph 經過 flink-optimizer 模組最佳化後生成 JobGraph。生成
JobGraph 時,會將多個滿足條件的運算元chain 連結到一起作為一個頂點(JobVertex), 在執行時對應1個 Task。Task 是
Flink 程式的基本執行單元,任務排程時將Task分配到TaskManager上執行。
ExecutionGraph
物理執行圖是由JobGraph轉換而來,描述了整個流處理任務的物理執行細節,包括了任務的排程、任務的執行順序、任務之間的資料傳輸、任務的狀態管理等。Task會在步驟中拆分為多個SubTask。對應Task中的每個並行度。
Physical Graph
PhysicalGraph是在執行時的ExecutionGraph。ExecutionGraph中的每一個頂點ExecutionJobVertex都對應一個或多個頂點ExecutionVertex,它們是物理執行圖中的節點。
二、畫布模式實現思路
實現流程
首先,我們採用畫布模式(拖拉拽方式)來實現Flink程式的組裝,將極大程度上方便我們複用部分加工的運算元,最終實現零程式碼的Flink應用開發。我們透過繪圖的方式,直接將內建的運算元繪製在圖示上。如下所示:
-
構建有向無環圖(DAG),並持久化。透過拖拉拽的方式(畫布模式)構建你的Flink應用,後端的持久化儲存採用鄰接表方式。我們在 mysql 關聯式資料庫中將 Node(運算元:Source、Sink、中間加工邏輯運算元)儲存到 flink_node 表中;將邊存到一張 flink_realation 表中。
-
重新組將Flink作業
要組裝以上畫布模式的Flink應用,首先需要初始化好 StreamExecutionEnvironment 相關引數,其次將上述表中的 flink_node 和flink_edge 轉化為DataStream,並將轉化出的 DataStream 合理地拼接成一個 DataStream API Flink 應用程式。
在將flink_node、flink_edge轉為為DataStream時選擇何種遍歷演算法來組裝呢?我們知道有向無環圖的遍歷最常用的有:深度優先遍歷(DFS)和廣度優先遍歷(BFS)。這裡我們採用了BFS演算法+層序遍歷的方式,BFS便於在組裝的過程中將已visit到的node節點拼裝到其parent 的節點上。
總結
在實際的實現過程中,遇到的問題往往比以上覆雜很多。比如需要將更多的資訊儲存在node節點和edge邊上。node上需要儲存並行度、運算元處理前後的表schema等;edge需要儲存keyby的欄位、上下游之間的資料shuffle的方式等等。此外在內建的運算元無法滿足使用者需求時,還需要考慮如何友好的支援自定義運算元(UDF)的嵌入等問題。