Flink 調優:Slot and Parallelism

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 的總數與應用程序的最大並行度相同。
    圖1 Tasks and Operator Chains

2. Slot

每個 TaskManager 是一個 JVM 進程,它可以執行一個或多個 Subtask。而 TaskManager 中的 Task 的數量,就是用 Task Slot 來控制的。在 TaskManager 中,每個 Slot 擁有相等的內存資源。如圖2,在 TaskManager 中有 3 個 Slot,那麼每個 Slot 擁有 1/3 的資源。需要注意的是,Flink 對 Slot 只做了內存隔離,暫時並未做 CPU 隔離
圖2 Task Slot

3. Slot and Parallelism

並行度是 TaskManager 可以並行執行的能力。Flink 默認配置一個應用程序的算子只有一個 Slot。修改應用程序並行度的方式有以下三種,且優先級依次增加:

  1. 通過修改配置文件 flink-conf.yaml 中的 taskmanager.numberOfTaskSlots,對所有應用程序設置相同的併發度;
  2. 通過調用 env.setParallelism(int) 方法,對應用程序的每個算子設置相同併發度;
  3. 在代碼裏通過對每個算子調用 setParallelism(int) 方法,對指定算子的並行度進行修改。

網友給了一組圖進行了比較好的描述,如圖 3 中,應用程序的最大並行度是 9,taskmanager.numberOfTaskSlots 表示每個 TaskManager 擁有 3 個 Slot,即每個 TaskManager 的併發能力是 3。
圖3 最大並行度是 9 的應用程序
如圖 4 中,應用程序的最大並行度是 9,每個 TaskManager 擁有 3 個 Slot,而 Source→flatMap、Reduce、Sink 這三組算子的併發度是 1。
圖4 最大並行度是 9 的應用程序且算子併發度是 1
圖 5 是一張對比圖,其中分別把應用程序的並行度設置爲 2 和 9。不難發現,並行度設置爲 2 時,應用程序並沒有充分使用每個 Slot,即資源沒有充分利用,會使性能下降。
圖 5 最大並行度是 9 的應用程序且算子併發度分別是 2 和 9
上文提到過,我們除了可以對整個應用程序的每個算子設置同樣的並行度之外,還可以分別對不同的算子設置不同的並行度。如圖 6 所示,Source→flatMap 和 Reduce 的並行度設置爲 9,而 Sink 的並行度設置爲 1,同時也覆蓋了 flink-conf.yaml 或者 env.setParallelism(int) 的設置。
圖 6 最大並行度是 9 的應用程序且對算子顯示設置併發度

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 所示。
圖7 示例的 Job Graph
該栗子一共需要 10 個 Slot,其中 orange 和 blue 各需要 4 個 Slot,yellow 需要 2 個 Slot。

5. Conclusion

Slot 是 Flink 應用程序的資源單位,但只能做到內存隔離,暫不支持 CPU 隔離。Slot 的總數與應用程序的最大並行度相同。如果設置的並行度大於應用程序的實際的並行度時,那麼會導致資源浪費,比如 Kafka Partition 小於 Flink Source 的並行度。

掃碼關注公衆號:冰山烈焰的黑板報
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章