深刻了解 Flink 網絡棧(二):監控、指標和處理背壓

做者 | Nico Krube
譯者 | 王強html

在以前的文章中,咱們從高級抽象到底層細節各個層面全面介紹了 Flink 網絡棧的工做機制。做爲這一系列的第二篇文章,本文將在第一篇的基礎上更進一步,主要探討如何監視與網絡相關的指標,從而識別背壓等因素帶來的影響,或找出吞吐量和延遲的瓶頸所在。本文將簡要介紹處理背壓的手段,而以後的文章將進一步研究網絡棧微調的話題。若是你不是很熟悉網絡棧的知識,強烈建議先閱讀本系列的第一篇文章 《原理解析 | 深刻了解 Apache Flink 的網絡協議棧》。java

監控

網絡監控工做中最重要的環節可能就是監控背壓了,所謂背壓是指系統接收數據的速率高於其處理速度 [1]。這種現象將給發送者帶來壓力,而致使它的緣由可能有兩種狀況:apache

  • 接收器很慢。

這多是由於接收器自己就遇到了背壓,因此沒法以與發送方相同的速率繼續處理數據;也有多是接收器由於垃圾回收工做、缺乏系統資源或 I/O 瓶頸而暫時卡住了。緩存

  • 網絡通道很慢。

