如何分析及處理 Flink 反壓?

如何分析及處理 Flink 反壓?
反壓(backpressure)是實時計算應用開發中,特別是流式計算中,十分常見的問題。反壓意味着數據管道中某個節點成爲瓶頸,處理速率跟不上上游發送數據的速率,而需要對上游進行限速。由於實時計算應用通常使用消息隊列來進行生產端和消費端的解耦,消費端數據源是 pull-based 的,所以反壓通常是從某個節點傳導至數據源並降低數據源(比如 Kafka consumer)的攝入速率。

關於 Flink 的反壓機制,網上已經有不少博客介紹,中文博客推薦這兩篇1。簡單來說,Flink 拓撲中每個節點(Task)間的數據都以阻塞隊列的方式傳輸,下游來不及消費導致隊列被佔滿後,上游的生產也會被阻塞,最終導致數據源的攝入被阻塞。而本文將着重結合官方的博客[4]分享筆者在實踐中分析和處理 Flink 反壓的經驗。

反壓的影響


反壓並不會直接影響作業的可用性,它表明作業處於亞健康的狀態,有潛在的性能瓶頸並可能導致更大的數據處理延遲。通常來說,對於一些對延遲要求不太高或者數據量比較小的應用來說,反壓的影響可能並不明顯,然而對於規模比較大的 Flink 作業來說反壓可能會導致嚴重的問題。

這是因爲 Flink 的 checkpoint 機制,反壓還會影響到兩項指標: checkpoint 時長和 state 大小。

  • 前者是因爲 checkpoint barrier 是不會越過普通數據的,數據處理被阻塞也會導致 checkpoint barrier 流經整個數據管道的時長變長,因而 checkpoint 總體時間(End to End Duration)變長。
  • 後者是因爲爲保證 EOS(Exactly-Once-Semantics,準確一次),對於有兩個以上輸入管道的 Operator,checkpoint barrier 需要對齊(Alignment),接受到較快的輸入管道的 barrier 後,它後面數據會被緩存起來但不處理,直到較慢的輸入管道的 barrier 也到達,這些被緩存的數據會被放到state 裏面,導致 checkpoint 變大。

這兩個影響對於生產環境的作業來說是十分危險的,因爲 checkpoint 是保證數據一致性的關鍵,checkpoint 時間變長有可能導致 checkpoint 超時失敗,而 state 大小同樣可能拖慢 checkpoint 甚至導致 OOM (使用 Heap-based StateBackend)或者物理內存使用超出容器資源(使用 RocksDBStateBackend)的穩定性問題。

因此,我們在生產中要儘量避免出現反壓的情況(順帶一提,爲了緩解反壓給 checkpoint 造成的壓力,社區提出了 FLIP-76: Unaligned Checkpoints[4] 來解耦反壓和 checkpoint)。

定位反壓節點


要解決反壓首先要做的是定位到造成反壓的節點,這主要有兩種辦法:

  1. 通過 Flink Web UI 自帶的反壓監控面板;
  2. 通過 Flink Task Metrics。

前者比較容易上手,適合簡單分析,後者則提供了更加豐富的信息,適合用於監控系統。因爲反壓會向上遊傳導,這兩種方式都要求我們從 Source 節點到 Sink 的逐一排查,直到找到造成反壓的根源原因[4]。下面分別介紹這兩種辦法。

反壓監控面板

Flink Web UI 的反壓監控提供了 SubTask 級別的反壓監控,原理是通過週期性對 Task 線程的棧信息採樣,得到線程被阻塞在請求 Buffer(意味着被下游隊列阻塞)的頻率來判斷該節點是否處於反壓狀態。默認配置下,這個頻率在 0.1 以下則爲 OK,0.1 至 0.5 爲 LOW,而超過 0.5 則爲 HIGH。

如果處於反壓狀態,那麼有兩種可能性:

  1. 該節點的發送速率跟不上它的產生數據速率。這一般會發生在一條輸入多條輸出的 Operator(比如 flatmap)。
  2. 下游的節點接受速率較慢,通過反壓機制限制了該節點的發送速率。

如果是第一種狀況,那麼該節點則爲反壓的根源節點,它是從 Source Task 到 Sink Task 的第一個出現反壓的節點。如果是第二種情況,則需要繼續排查下游節點。

值得注意的是,反壓的根源節點並不一定會在反壓面板體現出高反壓,因爲反壓面板監控的是發送端,如果某個節點是性能瓶頸並不會導致它本身出現高反壓,而是導致它的上游出現高反壓。總體來看,如果我們找到第一個出現反壓的節點,那麼反壓根源要麼是就這個節點,要麼是它緊接着的下游節點。

那麼如果區分這兩種狀態呢?很遺憾只通過反壓面板是無法直接判斷的,我們還需要結合 Metrics 或者其他監控手段來定位。此外如果作業的節點數很多或者並行度很大,由於要採集所有 Task 的棧信息,反壓面板的壓力也會很大甚至不可用。

Task Metrics

Flink 提供的 Task Metrics 是更好的反壓監控手段,但也要求更加豐富的背景知識。

首先我們簡單回顧下 Flink 1.5 以後的網路棧,熟悉的讀者可以直接跳過。

TaskManager 傳輸數據時,不同的 TaskManager 上的兩個 Subtask 間通常根據 key 的數量有多個 Channel,這些 Channel 會複用同一個 TaskManager 級別的 TCP 鏈接,並且共享接收端 Subtask 級別的 Buffer Pool。

在接收端,每個 Channel 在初始階段會被分配固定數量的 Exclusive Buffer,這些 Buffer 會被用於存儲接受到的數據,交給 Operator 使用後再次被釋放。Channel 接收端空閒的 Buffer 數量稱爲 Credit,Credit 會被定時同步給發送端被後者用於決定發送多少個 Buffer 的數據。

