Apache Flink 在小米的穩定性最佳化和實踐

ITPUB社群發表於2022-11-22

本文整理自小米大資料部高階軟體工程師張蛟在 Flink Forward Asia 2021 生產實踐專場的演講。主要內容包括:

  1. 發展現狀和規模
  2. 穩定性最佳化及實踐
  3. 運維最佳化及實踐
  4. 未來規劃與展望


01

發展現狀及規模

Apache Flink 在小米的穩定性最佳化和實踐

現階段,我們的整體架構可以分成5層,資料從下往上流動,如上圖。


資料採集層主要負責收集各類資料,資料的來源分為兩類,一類是埋點和業務日誌以及服務日誌,經由 LCS Agent 進行採集,另一類是資料庫資料經由 Binlog 或 Checkpoint 資料整合等方式收集到訊息佇列中。以 Flink、Spark 為主的計算層對其進行處理,並最終儲存到各類儲存和查詢服務中,供業務使用。Flink 是計算層實時和準實時處理的主要框架,在其中正發揮著越來越重要的作用,尤其是 Flink+Iceberg 資料湖技術,正在讓流批一體成為現實。


Apache Flink 在小米的穩定性最佳化和實踐

目前我們的叢集上執行著 3000 多個作業,主力版本是 1.12,1.14 版本也已經合併上線,日均處理 10 萬億+ 條訊息,PB 級的資料量,峰值資料 2 億條/秒,執行在國內外 10 多個叢集,使用超過 45000 個 CPU core,記憶體使用超過 200tb。


Apache Flink 在小米的穩定性最佳化和實踐

在這樣規模的資料處理過程中,我們遇到了許多問題。


  • 作業記憶體佔用不可控,on Yarn 模式非常容易出現 Yarn container OOM kill,導致 container lost,引發作業頻繁重啟,包括框架內重啟。

  • on Yarn 模式無法支援作業自動平滑重啟,在機器過保、下線、機房遷移等過程中,只能觸發 failover。

  • 實時作業對負載較為敏感,啟動和執行的過程中需要保證機器效能,避免因離線和線上混部造成影響。

  • Checkpoint 作為 Flink 有狀態計算資料一致性的保障,存在穩定性問題。

  • historyserver 預設的清理策略不好設定,導致佔用的磁碟空間比較大,訪問慢。

  • 作業異常時難以確定異常原因和節點,需要檢視大量的作業日誌,導致故障排查困難。


02

穩定性最佳化及實踐

Apache Flink 在小米的穩定性最佳化和實踐

首先是 Yarn container lost 的最佳化。Flink JobManager 首先會向 Yarn reCheckpointmanager 申請資源,Yarn reCheckpointmanager 為該申請分配資源後將分配資訊返回給 JobManager,然後 JobManager 會根據分配資訊去啟動 taskmanager,並使之與 JobManager 進行心跳。


JobManager 包括 JobMaster 和 reCheckpointmanager,它會主動傳送心跳請求,探測 taskmanager 是否存活。如果 taskexecutor 因為某些原因意外被 kill,JobManager 的日誌中就會提示 container lost。


Apache Flink 在小米的穩定性最佳化和實踐

上圖是 container lost 現象的提示之一,一般老版本的 Flink 中出現比較多。


Apache Flink 在小米的穩定性最佳化和實踐

上圖是 container lost 現象的另一種提示。


Apache Flink 在小米的穩定性最佳化和實踐

在出現 container lost 時,如果去檢視 Yarn的nodemanager 或 JobManager 中異常前後的日誌,一般都可以看到類似 beyond the physical memory limit 的日誌,這表明它是因為實體記憶體使用超限被 Yarn kill。


Apache Flink 在小米的穩定性最佳化和實踐

這裡需要先介紹一下 Yarn 控制記憶體超用的方式,Yarn Nodemanager 會啟動一個 containersmonitor 的執行緒,這個執行緒會定期掃描 Nodemanager 上的 container 記憶體佔用,從而實現記憶體資源的隔離。


簡單來說,如果某個 container 對應程式樹中所有年齡大於 0 的程式,總記憶體使用量超過申請量的兩倍,或所有年齡大於 1 的程式,總記憶體使用量超過上限,就表明其記憶體超用,需要被 kill。

