深入解析Flink網絡協議棧

深入解析Flink網絡協議棧

原文地址:https://flink.apache.org/2019/06/05/flink-network-stack.html
05 Jun 2019 Nico Kruber

Flink的網絡協議棧是Flink的核心組件之一,構成了flink-runtime模塊,並且是每個Flink job運行的關鍵。協議棧連接了所有TaskManager的獨立工作單元(subtasks)。這是用戶輸入的流式數據傳輸的載體,因此對於Flink job的吞吐量、延時的性能至關重要。TaskManager和JobManager之間的RPC採用Akka,TaskManager之間的網絡協議棧採用更底層的Netty。
這篇博客文章是關於網絡堆棧的系列文章中的第一篇。在接下來的篇幅中,我們將會首先概覽暴露給stream operator的網絡抽象,然後詳細介紹物理實現和Flink做的各種優化。我們將會主要展現這些優化的效果,以及FLink在吞吐量和延時所做的權衡。本系列博客之後的文章,將會在monitoring,metrics,調優參數以及反模式等方面詳細描述。

  • 邏輯視圖
  • 物理傳輸
    • 造成反壓力
  • 基於認證的流控制
    • 造成反壓力
    • 我們得到了什麼?問題在哪裏?
  • 將記錄寫入網絡buffer,再次讀取他們
    • 將buffer信息給netty
    • buffer Builder & buffer消費者
  • 延時 vs. 吞吐量
  • 結論

邏輯視圖

Flink的網絡協議棧爲subtask之間的通信提供了下面的邏輯視圖。例如,keyBy()函數在網絡shuffle時的示意圖。在這裏插入圖片描述

上圖抽象展示了下面三個概念的不同設置:

  • subtask輸出類型(ResultPartitionType):
    • pipelined(bounded 或者unbounded):當有數據產生的時候,立即將數據發送給下游。數據很有可能一個接一個地以有界或無界記錄流的形式發送到下游。
    • blocking:只有當整個結果都生成,纔將數據發送給下游。
  • 調度模式:
    • all at once(eager):在同一時刻部署job下所有的subtask。(用於流式應用)
    • next stage on first output(lazy):當task的數據生產者產生輸出的時候,才部署下游的task。
    • next stage on complete output:當task的數據生產者產生了完整的數據集合,才部署下游的task。
  • 傳輸
    • 高吞吐量:與一個一個發送記錄相反,Flink在網絡緩衝區緩衝了一批數據,並將它們一起發出。這降低了單個記錄的傳輸消耗,提升了吞吐量。
    • 通過buffer超時的低延時:通過降低緩衝區數據超時時間,即使緩衝區沒有被數據填滿,從而讓用戶可以犧牲吞吐量來降低延時。

我們將會在第二部分描述吞吐量和低延時的優化,涉及了網絡協議棧的物理層。對於這一部分,讓我們詳細說明一下輸出和調度類型。首先,重要的是用戶要知道子任務輸出類型和調度類型是緊密交織在一起的,只有這兩種類型的特定組合纔是有效的。
pipeline的數據分區是流式風格的輸出,需要目標subtask是存活的。subtask需要在結果產生之前或第一個輸出之前就被調度完成。批量job生成有邊界的結果分區數據,流式job產生無界的結果。
批量job可能也產生阻塞形式的結果,這依賴於operator和鏈接方式。在這種情況下,在接收數據的task被調度之前,完整的數據必鬚生成完成。這允許批量job更加高效和低利用率。
下面的表格描述了可用的組合:
在這裏插入圖片描述
注:N/a1目前Flink沒有采用
N/a2當Batch/Streaming整合完成,這種組合纔會適用流式job

另外,當subtask有多個輸入,調度按兩種方式開始:after all或者after any。

物理傳輸

爲了理解物理數據通信,我們回顧一下,在Flink中不同的task可能通過slot sharing group共享同一個slot。TaskManager也可能爲同一個task的subtask提供多個slot。
舉例如下圖。我們假設並行度爲4,且兩個taskmanager每人提供兩個slot。TaskManager1執行A1,A2,B1,B2。TaskManager2執行A3,A4,B3,B4。在shuffle模式下,例如keyby()產生的結果,在每個TaskManager上將會有2X4個邏輯連接。這些連接可能是本地的,也可能是遠程的。
在這裏插入圖片描述
在Flink的網絡協議棧中,不同task之間的每個遠程網絡連接都會佔用一個TCP通道。然而,如果同一個task的不同subtask被調度在同一個TaskManager中,他們連接到相同的TaskManager的鏈接將會被混用,共享一個TCP鏈接,從而降低資源佔用。在我們的實例中,鏈接爲:A.1 → B.3, A.1 → B.4,A.2 → B.3, A.2 → B.4。
在這裏插入圖片描述
每個子任務的結果被稱爲ResultPartition,每個結果被分割爲單獨的resultsubpartition—每個邏輯通道一個。在這種情況下的協議棧,Flink沒有單獨處理各個記錄,而是將一組序列化的記錄組裝到一起發送到網絡緩衝區。每個subtask自己獨立使用的本地buffer池(接收和發送各一個)都被限制了大小:

