1. Task and Operator Chain
Flink 應用程序是以並行的方式在 Task 的並行化算子中執行的。Flink 應用程序的性能取決於 Task 如何被調度執行。在此之前,需要了解幾個概念:
- Task:代表可以在單個線程中執行的 Operator Chain 的抽象。 諸如,keyBy(這會導致網絡改組通過 Key 對流進行分區),或者 Pipeline 並行度的變化都會破壞 Chain,並迫使 Operator 分配到不同的任務中。如圖 1 中,該應用程序具有 3 個 Task。
- Operator Chain:將兩個或多個算子的並行化 Task 融合到單個線程,並在該單個線程中執行。融合的 Task 通過方法調用交換數據,因此基本上沒有通信成本。所以,Chain 可以提高大多數應用程序的性能,這也是 Flink 默認的配置。
- Subtask:是任務的一個並行切片。它是可調度的、可運行的執行單元。
- Task Slot: 一個 Task Slot 具有運行應用程序的一個並行切片的資源。 Task Slot 的總數與應用程序的最大並行度相同。
2. Slot
每個 TaskManager 是一個 JVM 進程,它可以執行一個或多個 Subtask。而 TaskManager 中的 Task 的數量,就是用 Task Slot 來控制的。在 TaskManager 中,每個 Slot 擁有相等的內存資源。如圖2,在 TaskManager 中有 3 個 Slot,那麼每個 Slot 擁有 1/3 的資源。需要注意的是,Flink 對 Slot 只做了內存隔離,暫時並未做 CPU 隔離。
3. Slot and Parallelism
並行度是 TaskManager 可以並行執行的能力。Flink 默認配置一個應用程序的算子只有一個 Slot。修改應用程序並行度的方式有以下三種,且優先級依次增加:
- 通過修改配置文件 flink-conf.yaml 中的 taskmanager.numberOfTaskSlots,對所有應用程序設置相同的併發度;
- 通過調用 env.setParallelism(int) 方法,對應用程序的每個算子設置相同併發度;
- 在代碼裏通過對每個算子調用 setParallelism(int) 方法,對指定算子的並行度進行修改。
網友給了一組圖進行了比較好的描述,如圖 3 中,應用程序的最大並行度是 9,taskmanager.numberOfTaskSlots 表示每個 TaskManager 擁有 3 個 Slot,即每個 TaskManager 的併發能力是 3。
如圖 4 中,應用程序的最大並行度是 9,每個 TaskManager 擁有 3 個 Slot,而 Source→flatMap、Reduce、Sink 這三組算子的併發度是 1。
圖 5 是一張對比圖,其中分別把應用程序的並行度設置爲 2 和 9。不難發現,並行度設置爲 2 時,應用程序並沒有充分使用每個 Slot,即資源沒有充分利用,會使性能下降。
上文提到過,我們除了可以對整個應用程序的每個算子設置同樣的並行度之外,還可以分別對不同的算子設置不同的並行度。如圖 6 所示,Source→flatMap 和 Reduce 的並行度設置爲 9,而 Sink 的並行度設置爲 1,同時也覆蓋了 flink-conf.yaml 或者 env.setParallelism(int) 的設置。
4. Slot-Sharing Group
默認情況下,Flink 的每個算子都在名爲 default 的 Slot-Sharing Group 裏。我們也可以顯示地用 slotSharingGroup(String) 方法指定算子的 Slot-Sharing Group。如果一個算子和它的直接上游算子都屬於同一個 Group,那麼該算子將會繼承直接上游算子的 Slot-Sharing Group。
結合前面所講述的,我們現在看一段示例代碼:
// ssg:oringe
val a: DataStream[A] = env.addSource(...)
.slotSharingGroup("oringe")
.setParallelism(4)
val b: DataStream[B] = a.map(...)
// ssg oringe is inherited from a
.setParallelism(4)
// ssg:yellow
val c: DataStream[C] = env.addSource(...)
.slotSharingGroup("yellow")
.setParallelism(2)
// ssg:blue
val d: DataStream[D] = b.connect(c)
.process(...)
.slotSharingGroup("blue")
.setParallelism(4)
val e: DataStream[E] = d.addSink(...)
// ssg blue is inherited from d
.setParallelism(2)
上面的栗子中,共有 5 個算子,兩個 Source 是 a 和 c,兩個 Transformer 是 b 和 d,一個 Sink 是 e;三個 Slot-Sharing Group 分別是 a 和 b、c、d 和 e,即 oringe、yellow、blue。該栗子的 Job Graph 如圖 7 所示。
該栗子一共需要 10 個 Slot,其中 orange 和 blue 各需要 4 個 Slot,yellow 需要 2 個 Slot。
5. Conclusion
Slot 是 Flink 應用程序的資源單位,但只能做到內存隔離,暫不支持 CPU 隔離。Slot 的總數與應用程序的最大並行度相同。如果設置的並行度大於應用程序的實際的並行度時,那麼會導致資源浪費,比如 Kafka Partition 小於 Flink Source 的並行度。
掃碼關注公衆號:冰山烈焰的黑板報