Flink 新一代流計算和容錯——階段總結和展望

ApacheFlink發表於2022-03-03

摘要:本文整理自 Apache Flink 引擎架構師、阿里巴巴儲存引擎團隊負責人梅源在 Flink Forward Asia 2021 核心技術專場的演講。本次演講內容圍繞 Flink 的高可用性探討 Flink 新一代流計算的核心問題和技術選型,包括:

  1. Flink 高可用流計算的關鍵路徑
  2. 容錯 (Fault Tolerance) 2.0 及關鍵問題
  3. 資料恢復過程
  4. 穩定快速高效的 Checkpointing
  5. 雲原生下容錯和彈性擴縮容

FFA 2021 直播回放 & 演講 PDF 下載

一、高可用流計算的關鍵路徑

img

上圖的雙向軸線是大資料應用隨時間延遲的圖譜,越往右邊時間延遲要求越短,越往左延遲要求沒那麼高。Flink 誕生之初大概是在上圖中間,可以理解為往右對應的是流式計算,而往左邊對應的是批式計算。過去一兩年,Flink 的應用圖譜向左邊有了很大的擴充套件,也就是我們常說的流批一體;與此同時我們也從來沒有停止過把圖譜向更實時的方向推進。

Flink 是以流式計算起家,那麼向更實時的方向推進到底是指什麼?什麼是更實時更極致的流式計算?

在正常處理的情況下,Flink 引擎框架本身除了定期去做 Checkpoint 的快照,幾乎沒有其他額外的開銷,而且 Checkpoint 快照很大一部分是非同步的,所以正常處理下 Flink 是非常高效的,端到端的延遲在 100 毫秒左右。正因為要支援高效的處理,Flink 在做容錯恢復和 Rescale 的時候代價都會比較大:需要把整個作業停掉,然後從過去的快照檢查點整體恢復,這個過程大概需要幾秒鐘,在作業狀態比較大的情況下會達到分鐘級。如果需要預熱或啟動其他服務程式,時間就更長了。

所以,Flink 極致流計算的關鍵點在容錯恢復部分。這裡說的極致的流計算是指對延遲性、穩定性和一致性都有一定要求的場景,比如風控安全。這也是 Fault Tolerance 2.0 要解決的問題。

二、容錯 (Fault Tolerance) 2.0 及關鍵問題

img

容錯恢復是一個全鏈路的問題,包括 failure detect、job cancel、新的資源申請排程、狀態恢復和重建等。同時,如果想從已有的狀態恢復,就必須在正常處理過程中做 Checkpoint,並且將它做得足夠輕量化才不會影響正常處理。

容錯也是多維度的問題,不同的使用者、不同的場景對容錯都有不同需求,主要包括以下幾個方面:

  • 資料一致性 (Data Consistency),有些應用比如線上機器學習是可以容忍部分資料丟失;
  • 延遲 (Latency),某些場景對端到端的延遲要求沒那麼高,所以可以將正常處理和容錯恢復的時候要做的工作綜合平均一下;
  • 恢復時的行為表現 (Recovery Behavior),比如大屏或者報表實時更新的場景下,可能並不需要迅速全量恢復,更重要的在於迅速恢復第一條資料;
  • 代價 (Cost),使用者根據自己的需求,願意為容錯付出的代價也不一樣。綜上,我們需要從不同的角度去考慮這個問題。

另外,容錯也不僅僅是 Flink 引擎側的問題。Flink 和雲原生的結合是 Flink 未來的重要方向,我們對於雲原生的依賴方式也決定了容錯的設計和走向。我們期望通過非常簡單的弱依賴來利用雲原生帶來的便利,比如 across region durability,最終能夠將有狀態的 Flink 的應用像原生的無狀態應用一樣彈性部署。

基於以上考慮,我們在 Flink 容錯 2.0 工作也有不同的側重點和方向。

第一,從排程的角度來考慮,每次錯誤恢復的時候,不會把和全域性快照相對應的所有 task 節點都回滾,而是隻恢復失敗的單個或者部分節點,這個對需要預熱或單個節點初始化時間很長的場景是很有必要的,比如線上機器學習場景。與此相關的一些工作比如 Approximate Task-local Recovery 已在 VVP 上線;Exactly-once Task-local Recovery,我們也已經取得了一些成果。

接下來重點聊一下 Checkpoint 以及和雲原生相關的部分。

三、Flink 中的資料恢復過程

那麼,容錯到底解決了什麼?在我看來其本質是解決資料恢復的問題。

img

Flink 的資料可以粗略分為以下三類,第一種是元資訊,相當於一個 Flink 作業執行起來所需要的最小資訊集合,包括比如 Checkpoint 地址、Job Manager、Dispatcher、Resource Manager 等等,這些資訊的容錯是由 Kubernetes/Zookeeper 等系統的高可用性來保障的,不在我們討論的容錯範圍內。Flink 作業執行起來以後,會從資料來源讀取資料寫到 Sink 裡,中間流過的資料稱為處理的中間資料 Inflight Data (第二類)。對於有狀態的運算元比如聚合運算元,處理完輸入資料會產生運算元狀態資料 (第三類)。