但實際上這種方式存在一定的問題:

  • 一是定期掃描對於記憶體突增的隔離性比較差,可能還沒有開始掃描就已經達到系統總記憶體上限,導致被系統 kill;

  • 二是 Yarn 通常會開啟節點資源的超賣,此時如果所有資源都被使用,會導致節點不穩定;

  • 三是如果作業只是臨時的記憶體需求,即使此時節點仍有富餘記憶體,也會觸發 kill。


Apache Flink 在小米的穩定性最佳化和實踐

針對這些問題,我們採用 Cgroup + JDK升級 + Jemalloc 的方式進行了最佳化。可能有人會問為什麼需要進行 JDK 升級?這是因為老版本的 JDK 使用 Jemalloc 存線上程死鎖的問題,另外升級最新的 JDK 也能避免其他的 JDK bug,通常這類 bug 都不容易被找到和復現。


Apache Flink 在小米的穩定性最佳化和實踐

Cgroup 的方式主要是開啟記憶體軟限制,它對 container 的記憶體限制不再是基於單個 container 的記憶體申請量,而是整個 Nodemanager 的記憶體量。這個時候如果 NodeManager 上仍有富餘記憶體,記憶體超用的 container 就可以接著使用這些富餘的記憶體。一個節點上同時存在多個 Container 記憶體超用導致整個節點記憶體達到上限,才會觸發 oom event。Oom listener 對該事件進行監聽並判斷,如果達到節點總記憶體就會選取記憶體實際佔用量超過申請量且啟動時間最短、優先順序最低的作業觸發 oom kill。


Apache Flink 在小米的穩定性最佳化和實踐

然而,Cgroup 只是在一定程度上解決了 container 頻繁被 Yarn oom kill 導致 lost 的問題,並沒有完全徹底地解決。在使用的過程中,依然存在某些 container 的記憶體使用持續上漲,最終被 cgroup oom kill 的情況,然後我們發現該問題可能與 glibc 的記憶體分配 bug 有關,長期執行的程式會存在連續多塊大小為 65536 的 anon 塊,所以我們最終的解決方案如下:


Apache Flink 在小米的穩定性最佳化和實踐

使用 Cgroup 解決記憶體臨時超用的問題,比如 RocksDB 對記憶體的限制不嚴格、小白使用者對記憶體的設定和使用不正確等造成的問題,然後升級 JDK 版本,解決 Jemalloc 分配時的執行緒死鎖 bug,最後切換 Jemalloc,解決 Linux 系統下的 64M anon 分配 bug。


Apache Flink 在小米的穩定性最佳化和實踐

經過一系列的最佳化,從上圖可以看出,container lost 的頻率由每月的近 5000 次減少到不到 100 次,因 Yarn oom kill 造成的作業異常重啟減少了 90% 以上,效果顯著。


Apache Flink 在小米的穩定性最佳化和實踐

第二個最佳化實踐是節點的平滑重啟功能,流式作業是長時間執行的作業,由於大部分都執行在廉價的機器上,因此機器出現過保、硬體故障、維修下線、機房遷移等都比較常見。為了提前預防可能出現的隱患,避免框架重啟造成的影響,提升雲環境下作業的穩定性,解決 Yarn 模式下恢復時間過長帶來的問題,我們開發了作業的平滑重啟功能。


Apache Flink 在小米的穩定性最佳化和實踐

將節點加入到 exclude 後,Flink recheckpoint manager 會獲取到 decommission 的資訊,透過解析該資訊得到對應的節點,並判斷當前執行任務的 container 是否執行在被 decommission 的節點上。如果是,就透過呼叫任務的 JobManager 的 stop with savepoint 介面去停止。平臺會自動檢測任務的執行狀態,如果某個作業不是透過平臺停止,則平臺會自動將該任務重新拉起,作業從 savepoint 恢復。這個過程會進行週期性的觸發並批次合併後再處理,避免訊息頻繁觸發造成瞬時負載壓力。此外,節點和 container 都會進行去重,避免對同一任務多次觸發影響穩定性。另外它的觸發週期遠小於 sre 在下線節點時設定的下線週期,也緩解了運維壓力。


