Flink on K8s 在京東的持續優化實踐

ApacheFlink發表於2022-04-08

摘要:本文整理自京東資深技術專家付海濤在 Flink Forward Asia 2021 平臺建設專場的演講。主要內容包括:

  1. 基本介紹
  2. 生產實踐
  3. 優化改進
  4. 未來規劃

點選檢視直播回放 & 演講PDF

一、基本介紹

img

K8s 是目前業內非常流行的容器編排和管理平臺,它可以非常簡單高效地管理雲平臺中多個主機上的容器化應用。在 2017 年左右,我們實時計算是多個引擎並存的,包括 Storm、Spark Streaming 以及正在引入的新一代計算引擎 Flink,其中 Storm 叢集執行在物理機上,Spark Streaming 執行在 YARN 上,不同的執行環境導致部署和運營成本特別高,且資源利用有一定浪費,所以迫切需要一個統一的叢集資源管理和排程系統來解決這個問題。

而 K8s 可以很好地解決這些問題:它可以很方便地管理成千上萬的容器化應用,易於部署和運維;很容易做到混合部署,將不同負載的服務比如線上服務、機器學習、流批計算等混合在一起,獲得更好的資源利用;此外,它還具有天然容器隔離、原生彈性自愈的能力,可以提供更好的隔離性與安全性。

經過一系列的嘗試、優化和效能對比後,我們選擇了 K8s。

img

2018 年初,實時計算平臺開始全面容器化改造;到 2018 年 6 月,已經有 20% 的任務執行在 K8s 上,從執行結果看,無論是資源的共享能力、還是業務處理能力,以及敏捷性和效率方面都獲得了較大提升,初步達到了預期的效果;到 2019 年 2 月實現了實時計算全部容器化;之後直到現在,我們在 K8s 的環境也一直在進行優化和實踐,比如進行彈性伸縮、服務混部、任務快速恢復能力建設等方面的實踐。

全部 on K8s 後收益還是比較明顯的:首先混合部署服務和資源共享能力獲得了提升,節省機器資源 30%;其次,具有更好的資源隔離和彈性自愈能力,比較容易實現根據業務的負載進行資源的彈性伸縮,保證了業務的穩定性;最後開發、測試、生產一致性的環境,避免環境給整個開發過程帶來問題,同時極大提升了部署和運營自動化的能力,降低了管理運維的成本。

img

京東 Flink on K8s 的平臺架構如上圖,最下面是物理機和雲主機,之上是 K8s,它採用京東自研的 JDOS 平臺,基於標準的 K8s 進行了許多定製優化,使之更適應我們生產環境的實際情況。JDOS 大部分執行在物理機上,少部分是在雲主機上。再往上是基於社群版 Flink 進行深度定製化後的 Flink 引擎。

最上面就是京東的實時計算平臺 JRC,支援 SQL 作業和 jar 包作業,提供高吞吐、低延遲、高可用、彈性自愈易用的一站式海量流批資料計算能力,支援豐富的資料來源和目標源,具備完善的作業管理、配置、部署、日誌監控和自運維的功能,提供備份回滾和一鍵遷移的功能。

我們的實時計算平臺服務於京東內部非常多的業務線,主要應用場景包括實時數倉,實時大屏、實時推薦、實時報表、實時風控和實時監控以及其他的應用場景。目前我們的實時 K8s 叢集由 7000 多臺機器組成,線上 Flink 任務數有 5000 多,資料處理峰值可以達到每秒 10 億多條。

二、生產實踐

img

最開始容器化方案採用的是基於 K8s deployment 部署的 standalone session 叢集,這是資源靜態分配的模式,如上圖所示,需要使用者在建立的時候就決定好所需要的管理節點 Jobmanager 的個數和規格 (包括 CPU 的核數、記憶體和磁碟的大小等)、執行節點 Taskmanager 的個數和規格 (包括 CPU、記憶體和磁碟大小等),以及 Taskmanager 包含的 slot 個數。建立叢集后,JRC 平臺通過 K8s 客戶端向 K8s master 發出請求,建立 Jobmanager 的 deployment,這裡使用 ZK 保證高可用,使用 HDFS 和 OSS 進行狀態儲存,叢集建立完成後就可以提交任務了。