Flink 會週期性地對所有運算元的狀態資料做快照,上傳到持久穩定的海量儲存中 (Durable Bulk Store),這個過程就是做 Checkpoint。Flink 作業發生錯誤時,會回滾到過去的一個快照檢查點 Checkpoint 恢復。

我們當前有非常多的工作是針對提升 Checkpointing 效率來做的,因為在實際工作中,引擎層大部分 Oncall 或工單問題基本上都與 Checkpoint 相關,各種原因會造成 Checkpointing 超時。

下面簡單回顧一下 Checkpointing 的流程,對這部分內容比較熟悉的同學可以直接跳過。Checkpointing 的流程分為以下幾步:

img

第一步:Checkpoint Coordinate 從 Source 端插入 Checkpoint Barrier (上圖黃色的豎條)。

img

第二步:Barrier 會隨著中間資料處理向下遊流動,流過運算元的時候,系統會給運算元的當前狀態做一個同步快照,並將這個快照資料非同步上傳到遠端儲存。這樣一來,Barrier 之前所有的輸入資料對運算元的影響都已反映在運算元的狀態中了。如果運算元狀態很大,會影響完成 Checkpointing 的時間。

img

第三步:當一個運算元有多個輸入的時候,需要運算元拿到所有輸入的 Barrier 之後才能開始做快照,也就是上圖藍色框的部分。可以看到,如果在對齊過程中有反壓,造成中間處理資料流動緩慢,沒有反壓的那些線路也會被堵住,Checkpoint 會做得很慢,甚至做不出來。

img

第四步:所有運算元的中間狀態資料都成功上傳到遠端穩定儲存之後, 一個完整的 Checkpoint 才算真正完成。

從這 4 個步驟中可以看到,影響快速穩定地做 Checkpoint 的因素主要有 2 個,一個是處理的中間資料流動緩慢,另一個是運算元狀態資料過大,造成上傳緩慢,下面來講一講如何來解決這兩個因素。

四、穩定快速高效的 Checkpointing

img

針對中間資料流動緩慢,可以:

  1. 想辦法不被中間資料堵塞:Unaligned Checkpoint——直接跳過阻塞的中間資料;
  2. 或者讓中間的資料變得足夠少: Buffer Debloating。
  3. 針對狀態資料過大,我們需要將每次做 Checkpoint 時上傳的資料狀態變得足夠小:Generalized Log-Based Incremental Checkpoint。

下面來具體展開闡述每一種解決方法。

4.1 Unaligned Checkpoint

img

Unaligned Checkpoint 的原理是將從 Source 插入的 Barrier 跳過中間資料瞬時推到 Sink,跳過的資料一起放在快照裡。所以對於 Unaligned Checkpoint 來說,它的狀態資料不僅包括運算元的狀態資料,還包括處理的中間資料,可以理解成給整個 Flink Pipeline 做了一個完整的瞬時快照,如上圖黃色框所示。雖然 Unaligned Checkpoint 可以非常快速地做 Checkpoint,但它需要儲存額外的 Pipeline Channel 的中間資料,所以需要儲存的狀態會更大。Unaligned Checkpoint 在去年 Flink-1.11 版本就已經發布,Flink-1.12 和 1.13 版本支援 Unaligned Checkpoint 的 Rescaling 和動態由 Aligned Checkpoint 到 Unaligned Checkpoint 的切換。

4.2 Buffer Debloating

Buffer Debloating 的原理是在不影響吞吐和延遲的前提下,縮減上下游快取的資料。經過觀察,我們發現運算元並不需要很大的 input/output buffer。快取太多資料除了讓作業在資料流動緩慢時把整個 pipeline 填滿,讓作業記憶體超用 OOM 以外,沒有太大的幫助。

img

這裡可以做個簡單的估算,對於每個 task,無論是輸出還是輸入,我們總的 buffer 數目大概是每個 channel 對應的 exclusive buffer 數乘以 channel 的個數再加上公用的 floating buffer 數。這個 buffer 總數再乘以每個 buffer 的 size,得到的結果就是總的 local buffer pool 的 size。然後我們可以把系統預設值代進去算一下,就會發現併發稍微大一點再多幾次資料 shuffle,整個作業中間的流動資料很容易就會達到幾個 Gigabytes。

實際中我們並不需要快取這麼多資料,只需要足夠量的資料保證運算元不空轉即可,這正是 Buffer Debloating 做的事情。Buffer Debloating 能夠動態調整上下游總 buffer 的大小,在不影響效能的情況下最小化作業所需的 buffer size。目前的策略是上游會動態快取下游大概一秒鐘能夠處理的資料。此外,Buffer Debloating 對 Unaligned Checkpoint 也是有好處的。因為 Buffer Debloating 減少了中間流動的資料,所以 Unaligned Checkpoint 在做快照的時候,需要額外儲存的中間資料也會變少。

img

上圖是對 Buffer Debloating 在反壓的情況下,Checkpointing 時間隨 Debloat Target 變化的時間對比圖。Debloat Target 是指上游快取 “預期時間” 內下游能處理的資料。這個實驗中,Flink 作業共有 5 個 Network Exchange,所以總共 Checkpointing 所需的時間大約等於 5 倍的 Debloat Target,這與實驗結果也基本一致。