#channels * buffers-per-channel + floating-buffers-per-gate

通常情況下每個TaskManager不需要配置buffer的全部數量。Configuring the Network buffers章節詳細描述了怎樣設置。

反壓(1)

只要有一個subtask緩衝池耗盡,那麼所有的數據傳輸都會被阻塞。如下圖:
在這裏插入圖片描述
B4subtask的緩衝池資源耗盡,則整個公共池資源阻塞,導致整體不可用。
爲了防止這種情況,Flink1.5提供了自己的流控制機制。

基於認證的流控制

基於認證的流量控制確保“網絡上”的任何東西在接收端都有處理能力。這依賴於使用網絡緩存作爲之前的Flink機制的一個擴展。不僅僅共享一個本地緩衝池,每個遠程輸入channel擁有自己的buffer集合,且這些集合的使用是排他的。由於本地緩衝池中的buffer對每個通道都是可用的,相當於在各個通道之間流轉使用,所以被稱爲浮動緩存。
接受者將根據發送端的認證,聲明哪些buffer是自己可用的(1個buffer = 1個認證)。每個結果子分區將會保存他的通道認證軌跡。在認證可用的情況下,buffer僅僅面向低層次網絡協議棧,每個發送buffer順次減少認證分。除了buffer,我們也發送當前backlog的大小信息,這些信息描述了子分區隊列中有多少個buffer在等待。接受者將會使用這些信息,按照其中的backlog大小申請浮動buffer。但是真實大小是不定的,可能分配一些,也可能根本不分配buffer。接受者將會使用接收到的buffer,並且監聽接下來的可用buffer。
在這裏插入圖片描述
認證流控制使用buffers-per-channel來定義多少buffer是排他的,通過floating-buffers-per-gate定義多少緩衝池作爲本地浮動緩衝池。

反壓

當接受者不能運行,他可用的信用分將會達到0,接受者將會停止發送數據。只有這個邏輯通道發生反壓,混用的TCP通道並沒有被阻塞。其他的接受者並不會被影響。

我們得到了什麼?問題在哪裏?

由於在流控制的情況下,複用通道中的一個邏輯通道,不能阻塞其他的邏輯通道,總體的資源利用率應該增加。另外,通過完全控制有多少數據在“網絡中”,我們也能夠提升checkpoint對齊:沒有流控制,通道將會一定延遲後佔滿網絡緩衝區,然後才能判斷出接收方停止讀取數據。在這個期間,大量的緩衝數據將被滯留。任何一個checkpoint屏障必須在buffre中排隊,等待,直到所有問題被處理。
然而,接收端額外的聲明信息將會導致額外的消耗,尤其是使用SSL加密的通道的時候。單個輸入通道不能使用緩衝池中所有的緩衝區,因爲排他緩衝區不能共享。如果生產數據的速度快於返回認證的速度,將會消耗更多的時間發送數據。雖然可能影響job的性能,但是通常情況下使用流量控制有更高的性能。可以通過增加buffers-per-channel來增加排他緩衝區的大小,代價是佔用更多的內存。
另外一個值得注意的是,當使用認證流控制時:由於我們在發送者和接受者之間緩存更少的數據,用戶會更早的發現反壓。如果想要保留流控制的同時緩衝更多的數據,可以考慮通過floating-buffers-per-gate增加緩存。

在這裏插入圖片描述
在這裏插入圖片描述
可以通過設置taskmanager.network.credit-model: false關閉流控制。

將記錄寫入網絡buffer,再次讀取他們