但是在我們實踐的過程中發現該方案存在一些不足,它需要業務提前預估出所需要的資源,對業務不太友好,無法滿足靈活多變的業務場景。比如對一些複雜拓撲或者一個叢集跑多個任務的場景,業務很難預先精準確定出所需要資源,這時候一般都會先建立出一個較大的叢集,這樣就會帶來一定的資源浪費。在任務執行的過程中,也沒有辦法根據任務的執行情況,按需進行資源的動態伸縮。

img

於是我們又對容器化方案進行了升級,支援彈性資源模式。這是採用資源按需分配的方式,如上圖所示,它需要使用者在建立時指定好所需要管理節點 Jobmanager 的個數和規格,以及執行節點 Taskmanager 的規格,而 Taskmanager 的個數可以不指定。點選建立叢集后,JRC 平臺會通過 K8s 客戶端向 K8s master 發出請求,建立 Jobmanager 的 deployment 以及可選地預建立指定數量 Taskmanager 的 pod。

平臺提交任務後,由 JobMaster 通過 JDResourceManager 向 JRC 平臺發出申請資源的 rest 請求,然後平臺向 K8s master 動態申請資源去建立執行 Taskmanager 的 pod,在執行過程中,如果發現某個 Taskmanager 長時間空閒,可以根據配置動態釋放資源。這裡通過平臺與 K8s 互動進行資源的建立和銷燬,主要是為了保證計算平臺對資源的管控,同時避免了叢集配置和邏輯變化對映象的影響;通過支援使用者配置 Taskmanager 個數進行資源的預分配,可以做到與資源靜態分配同樣快速的任務提交速度;同時通過定製資源分配策略,可以做到相容原有 slot 分散分佈的均衡排程。

img

在 Flink on K8s 的環境中,日誌和監控指標是非常重要的,它可以幫助我們觀察整個叢集、容器、任務的執行情況,根據日誌和監控快速定位問題並及時處理。

這裡的監控指標包括物理機指標 (比如 CPU、記憶體、負載、網路、連通性、磁碟等指標)、容器指標 (比如 CPU、記憶體、網路等指標)、JVM 指標和 Flink 指標 (叢集指標和任務指標)。其中物理機指標和容器指標是通過 metric agent 採集上報到 Origin 系統,JVM 指標和 Flink 指標是通過 Jobmanager 和 Taskmanager 中定製的 metric reporter 上報到白澤系統,之後統一在計算平臺進行監控的檢視和告警。

日誌採集採用京東的 Logbook 服務,它的基本機制是在每個 Node 上會執行一個 log agent,用於採集指定路徑的日誌;然後 Jobmanager 或 Taskmanager 會按照指定規則輸出日誌到指定目錄,之後日誌就會被自動採集到 Logbook 系統;最後可以通過計算平臺進行實時日誌和歷史日誌的檢索和查詢。

img

接下來是容器網路的效能問題。一般來說虛擬化的東西都會帶來一定的效能損耗,容器網路作為容器虛擬化的一個重要元件,相比物理機網路來說,不可避免地會出現一些效能的損耗。效能的下降程度根據網路外掛的不同、協議型別和資料包的大小會有所不同。

如上圖所示,是對於跨主機容器網路通訊的效能測評。參考基線是 server 和 client 在同一主機上進行通訊。從圖中可以看到,host 模式取得了接近參考基線的吞吐量和延遲,NAT 和 Calico 有較大的效能損失,這是由於地址轉換和網路包路由的開銷導致的;而所有 overlay 網路都有非常大的效能損失。總的來說,網路包的封裝和解封相比地址轉換和路由來說開銷更大,那麼採用何種網路就需要做一個權衡。比如 overlay 網路由於網路包的封裝和解封導致了很大的開銷,效能會比較差,但允許更靈活和安全的網路管理;NAT 和主機模式的網路比較容易取得好的效能,但是安全性較差;Routing 網路效能也不錯但需要額外的支援。

img