這種狀況可能和接收器沒有(直接)關係,咱們說這時是發送器遇到了背壓,由於在同一臺機器上運行的全部子任務共享的網絡帶寬可能供不該求了。請注意,除了 Flink 的網絡棧以外可能還有其餘網絡用戶,例如源(source)和匯(sink)、分佈式文件系統(檢查點、網絡附加存儲)、日誌記錄和指標監測等。咱們以前的一篇關於容量規劃的文章(https://www.ververica.com/blog/how-to-size-your-apache-flink-cluster-general-guidelines)介紹了更多相關內容。網絡

[1] 若是你不熟悉背壓,不瞭解它與 Flink 的交互方式,建議閱讀咱們在 2015 年發表的關於背壓的文章(https://www.ververica.com/blog/how-flink-handles-backpressure)。併發

當背壓出現時,它將一路向上遊傳導並最終到達你的源,還會減慢它們的速度。這自己並非一件壞事,只是代表你缺少足夠的資源處理當前的負載。但你可能想要作一些改進,在不動用更多資源的前提下處理更高的負載。爲此你須要找到(1)瓶頸在哪裏(位於哪一個任務 / 操做符)和(2)產生瓶頸的緣由。Flink 提供了兩種識別瓶頸的機制:oracle

  • 直接經過 Flink 的 Web UI 及其背壓監視器識別
  • 間接經過一些網絡指標識別。

Flink 的 Web UI 大概是快速排除故障時的首選,但它存在一些缺點,咱們將在下面解釋。另外一方面,Flink 的網絡指標更適合持續監控和推斷是哪些瓶頸致使了背壓,並分析這些瓶頸的本質屬性。咱們將在下文中具體介紹這兩個部分。在這兩種狀況下,你都須要從全部的源和匯中找出背壓的根源。調查工做的起點通常來講是最後一個承受背壓的操做符;並且最後這個操做符極可能就是背壓產生的源頭。dom

背壓監視器

背壓監視器只暴露在 Flink 的 WebUI[2] 中。因爲它是僅在請求時纔會觸發的活動組件,所以目前沒法經過監控指標來提供給用戶。背壓監視器經過 Thread.getStackTrace() 對 TaskManager 上運行的全部任務線程採樣,並計算緩存請求中阻塞任務的樣本數。這些任務之因此會阻塞,要麼是由於它們沒法按照網絡緩衝區生成的速率發送這些緩存,要麼就是下游任務處理它們的速度很慢,沒法保證發送的速率。背壓監視器將顯示阻塞請求與總請求的比率。因爲某些背壓被認爲是正常 / 臨時的,因此監視器將顯示如下狀態:分佈式

  • OK,比率 ≤ 0.10
  • LOW,0.10 < 比率 ≤ 0.5
  • HIGH,0.5 < 比率 ≤ 1

雖然說你也能夠調整刷新間隔、樣本數或樣本之間的延遲等參數,但一般狀況下這些參數用不着你來調整,由於默認值提供的結果已經夠好了。ide

1

[2] 你還能夠經過 REST API 訪問背壓監視器:/jobs/:jobid/vertices/:vertexid/backpressure

背壓監視器能夠幫助你找到背壓源自何處(位於哪一個任務 / 操做符)。但你無法用它進一步推斷背壓產生的緣由。此外,對於較大的做業或較高的並行度來講,背壓監視器顯示的信息就太亂了,很難分析,還可能要花些時間才能完整收集來自 TaskManager 的數據。另請注意,採樣工做可能還會影響你當前做業的性能。

網絡指標

網絡指標和任務 I/O 指標比背壓監視器更輕量一些,並且會針對當前運行的每一個做業不斷更新。咱們能夠利用這些指標得到更多信息,收集到的信息除了用來監測背壓外還有其餘用途。和用戶關係最大的指標有:

  • Flink 1.8 及更早版本:outPoolUsage、inPoolUsage。它們是對各個本地緩衝池中已用緩存與可用緩存的比率估計。在使用基於信用的流控制解析 Flink 1.5-1.8 中的 inPoolUsage 時,請注意它只與浮動緩存有關(獨佔緩存不算在緩衝池裏)。
  • Flink 1.9 及更新版本:outPoolUsage、inPoolUsage、floatingBuffersUsage、exclusiveBuffersUsage
    它們是對各個本地緩衝池中已用緩存與可用緩存的比率估計。從 Flink 1.9 開始,inPoolUsage 是 floatingBuffersUsage 和 exclusiveBuffersUsage 的總和。
  • numRecordsOut、numRecordsIn。這兩個指標都帶有兩個做用域:一個是運算符,另外一個是子任務。網絡監視使用的是子任務做用域指標,並顯示它已發送 / 接收的記錄總數。你可能須要進一步研究這些數字來找出特定時間跨度內的記錄數量,或使用等效的 PerSecond 指標。
  • numBytesOut、numBytesInLocal、numBytesInRemote。表示這個子任務從本地 / 遠程源發出或讀取的字節總數。也能夠經過 PerSecond 指標獲取。
  • numBuffersOut、numBuffersInLocal、numBuffersInRemote。與 numBytes 相似,但這裏計算的是網絡緩衝區的數量。

警告:爲了完整起見,咱們將簡要介紹 outputQueueLength 和 inputQueueLength 這兩個指標。它們有點像 [out、in] PoolUsage 指標,但這兩個指標分別顯示的是發送方子任務的輸出隊列和接收方子任務的輸入隊列中的緩存數量。但想要推斷緩存的準確數量是很難的,並且本地通道也有一個很微妙的特殊問題:因爲本地輸入通道沒有本身的隊列(它直接使用輸出隊列),所以通道的這個值始終爲 0(參見 FLINK-12576,https://issues.apache.org/jira/browse/FLINK-12576);在只有本地輸入通道的狀況下 inputQueueLength = 0。

總的來講,咱們不鼓勵使用 outputQueueLength 和 inputQueueLength,由於它們的解析很大程度上取決於運算符當前的並行度以及獨佔緩存和浮動緩存的配置數量。相比之下,咱們建議使用各類 *PoolUsage 指標,它們會爲用戶提供更詳盡的信息。

注意:若是你要推斷緩存的使用率,請記住如下幾點:

任何至少使用過一次的傳出通道老是佔用一個緩存(Flink 1.5 及更高版本)。

Flink 1.8 及較早版本:這個緩存(即便是空的!)老是在 backlog 中計 1,所以接收器試圖爲它保留一個浮動緩存區。

Flink 1.9 及以上版本:只有當一個緩存已準備好消費時纔在 backlog 中計數,好比說它已滿或已刷新時(請參閱 FLINK-11082)。

接收器僅在反序列化其中的最後一條記錄後才釋放接收的緩存。

後文會綜合運用這些指標,以瞭解背壓和資源的使用率 / 效率與吞吐量的關係。後面還會有一個獨立的部分具體介紹與延遲相關的指標。

背壓

有兩組指標能夠用來監測背壓:它們分別是(本地)緩衝池使用率和輸入 / 輸出隊列長度。這兩種指標的粒度粗細各異,惋惜都不夠全面,怎樣解讀這些指標也有不少說法。因爲隊列長度指標解讀起來有一些先天困難,咱們將重點關注輸入和輸出池的使用率指標,該指標也提供了更多細節信息。

  • 若是一項子任務的 outPoolUsage 爲 100%,則它正在經受背壓。子任務是已經阻塞了,仍是仍在將記錄寫入網絡緩衝區,取決於 RecordWriter 當前正在寫入的緩存有沒有寫滿。這與背壓監視器顯示的結果是不同的!
  • 當 inPoolUsage 爲 100%時表示全部浮動緩存都分配給了通道,背壓最終將傳遞到上游。這些浮動緩存處於如下任一狀態中:因爲一個獨佔緩存正被佔用(遠程輸入通道一直在嘗試維護 #exclusive buffer 的信用),這些浮動緩存被保留下來供未來在通道上使用;它們爲一個發送器的 backlog 保留下來等待數據;它們可能包含數據並在輸入通道中排隊;或者它們可能包含數據並正由接收器的子任務讀取(一次一個記錄)。
  • Flink 1.8 及更早的版本:根據 FLINK-11082(https://issues.apache.org/jira/browse/FLINK-11082),即便在正常狀況下 100% 的 inPoolUsage 也很常見。
  • Flink 1.9 及以上版本:若是 inPoolUsage 持續在 100%左右,這就是出現上游背壓的強烈信號。

下表總結了全部組合及其解釋。但請記住,背壓多是次要的的或臨時的(也就是無需查看),或者只出如今特定通道上,或是由特定 TaskManager 上的其餘 JVM 進程(例如 GC、同步、I/O、資源短缺等)引發的,源頭不是某個子任務。

outPoolUsage low outPoolUsage high
inPoolUsage low 正常 注意(產生背壓,當前狀態:上游暫未出現背壓或已經解除背壓)
inPoolUsage high (Flink 1.9+) 若是全部上游任務的 outPoolUsage 都很低,則只須要注意(可能最終會產生背壓); 若是任何上游任務的 outPoolUsage 變高,則問題(可能在上游致使背壓,還多是背壓的源頭) 問題(下游任務或網絡出現背壓,可能會向上遊傳遞)

咱們甚至能夠經過查看兩個連續任務的子任務的網絡指標來深刻了解背壓產生的緣由:

  • 若是接收器任務的全部子任務的 inPoolUsage 值都很低,而且有任一上游子任務的 outPoolUsage 較高,則多是網絡瓶頸致使了背壓。因爲網絡是 TaskManager 的全部子任務共享的資源,所以瓶頸可能不是直接源自這個子任務,而是來自於各類併發操做,例如檢查點、其餘流、外部鏈接或同一臺計算機上的其餘 TaskManager/ 進程。
  • 背壓也能夠由一個任務的全部並行實例或單個任務實例引發。

第一種狀況一般是由於任務正在執行一些應用到全部輸入分區的耗時操做;後者一般是某種誤差的結果,多是數據偏斜或資源可用性 / 分配誤差。後文的「如何處理背壓」一節中會介紹這兩種狀況下的應對措施。

Flink 1.9 及以上版本

  • 若是 floatingBuffersUsage 沒到 100%,那麼就不太可能存在背壓。若是它達到了 100% 且全部上游任務都在承受背壓,說明這個輸入正在單個、部分或所有輸入通道上承受背壓。你可使用 exclusiveBuffersUsage 來區分這三種狀況:
    假設 floatingBuffersUsage 接近 100%,則 exclusiveBuffersUsage 越高,輸入通道承受的背壓越大。在 exclusiveBuffersUsage 接近 100%的極端狀況下,全部通道都在承受背壓。
    • 下表總結了 exclusiveBuffersUsage、floatingBuffersUsage 和上游任務的 outPoolUsage 之間的關係,還比上表多了一個 inPoolUsage = floatingBuffersUsage + exclusiveBuffersUsage:
exclusiveBuffersUsage low exclusiveBuffersUsage high
floatingBuffersUsage low + 全部上游 outPoolUsage low 正常 [3]
floatingBuffersUsage low + 任一上游 outPoolUsage high 問題(多是網絡瓶頸) [3]
floatingBuffersUsage high + 全部上游 outPoolUsage low 注意(最終只有一些輸入通道出現背壓) 注意(最終多數或所有輸入通道出現背壓)
floatingBuffersUsage high + 任一上游 outPoolUsage high 問題(只有一些輸入通道在承受背壓) 問題(多數或所有輸入通道都在承受背壓)

[3] 不該該出現這種狀況

資源使用率 / 吞吐量

除了上面提到的各個指標的單獨用法外,還有一些組合用法能夠用來探究網絡棧的深層情況:

  • 吞吐量較低時 outPoolUsage 值卻常常接近 100%,但同時全部接收器的 inPoolUsage 都很低,這代表咱們的信用通知的往返時間(取決於你的網絡延遲)過久,致使默認的獨佔緩存數量沒法充分利用你的帶寬。能夠考慮增長每通道緩存參數或嘗試禁用基於信用的流量控制。
  • numRecordsOut 和 numBytesOut 這個組合能夠用來肯定序列化記錄的平均大小,進而幫助你針對峯值場景作容量規劃。
  • 若是要了解緩存填充率和輸出刷新器的影響,能夠考察 numBytesInRemote 與 numBuffersInRemote 的組合。在調整吞吐量(而不是延遲!)時,較低的緩存填充率可能意味着網絡效率較低。在這種狀況下請考慮增長緩存超時時間。請注意,在 Flink 1.8 和 1.9 中,numBuffersOut 僅在緩存快填滿或某事件停用某緩存(例如一個檢查點屏障)時纔會增長,這個動做還可能滯後。還請注意,因爲緩存是針對遠程信道的優化技術,對本地信道影響有限,所以不須要在本地信道上考察緩存填充率。
  • 你還可使用 numBytesInLocal 和 numBytesInRemote 的組合區分本地與遠程流量,但在大多數狀況下沒這個必要。

如何處理背壓?

假設你肯定了背壓的來源,也就是瓶頸所在,下一步就是分析爲何會發生這種狀況。下面咱們按照從基本到複雜的順序列出了致使背壓的一些潛在成因。咱們建議首先檢查基本成因,而後再深刻研究更復雜的成因,不然就可能得出一些錯誤的結論。
另外回想一下,背壓多是暫時的,多是因爲負載高峯、檢查點或做業重啓時數據 backlog 待處理致使的結果。若是背壓是暫時的,那麼忽略它就好了。此外還要記住,分析和解決問題的過程可能會受到瓶頸自己的影響。話雖如此,這裏仍是有幾件事須要檢查一下。

系統資源

首先,你應該檢查受控機器的基本資源使用狀況,如 CPU、網絡或磁盤 I/O 等指標。若是某些資源在被所有或大量佔用,你能夠執行如下操做:

  1. 嘗試優化你的代碼。此時代碼分析器是頗有用的。
  2. 調整這項資源的 Flink。
  3. 經過增長並行度和 / 或增長羣集中的計算機數量來擴展資源。

垃圾收集

通常來講,長時間的垃圾回收工做會引起性能問題。你能夠打印 GC 調試日誌(經過 -XX: +PrintGCDetails)或使用某些內存 /GC 分析器來驗證你是否處於這種情況下。因爲 GC 問題的處理與應用程序高度相關,而且獨立於 Flink,所以咱們不會在此詳細介紹(可參考 Oracle 的垃圾收集調整指南,https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html 或 Plumbr 的 Java 垃圾回收手冊,https://plumbr.io/java-garbage-collection-handbook)。

CPU/ 線程瓶頸

若是 CPU 瓶頸來自於一個或幾個線程,而整臺機器的 CPU 使用率仍然相對較低,則 CPU 瓶頸可能就很難被發現了。例如,48 覈計算機上的單個 CPU 線程瓶頸只會帶來 2%的 CPU 使用率。能夠考慮使用代碼分析器,由於它們能夠顯示每一個線程的 CPU 使用狀況,這樣就能識別出熱線程。

線程爭用

與上面的 CPU/ 線程瓶頸問題相似,共享資源上較高的線程爭用率可能會致使子任務瓶頸。仍是要請出 CPU 分析器,考慮查找用戶代碼中的同步開銷 / 鎖爭用——雖然咱們應該避免在用戶代碼中添加同步性,這可能很危險!還能夠考慮調查共享系統資源。例如,默認 JVM 的 SSL 實現能夠從共享的 /dev/urandom 資源周圍獲取數據。

加載不均衡

若是你的瓶頸是由數據誤差引發的,能夠嘗試將數據分區更改成幾個獨立的重鍵,或實現本地 / 預聚合來清除誤差或減輕其影響。

除此以外還有不少狀況。通常來講,爲了削弱瓶頸從而減小背壓,首先要分析它發生的位置,而後找出緣由。最好從檢查哪些資源處於充分利用狀態開始入手。

延遲追蹤

追蹤各個可能環節出現的延遲是一個獨立的話題。在本節中,咱們將重點關注 Flink 網絡棧中的記錄的等待時間——包括系統網絡鏈接的狀況。在吞吐量較低時,這些延遲會直接受輸出刷新器的緩存超時參數的影響,或間接受任何應用程序代碼延遲的影響。處理記錄的時間比預期的要長或者(多個)計時器同時觸發——並阻止接收器處理傳入的記錄——時,網絡棧內後續記錄的等待時間會大大延長。咱們強烈建議你將本身的指標添加到 Flink 做業中,以便更好地跟蹤做業組件中的延遲,並更全面地瞭解延遲產生的緣由。

Flink 爲追蹤經過系統(用戶代碼以外)的記錄延遲提供了一些支持。但默認狀況下此功能被禁用(緣由參見下文!),必須用 metrics.latency.interval 或 ExecutionConfig #setLatencyTrackingInterval() 在 Flink 的配置中設置延遲追蹤間隔才能啓用此功能。啓用後,Flink 將根據 metrics.latency.granularity 定義的粒度生成延遲直方圖:

  • single:每一個操做符子任務有一個直方圖
  • operator(默認值):源任務和操做符子任務的每一個組合有一個直方圖
  • subtask:源子任務和操做符子任務的每一個組合有一個直方圖(並行度翻了兩番!)

這些指標經過特殊的「延遲標記」收集:每一個源子任務將按期發出包含其建立時間戳的特殊記錄。而後,延遲標記與正常記錄一塊兒流動,不會在線路上或緩存隊列中超過正常記錄。可是,延遲標記不會進入應用程序邏輯,並會在那裏超過正常記錄。所以,延遲標記僅測量用戶代碼之間的等待時間,而不是完整的「端到端」延遲。但用戶代碼會間接影響這些等待時間!

因爲 LatencyMarker 就像普通記錄同樣位於網絡緩衝區中,它們也會因緩存已滿而等待,或因緩存超時而刷新。當信道處於高負載時,網絡緩衝區數據不會增長延遲。可是隻要一個信道處於低負載狀態,記錄和延遲標記就會承受最多 buffer_timeout/2 的平均延遲。這個延遲會加到每一個鏈接子任務的網絡鏈接上,在分析子任務的延遲指標時應該考慮這一點。

只要查看每一個子任務暴露的延遲追蹤指標,例如在第 95 百分位,你就應該能識別出是哪些子任務在顯著影響源到匯延遲,而後對其作針對性優化。

 

 

 

 

 

 

 

 

 

 

 

原文連接

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索