Apache Flink 在小米的穩定性最佳化和實踐

JobManager 會啟動指標收集監控執行緒,並週期性地採集節點的 CPU、記憶體、磁碟 io 和網路 io 等指標,然後匯聚成指標集合,透過動態指標規則對指標進行判定,如果滿足條件就會將其加入到節點黑名單,這樣該 Application 的 container 便不會再執行在這個節點上。如果某個節點被多個 application 加入黑名單,則表明該節點可能存在問題,會自動觸發作業平滑重啟,並進行監控報警,以此來自動發現可能的異常節點。


Apache Flink 在小米的穩定性最佳化和實踐

上圖是 Flink Checkpoint 的大致流程,Checkpoint coordinator 會觸發 Checkpoint Operator 進行 Checkpoint,Checkpoint Operator 生成並向下遊廣播 Checkpoint Barrier,然後 Snapshot State。Checkpoint Operator 完成 Checkpoint 後進行 ack,下游節點收到 Checkpoint Barrier 後,根據是否要進行對齊做對應的處理,然後進入 Checkpoint 邏輯。所有的節點都向 Checkpoint Coordinateor ack 之後,表示該次 Checkpoint 已經完成,接著向所有參與 Checkpoint 的 Operator 傳送完成通知,最後 Operator 做最後的提交操作等。


Apache Flink 在小米的穩定性最佳化和實踐

Checkpoint 過程中遇到的問題包括以下這些:


  • 磁碟滿或其他 io 異常,會導致 Checkpoint 長期無法觸發,但異常資訊只存在於 JobManager 的日誌中,並不影響作業的正常執行,導致潛在的隱患不容易被感知。


  • 作業因邏輯變更、調整併發、重新排程等原因,重啟時預設不會從 Checkpoint 恢復,導致狀態丟失或者訊息積壓。


  • 大併發度時 Checkpoint 小檔案過多,引發大量的 HDFS RPC 負載壓力。


  • 使用者錯誤配置 Checkpoint 目錄引發的恢復衝突非常不容易控制,也不易排查。


針對以上問題,我們也進行了一些最佳化。


Apache Flink 在小米的穩定性最佳化和實踐

針對磁碟滿、io 異常、Kerberos 檔案損壞的問題,我們會捕獲異常棧,根據異常棧進行判斷和重試,並在失敗時增加 Checkpoint 的失敗計數,超過一定次數則進行框架內的重啟,或向使用者傳送告警,保證作業不會出現長時間的 Checkpoint 失敗而從一個非常老的 Checkpoint 恢復。


Apache Flink 在小米的穩定性最佳化和實踐

針對作業重啟時無法從 Checkpoint 恢復的問題,最佳化方式是對每個作業設定預設的保留數量,並在進行 Checkpoint 時先生成一個臨時的 Checkpoint Metadata 檔案,只有在 Finalize 時才會被 rename 成正式的檔案。接著將所有 Checkpoint 檔案按最後修改時間降序排序,在其中尋找正式的 Checkpoint Metadata 檔案。如果成功則表明其是一個完備的、可用於恢復的 Checkpoint 檔案。


在這樣的設定下,必須確保檔案最後修改時間的正確性。為此我們設定了任務 finish 預設不刪除 Checkpoint 檔案,任務在做 Savepoint 時預設不 discard 最新的 Checkpoint 檔案,以確保這兩類檔案最後修改時間的正確性。透過以上方式保證了任務能自動從最新的、完備的狀態進行恢復,需要重新處理的資料和狀態儘量少。另外,如果任務已經找到最新的、完備的 Checkpoint 並可以用來恢復,這表明前面的 Savepoint 和 Checkpoint 已經可以清理,由此減少空間的佔用。


於是我們透過為 Savepoint 設定生命週期來清理全量 Savepoint;對於增量的 Checkpoint,為了避免清除掉正在使用的狀態,會先去讀取其 Metadata 檔案的內容,將其中用到的狀態檔案對應的父資料夾保留,其餘的進行清理,從而確保在不影響狀態恢復的前提下,儘量減少檔案數和空間佔用。