此外,網路損耗對於 checkpoint 的快慢影響也很大。根據我們對比測試,網路模式不同的情況下,同樣的環境下執行同樣的任務,採用容器網路任務的 checkpoint 時長比使用主機網路慢了一倍以上。那麼怎麼解決這個容器網路的效能問題?

  • 一是可以根據機房環境選擇合適的網路模式:比如對於我們一些舊的機房,容器網路效能下降特別明顯,而且網路的架構也不能升級,採用了主機網路 (如上圖所示,在 pod yaml 檔案中配置 hostNetwork=true) 來避免損耗的問題,雖說這不太符合 K8s 的風格,但需要根據條件做個權衡;而對於新的機房,由於基礎網路的效能提升以及採用了新的高效能網路外掛,效能損耗相比主機網非常小,就採用了容器網;
  • 二是儘量不要使用異構網路環境,避免 K8s 跨機房,同時適當調整叢集網路的相關引數,增加網路的容錯能力。比如可以適當調大 akka.ask.timeouttaskmanager.network.request-backoff.max 兩個引數。

img

下面說一下磁碟的效能問題。容器中的儲存空間由兩部分組成,如上圖所示,底層是隻讀的映象層,頂部是可讀寫的容器層。容器執行的時候涉及到檔案的寫操作都是在容器層中完成的,這裡需要一個儲存驅動提供聯合檔案系統來管理。儲存驅動一般來說為空間效率進行了優化,額外的抽象會帶來一定的效能損耗 (取決於具體儲存驅動),寫入速度要低於本地檔案系統,特別是使用了寫時複製的儲存驅動來說,損耗更大。這對於寫密集型的應用來說,會有更大的效能影響。而在 Flink 中,很多地方都涉及到本地磁碟的讀寫,比如日誌輸出、RocksDB 讀寫、批任務 shuffle 等。那麼該如何處理來減小影響?

  • 一是可以考慮使用外掛的 Volume,使用本地儲存卷,直接寫資料到 host fileSystem 來提升效能;
  • 此外也可以調優磁碟 IO 相關引數,比如調優 RocksDB 引數,提升磁碟的訪問效能;
  • 最後也可以考慮採用一些儲存計算分離的方案,比如使用 remote shuffle,提升本地 shuffle 的效能和穩定性。

img

在實踐過程中經常會發現,很多業務的計算任務配置不合理,佔用了過多的資源造成了資源浪費。此外,流量存在波峰波谷,如何在洪峰時自動擴容,在波谷時自動縮容,在減少人工干預、保證業務穩定的同時提高資源利用率,這都涉及到資源彈性伸縮的問題。為此我們開發了彈性伸縮的服務,根據作業執行情況動態調整任務的並行度以及 Taskmanager 的規格,來解決作業吞吐不足、資源浪費等問題。

如上圖所示,大致的工作流程如下:首先在 JRC 平臺進行任務的伸縮配置,主要包括執行度調整的上下限以及一些伸縮策略的閾值,這些配置都會傳送到伸縮服務;伸縮服務執行過程中會實時監測叢集和任務的執行指標 (主要是一些 CPU 的使用率和運算元的繁忙程度等),結合伸縮配置和調整策略生成任務調整結果,傳送到 JRC 平臺;最後 JRC 平臺根據調整結果,對叢集和任務進行調整。

目前通過該伸縮服務,可以較好地解決一些場景的資源浪費問題,以及任務吞吐與運算元並行度呈線性關係條件下的效能問題。不過它還是存在一定的侷限性,比如對於外部的系統瓶頸、資料傾斜以及任務本身的效能瓶頸還有無法通過擴並行度提升的場景,不能很好地應對解決。

此外,結合彈性伸縮,我們也進行了一些實時流任務和離線批任務錯峰混部的嘗試。如上圖右所示,在凌晨前後,流任務比較空閒,會縮容釋放出一些資源給批任務;之後可以使用這些釋放的資源在夜間執行批任務;到了白天批任務執行完釋放的資源又可以還給流任務,用於擴容以應對流量洪峰,從而提高資源的整體利用率。

img

相比物理機或 YARN 環境,Flink on K8s 出現問題以後的排查相對要更困難,因為這裡面還涉及到 K8s 許多元件,比如容器網路、DNS 解析、K8s 排程等各方面的問題,都存在一定的門檻。