在流量較大時,Channel 的 Exclusive Buffer 可能會被寫滿,此時 Flink 會向 Buffer Pool 申請剩餘的 Floating Buffer。這些 Floating Buffer 屬於備用 Buffer,哪個 Channel 需要就去哪裏。而在 Channel 發送端,一個 Subtask 所有的 Channel 會共享同一個 Buffer Pool,這邊就沒有區分 Exclusive Buffer 和 Floating Buffer。

我們在監控反壓時會用到的 Metrics 主要和 Channel 接受端的 Buffer 使用率有關,最爲有用的是以下幾個 Metrics:

Metris

描述

outPoolUsage

發送端 Buffer 的使用率

inPoolUsage

接收端 Buffer 的使用率

floatingBuffersUsage(1.9 以上)

接收端 Floating Buffer 的使用率

exclusiveBuffersUsage (1.9 以上)

接收端 Exclusive Buffer 的使用率

其中 inPoolUsage 等於 floatingBuffersUsage 與 exclusiveBuffersUsage 的總和。

分析反壓的大致思路是:如果一個 Subtask 的發送端 Buffer 佔用率很高,則表明它被下游反壓限速了;如果一個 Subtask 的接受端 Buffer 佔用很高,則表明它將反壓傳導至上游。反壓情況可以根據以下表格進行對號入座(圖片來自官網):

outPoolUsage 和 inPoolUsage 同爲低或同爲高分別表明當前 Subtask 正常或處於被下游反壓,這應該沒有太多疑問。而比較有趣的是當 outPoolUsage 和 inPoolUsage 表現不同時,這可能是出於反壓傳導的中間狀態或者表明該 Subtask 就是反壓的根源。

如果一個 Subtask 的 outPoolUsage 是高,通常是被下游 Task 所影響,所以可以排查它本身是反壓根源的可能性。如果一個 Subtask 的 outPoolUsage 是低,但其 inPoolUsage 是高,則表明它有可能是反壓的根源。因爲通常反壓會傳導至其上游,導致上游某些 Subtask 的 outPoolUsage 爲高,我們可以根據這點來進一步判斷。值得注意的是,反壓有時是短暫的且影響不大,比如來自某個 Channel 的短暫網絡延遲或者 TaskManager 的正常 GC,這種情況下我們可以不用處理。

對於 Flink 1.9 及以上版本,除了上述的表格,我們還可以根據 floatingBuffersUsage/exclusiveBuffersUsage 以及其上游 Task 的 outPoolUsage 來進行進一步的分析一個 Subtask 和其上游 Subtask 的數據傳輸。
通常來說,floatingBuffersUsage 爲高則表明反壓正在傳導至上游,而 exclusiveBuffersUsage 則表明了反壓是否存在傾斜(floatingBuffersUsage 高、exclusiveBuffersUsage 低爲有傾斜,因爲少數 channel 佔用了大部分的 Floating Buffer)。

至此,我們已經有比較豐富的手段定位反壓的根源是出現在哪個節點,但是具體的原因還沒有辦法找到。另外基於網絡的反壓 metrics 並不能定位到具體的 Operator,只能定位到 Task。特別是 embarrassingly parallel(易並行)的作業(所有的 Operator 會被放入一個 Task,因此只有一個節點),反壓 metrics 則派不上用場。

分析具體原因及處理


定位到反壓節點後,分析造成原因的辦法和我們分析一個普通程序的性能瓶頸的辦法是十分類似的,可能還要更簡單一點,因爲我們要觀察的主要是 Task Thread。

在實踐中,很多情況下的反壓是由於數據傾斜造成的,這點我們可以通過 Web UI 各個 SubTask 的 Records Sent 和 Record Received 來確認,另外 Checkpoint detail 裏不同 SubTask 的 State size 也是一個分析數據傾斜的有用指標。

此外,最常見的問題可能是用戶代碼的執行效率問題(頻繁被阻塞或者性能問題)。最有用的辦法就是對 TaskManager 進行 CPU profile,從中我們可以分析到 Task Thread 是否跑滿一個 CPU 核:如果是的話要分析 CPU 主要花費在哪些函數裏面,比如我們生產環境中就偶爾遇到卡在 Regex 的用戶函數(ReDoS);如果不是的話要看 Task Thread 阻塞在哪裏,可能是用戶函數本身有些同步的調用,可能是 checkpoint 或者 GC 等系統活動導致的暫時系統暫停。

當然,性能分析的結果也可能是正常的,只是作業申請的資源不足而導致了反壓,這就通常要求拓展並行度。值得一提的,在未來的版本 Flink 將會直接在 WebUI 提供 JVM 的 CPU 火焰圖[5],這將大大簡化性能瓶頸的分析。

另外 TaskManager 的內存以及 GC 問題也可能會導致反壓,包括 TaskManager JVM 各區內存不合理導致的頻繁 Full GC 甚至失聯。推薦可以通過給 TaskManager 啓用 G1 垃圾回收器來優化 GC,並加上 -XX:+PrintGCDetails 來打印 GC 日誌的方式來觀察 GC 的問題。

總結
--

反壓是 Flink 應用運維中常見的問題,它不僅意味着性能瓶頸還可能導致作業的不穩定性。定位反壓可以從 Web UI 的反壓監控面板和 Task Metric 兩者入手,前者方便簡單分析,後者適合深入挖掘。定位到反壓節點後我們可以通過數據分佈、CPU Profile 和 GC 指標日誌等手段來進一步分析反壓背後的具體原因並進行針對性的優化。

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