Apache Flink 在小米的穩定性最佳化和實踐

針對使用者隨意配置 Checkpoint 目錄導致狀態恢復衝突和引發負載壓力的問題,透過在 Metadata 檔案中增加作業名和時間戳,當前的作業名與儲存的作業名不同則會提示告警資訊,恢復的 Checkpoint 的時間戳與當前時間存在較大的差異,也會有告警資訊。


小檔案是使用 HDFS 經常遇到的問題,由於 HDFS 適合於儲存大塊檔案,所以必須對小檔案進行最佳化來提升效能和穩定性。方法是在進行 Checkpoint 時對小檔案寫入進行合併,比如將多個小檔案寫入到 sequence file 中,形成一個大的檔案,這可能會造成空間浪費,但是對降低 HDFS Namenode 負載壓力效果比較明顯。


此外透過聯邦叢集的方式,使用多個 Namenode 均衡 RPC 請求負載,每一個 Namenode 都是一個相對獨立的服務,然後對使用者作業規範其 Checkpoint 目錄,使其訪問能夠被均衡到多個 Namenode 上,再對舊的 HDFS 檔案透過掛載表的形式讀舊寫新,逐步實現自動遷移到新的統一的規範目錄下。


Apache Flink 在小米的穩定性最佳化和實踐

接下來介紹一個案例,該案例來自小米資料採集服務,圖示是他們非常簡單的架構圖,主要是將多個源端 SDK 的埋點和資料收集到訊息佇列中,然後使用 Flink 進行 ETL,最終儲存到 Doris 中並在看板上進行展示。


Apache Flink 在小米的穩定性最佳化和實踐

目前該業務已經接入 750+ 國內外業務,日均處理 1600億+ 條訊息。透過採用 Checkpoint 相關的最佳化手段,將 RPC 延遲降低了約 40%,減少了小檔案。同時在作業透過 stop with savepoint 啟停時,保證了恢復的正確性,確保了 exactly once 的語義。


03

運維最佳化實踐

Apache Flink 在小米的穩定性最佳化和實踐

Flink Historyserver 對作業運維非常有效,尤其是它能在作業停止後,檢視作業的統計資訊,如果作業異常退出或處理結果有問題,我們又因為一些原因無法及時檢視相關日誌,就可以在將來透過 Historyserver 檢視。


Flink Historyserver 會在每一次定時清理時獲取上一次清理已經被快取的作業 ID,再獲取本次已經打包的歷史日誌資訊,然後判斷歷史日誌是否已經超過了配置的最大值,若是,就會將後面的歷史日誌直接執行清理,否則就會判斷上一次快取的作業在當次歷史日誌中是否存在,如果不存在也會執行清理。


但上述流程存在一系列的問題,一個是服務重啟會造成當前快取的已下載的作業資訊丟失,如果在重啟之間該作業的歷史日誌也丟失,就會形成懸浮的快取作業,本地快取的作業將會長期存在,無法清理。當前已打包的歷史日誌資訊不支援過期,導致大量的日誌存留於 HDFS 和本地磁碟,且會長期存在,不僅影響訪問的速度,也會造成磁碟空間的較大浪費。快取下來的作業歷史日誌最大值難以確定,基礎服務如 HDFS 等如果出現異常,會導致同時出現大量失敗,沖走有效日誌。另外當前預設並沒有記錄 Taskmanager 上的日誌,非常不利於異常排查,


針對上述問題我們也做了相應最佳化。


Apache Flink 在小米的穩定性最佳化和實踐

一個是讀取當前已經快取到本地磁碟的作業歷史日誌資訊,並將其與歷史日誌記錄進行對比,從而避免出現懸浮的快取作業;支援歷史日誌的最長保留時間,超過其生命週期就會進行清理,相比於當前支援的歷史日誌最大保留數量,更加科學合理;另外我們也支援了 Taskmanager 和 Container 歷史資料的打包和清理,更全面地記錄作業在異常退出時的各項資訊,方便排查問題。


Apache Flink 在小米的穩定性最佳化和實踐