為了解決這個問題,我們開發了智慧診斷的服務,將作業相關的各個維度的監控指標 (包括物理機的、容器的、叢集的和任務的指標) 與任務拓撲結合起來並與 K8s 打通,結合 pod 日誌和任務日誌聯合進行分析,並將日常人工運維的一些方法進行歸納總結應用到分析策略中,診斷出作業的問題並給出優化建議。目前支援對任務重啟、任務背壓、checkpoint 失敗、叢集資源利用率低等一些常見問題進行診斷,後續會持續豐富和完善。

三、優化改進

img

在實踐的過程中,採用資源靜態分配模式的時候,一般都會將 slot 按照 Taskmanager 打散,將耗費資源的運算元按照 Taskmanager 分散開來,實現作業的均衡排程,提高作業的效能。

如右上圖所示有 2 個 Taskmanager,每個 Taskmanager 有 4 個 slot,1 個作業有 2 個運算元 (分別用綠色和紅色表示),每個運算元 2 個並行度。在使用預設排程策略 (順序排程) 的情況下,這個作業的所有運算元都會集中在一個 Taskmanager;而如果使用均衡排程,這個作業的所有運算元都會按照 Taskmanager 進行橫向打散,每個 Taskmanager 都會分到兩個運算元的一個並行度 (綠色和紅色)。

而在採用資源動態分配模式 (native K8s) 的時候,資源是一個個 pod 單獨申請建立的,那麼這個時候如何實現均衡排程呢?我們採用了在任務排程之前進行資源預分配的方式來解決這個問題。具體過程如下:使用者提交作業後,如果開啟了資源預分配,JobMaster 不會立即排程任務,而是會向 ResourceManager 一次性預申請作業所需的資源,在所需資源到位後,JobMaster 會得到通知,此時再排程任務就可以做到和靜態資源分配模式時同樣的均衡排程了。這裡還可以給 JobMaster 配置一個超時時間,超時後就走正常任務排程流程,而不會無限地等待資源。

我們進行了真實場景的效能對比,如上圖右所示,使用順序排程的時候作業吞吐量為 5700 萬/分鐘,而開啟了資源預分配和均衡排程後,作業吞吐量為 8947 萬/分鐘,效能提升了 57%,還是有比較明顯的效果的。

img

我們平臺有不少業務採用一個叢集執行多個任務的模式,這樣就會存在一個 Taskmanager 分佈了不同 job 的 Task,從而導致不同 job 之間相互影響。那麼如何解決這個問題?

我們定製了 slot 的分配策略,在 Jobmanager 向 ResourceManager 請求 slot 時,如果開啟了任務資源隔離,SlotManager 會把已經分配 slot 的 Taskmanager 打上 job 的標籤,之後該 Taskmanager 的空閒 slot 只能用於該 job 的 slot 請求。通過將 Taskmanager 按照 job 分組,實現了叢集多工的資源隔離。

如上圖右所示,一個 Taskmanager 提供 3 個 slot,有 3 個 job,每個 job 有一個運算元,且並行度都為 3 (分別用綠色、藍色和紅色表示)。開啟 slot 平鋪分散,在隔離前,這三個 job 會共享這三個 Taskmanager,每個 Taskmanager 上都分佈了每個 job 的一個並行度。而在開啟任務資源隔離後,每一個 job 部將會獨佔一個 Taskmanager,不會相互影響。

img

容器環境複雜多變,pod 被驅逐或重啟時有發生:比如機器發生硬體故障、docker 故障、節點負載較高等都會導致 pod 被驅逐;程式不健康、程式異常退出、docker 異常重啟等也都會導致 pod 重啟。此時,會導致任務重啟恢復,對業務造成影響。那麼如何才能減少對業務的影響?

一個方面是針對容器環境,加快 pod 異常 (被驅逐或重啟) 的感知速度,迅速恢復作業。在官方的預設實現中,如果 pod 發生異常,可能會從兩個路徑感知到:一個是故障 pod 下游運算元可能會感知到網路連線的斷開,從而引發異常觸發 failover;一個是 Jobmanager 會首先感覺到 Taskmanager 心跳超時,此時也會觸發 failover。無論是通過哪個路徑,所需要的時長都會比超時要多一些,在我們預設系統配置下,所需的時間是 60 多秒。