4.3 Generalized Log-Based Incremental Checkpoint

前面提到狀態大小也會影響完成 Checkpointing 的時間,這是因為 Flink 的 Checkpointing 過程由兩個部分組成:同步的快照和非同步上傳。同步的過程通常很快,把記憶體中的狀態資料刷到磁碟上就可以了。但是非同步上傳狀態資料的部分和上傳的資料量有關,因此我們引入了 Generalized Log-Based Incremental Checkpoint 來控制每次快照時需要上傳的資料量。

img

對於有狀態的運算元,它的內部狀態發生改變後,這個更新會記錄在 State Table 裡,如上圖所示。當 Checkpointing 發生的時候,以 RocksDB 為例,這個 State Table 會被刷到磁碟上,磁碟檔案再非同步上傳到遠端儲存。根據 Checkpoint 的模式,上傳的部分可以是完整的 Checkpoint 或 Checkpoint 增量部分。但無論是哪種模式,它上傳檔案的大小都是與 State Backend 儲存實現強繫結的。例如 RocksDB 雖然也支援增量 Checkpoint,但是一旦觸發多層 Compaction,就會生成很多新的檔案,而這種情況下增量的部分甚至會比一個完整的 Checkpoint 更大,所以上傳時間依然不可控。

img

既然是上傳過程導致 Checkpointing 超時,那麼把上傳過程從 Checkpointing 過程中剝離開來就能解決問題。這其實就是 Generalized Log-Based Incremental Checkpoint 想要做的事情:本質上就是將 Checkpointing 過程和 State Backend 儲存 Compaction 完全剝離開。

具體實現方法如下:對於一個有狀態的運算元,我們除了將狀態更新記錄在 State Table 裡面,還會再寫一份增量到 State Changelog,並將它們都非同步的刷到遠端儲存上。這樣,Checkpoint 變成由兩個部分組成,第一個部分是當前已經物化存在遠端儲存上的 State Table,第二個部分是還沒有物化的增量部分。因此真正做 Checkpoint 的時候,需要上傳的資料量就會變得少且穩定,不僅可以把 Checkpoint 做得更穩定,還可以做得更高頻。可以極大縮短端到端的延遲。特別對於 Exactly Once Sink,因為需要完成完整的 Checkpoint 以後才能完成二階段提交。

五、雲原生下容錯和彈性擴縮容

在雲原生的大背景下,快速擴縮容是 Flink 的一大挑戰,特別是 Flink-1.13 版本引入了 Re-active Scaling 模式後,Flink 作業需要頻繁做 Scaling-In/Out,因此 Rescaling 已成為 Re-active 的主要瓶頸。Rescaling 和容錯 (Failover) 要解決的問題在很大程度上是類似的:例如拿掉一臺機器後,系統需要快速感知到,需要重新排程並且重新恢復狀態等。當然也有不同點,Failover 的時候只需要恢復狀態,將狀態拉回到運算元上即可;但 Rescaling 的時候,因為拓撲會導致並行度發生變化,需要重新分配狀態。

img

狀態恢復的時候,我們首先需要將狀態資料從遠端儲存讀取到本地,然後根據讀取的資料重新分配狀態。如上圖所示,整個這個過程在狀態稍大的情況下,單個併發都會超過 30 分鐘。並且在實際中,我們發現狀態重新分配所需要的時間遠遠大於從遠端儲存讀取狀態資料的時間。

img

那麼狀態是如何重新分配的呢?Flink 的狀態用 Key Group 作為最小單位來切分,可以理解成把狀態的 Key Space 對映到一個從 0 開始的正整數集,這個正整數集就是 Key Group Range。這個 Key Group Range 和運算元的所允許的最大併發度相關。如上圖所示,當我們把運算元併發度從 3 變成 4 的時候,重新分配的 Task1 的狀態是分別由原先的兩個 Task 狀態的一部分拼接而成的,並且這個拼接狀態是連續且沒有交集的,所以我們可以利用這一特性做一些優化。

img

上圖可以看到優化後,DB Rebuild 這部分優化效果還是非常明顯的,但目前這部分工作還處於探索性階段,有很多問題尚未解決,所以暫時還沒有明確的社群計劃。

最後簡單回顧一下本文的內容。我們首先討論了為什麼要做容錯,因為容錯是 Flink 流計算的關鍵路徑;然後分析了影響容錯的因素,容錯是一個全鏈路的問題,包括 Failure Detection、Job Canceling、新的資源申請排程、狀態恢復和重建等,需要從多個維度去權衡思考這個問題;當前我們的重點主要是放在如何穩定快速做 Checkpoint 的部分,因為現在很多實際的問題都和做 Checkpoint 相關;最後我們討論瞭如何將容錯放在雲原生的大背景下與彈性擴縮容相結合的一些探索性工作。


FFA 2021 直播回放 & 演講 PDF 下載

更多 Flink 相關技術問題,可掃碼加入社群釘釘交流群
第一時間獲取最新技術文章和社群動態,請關注公眾號~

image.png

相關文章