作業的全鏈路心跳監控功能主要是對作業的鏈路延時進行監控,實現方式是透過在 Stream Checkpoint 中插入特殊標記,標記資訊包括作業的名稱、當前的時間,名稱的生成方式是 op+operator 在整個鏈路的 index 以及 subtask 在 operator 的 index,非 Checkpoint 節點會在收到標記後更新名稱,並用當前的時間減去 Checkpoint 插入的時間生成從 Checkpoint 到該 subtask 的耗時,並上報到 Metrics Reporter 中,最終對這些 metrics 進行計算,透過這種方式可以發現鏈路中的異常節點,監測作業的資料異常丟失,還能夠透過心跳資訊的插入頻率預估其影響。


心跳標記在遇到多個下游鏈路時並不是隨機選擇鏈路,而是同時廣播到多條鏈路中,因此可能會出現心跳監控標記資訊過多的情況,影響正常作業的處理效能。


Apache Flink 在小米的穩定性最佳化和實踐

這時就出現了一個矛盾點,全鏈路心跳監控取樣越頻繁,對各節點處理效能的監控就越及時準確,但同時也會造成資訊過多、影響正常資料的處理。針對這個問題,我們進行了以下三個方面的改進和處理:

  • 一是將 chain operator metrics 資訊進行合併上報,因為它的監控資訊基本相同,這樣可以減少上報的資料量。

  • 二是透過 restful 介面動態啟停監控,這樣只有在有異常時才會進行取樣和監控,正常情況下不影響作業的執行。

  • 三是透過對取樣進行週期性的合併和處理,實現了對任務 pipeline 資料量和延遲的預估以及監控功能。


Apache Flink 在小米的穩定性最佳化和實踐

restful 介面動態啟停監控功能不僅能動態啟停心跳監控,我們發現還有其他場景也能從這個功能中受益,因此我們對其進行了擴充套件。簡單的程式碼修改就能讓它支援其他配置的動態調整,包括 Checkpoint 配置,如 Checkpoint 週期和超時時間,動態日誌的級別等。


Apache Flink 在小米的穩定性最佳化和實踐

當作業出現效能或 Checkpoint 問題時,可以透過 restful 介面動態開啟、問題確定後動態停止,這樣就能解決心跳資訊過多的問題。在負載突增、短時資料傾斜導致 Checkpoint 超時,動態調整 Checkpoint 超時時間能避免作業因 Checkpoint 超時而失敗,它也能避免由於 Checkpoint 長時間不成功導致資料積壓更多、資料傾斜問題更嚴重而陷入的死迴圈。同時它還能用於確定超時時間,使用者可以透過動態調整的方式,不斷測試最適合作業的超時時間,減少了壓測過程中的作業啟停次數。它也支援其他配置的調整,比如動態調整日誌級別,但是需要注意的是調整後的配置並沒有持久化,會因為框架重啟或作業的重啟而失效。


04

未來規劃

Apache Flink 在小米的穩定性最佳化和實踐

未來,我們將在以下方面繼續探索:

  • 持續開發並最佳化自動彈性伸縮容的功能。Flink1.13 開始提供了自動彈性伸縮容的功能,但是目前並不完善,要在生產環境上用起來還需要做不少的工作。

  • 版本收斂是很多Flink開發人員都會遇到的一個問題。Flink 社群的發展比較快,版本的釋出和迭代也是非常快。為了降低運維壓力,緊跟社群,這也是勢在必行的。

  • 對 state 讀寫效能進行最佳化,提升大狀態作業的效能。

  • Heartbeat timeout 也是目前線上對穩定性影響比較大的問題,我們也會進行跟進和最佳化。

  • 對作業啟動和恢復效能進行最佳化,減少作業因各種原因造成的斷流是 Flink 社群和許多業務非常關注的問題,我們同樣也有面臨著這樣的壓力。

  • 繼續打磨批流融合能力,完善對 batch 模式和資料湖等的支援,也是現在的熱點,我們希望能在這上面進行更多探索,從而更好地支撐業務,也讓 Flink 的應用更加廣泛。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2924593/,如需轉載,請註明出處,否則將追究法律責任。

相關文章