下圖是上面描述的高層概覽圖。
在這裏插入圖片描述
當創建一個對象並傳遞它,例如通過Collector.collect(),Flink將對象傳遞給RecordWriter。RecordWriter將對象序列化成二進制,最終將二進制放入網絡緩衝。
RecordWriter 首先使用SpanningRecordSerializer將對象序列化成靈活的堆字節數組。然後試着將這些字節寫入對應的網絡通道buffer。
在接受者端,底層網絡協議棧(netty)將接收到的數據buffer寫入對應的輸入buffer。task的線程將會最終從這些buffer中讀取數據。task將數據交給RecordReader反序列化, 具體的操作使用SpillingAdaptiveSpanningRecordDeserializer實現。
和序列化一端類似,反序列化必須處理各種特殊情況,例如對象被切分到多個網絡緩衝區中。這可能是因爲數據對象過大超過buffer大小(
32KiB by default, set via taskmanager.memory.segment-size),或者因爲buffer剩餘的大小不夠用。

將buffer信息給netty

在上圖,基於認證的流控制算法實際上在Netty Server/Client組件中實現,RecordWriter寫入的緩衝區總是以空狀態添加到結果子分區中,然後逐漸填充(序列化的)記錄。但是Netty實際上何時獲取buffer?顯然系統不可能無論何時數據可用都立即獲取他們。因爲這樣會增加線程間通信和同步的消耗,以及使整個buffer變得過時。
在Flink中,Netty server在三種情況下消費buffer:

  • 寫入一個數據後,buffer被填滿
  • buffer到了超時時間
  • 接到一個特殊的消息,例如checkpoint屏障。

Flush after Buffer Full

RecordWriter使用當前記錄的本地序列化緩衝區,並將這些字節逐漸寫入位於相應結果子分區隊列的一個或多個網絡緩衝區。雖然一個RecordWriter可以在多個子分區上工作,但是每個子分區只能分配給一個RecordWriter。另一邊,Netty server從多個子分區讀取數據,並混合寫入一個通道中。這是使用網絡緩衝作爲中間件實現生產者-消費者模式的經典場景。當完成1)序列化,2)將數據寫入buffer,RecordWriter更新相應的緩衝寫index。當buffer被寫滿,RecordWriter從本地緩衝池申請一個新的緩衝,並將當前記錄的剩餘字節,或者下一個記錄的信息寫入新的緩衝。這時如果Netty server還未知曉,將會4)通知Netty server數據可用。無論何時Netty能夠處理此通知,Netty server將會獲取此buffer,通過tcp通道發送這些數據。
在這裏插入圖片描述

Flush after Buffer Timeout

爲了支持低延遲的場景,我們不能僅僅依賴於buffer寫滿時觸發向下遊傳輸數據。可能存在這樣的場景,通信通道並沒有太多的數據傳輸,不需要爲了幾條數據增加了延時。因此不管數據是否準備好,一個定期的操作會發送數據:輸出淨化器。可以通過StreamExecutionEnvironment#setBufferTimeout設置週期長度,可以看成延遲的上限。下圖展示了flusher怎樣同其他組件協作:RecordWriter負責序列化和寫入網絡緩存,輸出flusher負責在超時的情況下通知Netty server。當Netty處理這些通知,他將從緩存消費掉可用數據信息,並更新緩存讀索引。Netty server下次會繼續從讀索引的位置讀取數據。
在這裏插入圖片描述

注意:嚴格意義上來講,Flusher並不能保證什麼。他只是負責向Netty發送通知。這意味着輸出Flusher對於通道是否發生反壓是沒有任何影響的。

Flush after special event

RecordWriter發出的一些特殊事件也會觸發立即清空緩存。最重要的事件就是checkpoint屏障或者end-of-partition事件。

Further remarks

與低於1.5版本的Flink相比,我們直接將網絡緩衝放在了子分區隊列中,而且在每個flush操作的時候並沒有關閉這些buffer。這給我們帶來下面幾個好處:

  • 更少的同步開銷(輸出flusher和RecordWriter都是獨立的)
  • 在高負載的場景下,Netty達到了瓶頸,我們可以依舊在部分緩衝區中處理數據。
  • Netty通知顯著減少

然而,可能會在低負載的情況下發現CPU利用率和TCP發包率有所增加。這是因爲當有數據發生,Flink使用任何可用的CPU指令週期來降低延時。一旦負載增加,buffer填滿數據的情況增加,Flink會自我調整。高負載場景不會被影響,甚至是因爲減少了同步消耗,有更好吞吐率。

Buffer Builder & Buffer Consumer

如果想更深入的瞭解生產者消費者的原理,可以閱讀BufferBuilder和BufferConsumer兩個類。

延時 vs. 吞吐量

下圖是在延時0-100ms-10000ms情況下,Flink兩個版本的吞吐量比較。Flink1.5在100ms的情況下,能夠達到最高吞吐量的75%。
在這裏插入圖片描述

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