這裡我們優化了 pod 異常感知的速度。在 pod 異常被停止時,預設會有一個 30 秒的優雅停止的時間,此時容器主程式啟動指令碼會收到來自 K8s 的 TERM 訊號,除了做必要的清理動作之外,我們增加了通知 Jobmanager 異常 Taskmanager 的環節;在容器內工作程式 Taskmanager 異常退出的時候,主程式 (這裡是啟動指令碼) 也會感知到,也會通知 Jobmanager 是哪個 Taskmanager 發生了異常。這樣一來,Jobmanager 就可以在 pod 異常的時候第一時間得到通知,並及時進行作業的故障恢復。

通過這項優化,測試典型場景下,在叢集有空餘資源的情況下,任務 failover 的時長從原來的 60 多秒縮短到幾秒;在叢集中沒有空餘資源需要等待 pod 重建的情況下,任務 failover 的時長也縮短了 30 多秒,效果還是比較明顯的。

img

另外一個方面是減小 pod 異常對作業的影響範圍。雖說社群版在 1.9 之後,提供了基於 region 的區域性恢復策略,在 Task 發生故障時,只重啟故障 Task 關聯 region 內的 Task,在有的場景下可以減小影響。但是很多時候一個作業的運算元之間都是 rebalance 或者 hash 等全連線的方式,region 策略也起不到太大作用。為此,我們在 1.10 和 1.12 版本中,開發了基於故障 Task 的單點故障恢復策略,Task 發生故障時只恢復該故障 Task,非故障 Task 不受影響。

如上圖所示,這個作業有三個運算元 source、map 和 sink。其中 source 和 map 都是 1 個並行度,sink 是 2 個並行度。 map 的第一個並行度 map(1/1) 和 sink 的第二個並行度 sink(2/2) 分佈在 pod_B 上,在 pod_B 被驅逐的時候,Jobmanager 會檢測到 pod_B 異常,之後會在新的 pod_D 上重新部署這兩個 Task,記為 map(1/1)’ 和 sink(2/2)’;部署完成後,會通知故障 Task map(1/1) 的下游 sink(1/1) 新的上游 Task map(1/1)’ 已經 ready,然後 sink(1/1) 就會和上游 map(1/1)’ 重新建立連線,進行通訊。

在具體實現的時候有以下幾點需要注意:

  • 一是故障恢復前,故障 Task 的上游對於待傳送資料和下游對於接收的殘留資料如何進行處理?這裡我們會將上游輸出到故障Task資料直接丟棄掉,下游如果收集到不完整的資料也會丟棄掉;
  • 二是上下游無法感知到對方異常時,再恢復的時候如何進行處理?這裡可能需要一個強制的更新處理;
  • 三是一個 pod 上分佈了多個 Task 的情況,如果該 pod 異常,存在多個故障 Task,這些故障 Task 之間如果存在依賴關係,如何正確地進行處理?這裡需要按照依賴關係進行順序的部署。

通過單點恢復策略,線上應用取得了不錯的效果,對作業的影響範圍大大減少 (取於具體的作業,能夠減少為原來的幾十分之一到幾百分之一),避免了業務的斷流,同時恢復時長也大大降低 (從典型場景的一分多鐘降低到幾秒 - 幾十秒)。

當然,這個策略也是有代價的,它在恢復的時候會帶來少量的丟數,適用於對少量丟數不敏感的業務場景,比如流量業務。

四、未來規劃

img

未來我們會在以下幾方面繼續探索:

  • 首先是排程優化:

    • 一個是 K8s 層面資源排程優化,更高效地管理大資料的線上服務和離線作業,提升 K8s 叢集的利用率和執行效率;
    • 一個是 Flink 作業排程優化,支援更豐富、更細粒度的排程策略,提升 Flink 作業資源的利用率和穩定性,滿足不同的業務場景需要。
  • 其次是服務混部:將不同負載的服務混部在一起,在保證服務穩定的前提下儘量提升資源利用率,使伺服器的價值最大化;
  • 然後是智慧運維:支援對任務進行智慧診斷,並自適應調整執行引數,實現作業的資質,降低使用者調優和平臺運維的成本;
  • 最後是 Flink AI 的支援:人工智慧應用場景中,Flink 在包括特徵工程、線上學習、資源預測等方面都有一些獨特的優勢,後續我們也將在這些場景從平臺層面進行探索和實踐。

點選檢視直播回放 & 演講PDF

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

image.png

相關文章