vivo 在離線混部探索與實踐

vivo互联网技术發表於2024-02-29
作者:來自 vivo 網際網路伺服器團隊

本文根據甘青、黃榮傑老師在“2023 vivo開發者大會"現場演講內容整理而成。

伴隨 vivo 網際網路業務的高速發展,資料中心的規模不斷擴大,成本問題日益突出。在離線混部技術可以在保證服務質量的同時,極大的提升資料中心資源利用率,降低成本。混部技術涉及任務排程、資源隔離、運維觀測等一系列技術難題,本文將介紹 vivo 在混部技術方面的實踐和探索,為讀者提供借鑑和參考

一、在離線混部技術背景

1.1 為什麼混部

圖片

資料中心執行的服務可以分為線上服務和離線任務兩大類,它們具有不同的資源使用特徵。

線上服務是指那些長時間執行、對時延非常敏感的服務,如電商、遊戲等,線上服務的資源利用率存在明顯的波峰波谷現象,平均利用率較低。離線任務是指那些執行週期短,有容錯性,對實時性要求低的服務,如資料轉換、模型訓練等,離線任務在執行過程中資源利用率很高。

在混部之前,線上和離線都是分開獨立部署,機器不共享,無法形成有效的資源互補,這導致資料中心整體資源利用率不高,卻要不斷購買新機器,造成了資源浪費。

1.2 混部技術定義

圖片

透過混部技術,我們可以將線上和離線部署到同一臺物理機上,形成資源互補,提升物理機的資源利用率,降低成本。混部技術最早由谷歌在2015年提出,經過多年的發展,混部技術已經趨於成熟,目前業內利用混部技術可以將資料中心的CPU利用率提升至40%左右 。

vivo在2020年開始調研混部技術,2023年混部平臺投入生產,目前我們已經將部分混部叢集的CPU利用率提升至25%(最新已達30%)左右。相較業界標杆這還有一定的差距,但隨著混部規模的擴大,我們將挑戰更高的目標。

二、在離線混部平臺實踐

2.1 混部平臺產品能力

圖片

混部平臺必須具備兩個產品能力:

  • 第一、強大的排程、隔離能力
  • 第二、完善的監控、運維能力

強大的排程能力解決了,我們如何將離線任務高效、合理的排程到線上服務所在的物理機上。而強大的隔離能力保障了線上服務的質量不受離線任務干擾。完善的監控和運維能力則可以讓我們洞悉整個混部平臺的執行情況,及時發現潛在風險,幫助運維人員更高效的完成系統和業務的運維工作,保障叢集的高穩定性。

2.2 混部差異化資源檢視

圖片

混部首先要解決的一個問題是離線使用哪一部分資源。

在vivo混部系統中線上和離線看到的資源檢視是不同的:

  • 線上可用資源為 整機資源
  • 離線可用資源為 整機資源減去 線上實際使用的資源

同時為了避免整機負載太高影響系統的穩定性,我們設定一個安全水位線,用於調節離線可用資源大小。

2.3 混部QoS等級

圖片

為了保障混部系統的slo,我們將服務分為三個等級:高、中,低

不同等級的服務對物理資源如:CPU、記憶體 使用時有不同的優先順序。高優先順序服務支援繫結CPU模式,適用對延時非常敏感的線上服務。一般的線上服務可設定為中優先順序。離線任務設定為低優先順序,透過這樣的等級劃分,我們很好的實現線上對離線的資源壓制和隔離,保障了線上服務質量。

2.4 混部核心元件架構

圖片

我們所有的混部元件都是以外掛方式獨立執行,對原生K8s無侵入。我們實現了一個混部排程器,線上和離線統一使用這個排程器,避免了多排程器資源賬本衝突的問題。

每臺物理機上都會部署一個混部agent,它可以實時採集容器資源使用資料,並根據安全水位線對離線任務進行壓制、驅逐等操作。

核心層面我們使用了龍蜥OS,它具備強大的資源隔離能力,可以幫助我們更好的隔離線上、離線資源使用,保障線上服務質量。

2.5 混部元件功能

圖片

我們把混部元件分為管控元件和單機元件兩大類。

管控元件主要負責排程和控制,根據vivo業務使用場景,我們對排程器做了一些增強,提供了numa感知、負載感知,熱點打散,批次排程等能力。

混部控制器主要提供了一些配置管理能力:如資源畫像統計、node slo配置、node擴充套件資源變更等。

2.6 混部視覺化監控

圖片

我們為混部建立一套完整的視覺化監控體系。

針對線上服務我們提供了:容器資源使用指標,受離線干擾指標、業務混部收益指標等監控能力。

針對離線任務,我們提供了離線可用資源、任務異常狀態等監控能力。

在平臺層面上我們提供了節點、核心,核心元件的監控,透過這些監控可及時發現平臺潛在的風險。

2.7 混部平臺運維

圖片

為了簡化運維操作,提升運維效率,我們對混部叢集搭建和節點擴縮容操作進行了白屏化改造,開發了資源池管理功能,簡化了物理機接入流程,運維效率大幅提升。

在運維平臺上運維人員可以快速調整混部隔離、水位線等引數,如果發現線上服務受到干擾,運維人員可以一鍵關閉混部,驅逐離線任務,保障線上服務質量。

2.8 問題與挑戰

2.8.1 apiServer拆分

圖片

透過混部產品能力的建設,我們很好的實現了容器混部能力,但是在實踐中我們還是遇到一些新的挑戰:相對於普通K8s叢集,混部叢集中執行著更多的容器,而且離線任務由於生命週期短,容器建立銷燬更為頻繁,這對K8s apiServer 產生了很大的壓力。

所以我們拆分了apiServer ,離線任務使用獨立的apiServer ,保障了叢集apiServer 負載一直處於一個安全水平。

2.8.2 監控架構最佳化

圖片

同樣混部之後由於採集了更多的監控指標,導致Prometheus記憶體消耗過多,無法滿足平臺指標 採集需求。針對這個問題,我們最佳化了監控架構,將線上和離線監控元件分開部署,離線改用效能更好的vmagent,透過這個最佳化,監控元件記憶體消耗減少到原來的十分之一。

2.9 利用率提升

圖片

混部初期雖然叢集CPU利用率有所提升,但是還是沒有達到我們的預期,主要原因有:

  • 一、部分低配置機器資源本身較少。
  • 二、Java 類應用堆會固定佔用大量記憶體,導致可提供給離線使用記憶體資源不足。

針對這些問題,我們開發了定時調整安全水位線功能,在業務低峰期上調安全水位線,釋放更多的資源給離線使用。透過一系列的最佳化手段,我們將其中一個混部叢集的CPU利用率由13%提升到了25%左右,幾乎翻倍,混部效果得到了有效的驗證。

三、Spark on K8s 彈性排程實踐

3.1 方案選型

圖片

在大方向的技術選型上,我們選擇了 Spark on K8s,在業內,也有一些公司採用了 YARN on K8s的方案。我們也對這兩種方案進行過對比。

從業務適用性來說,YARN on K8s 是通用的,可以相容Hive、Spark、Flink這些引擎,它不需要頻繁建立Nodemanager pod,對K8s的壓力比較小。這些都是它的優點,但另一方面,Nodemanager ESS服務是對磁碟有容量和讀寫效能要求的,混部機器磁碟一般難以滿足。所以我們要支援不同引擎的remote shuffle service。

如果計算引擎有不同的版本,那麼RSS也要支援不同版本,比如Spark2,Spark3。如果你有不同的引擎,不同的版本,很可能一種RSS還滿足不了需求。另外Nodemanager需要根據K8s混部節點的剩餘資源,動態調整可用的vcore和記憶體,所以還需要一個額外的元件來做這個事情,這需要較高的改造成本。在資源利用上,NM的資源粒度相對大,自身也會佔用一些資源,存在一定的浪費。在資源緊張的情況下,Nodemanager作為整體被驅逐,會影響多個任務。這些是YARN on K8s的劣勢。

作為對比,Spark on K8s 劣勢有哪些?

首先這個特性在Spark 3.1以上版本才正式可用。Spark on K8s由於會頻繁的建立、查詢、銷燬大量的executor pod,對K8s的排程能力以及master節點會造成比較大的壓力。另一方面,它的優勢在於只需要能支援spark3.X的RSS,這有較多的開源產品可選擇。而且改造成本比較低,不需要額外的元件。資源粒度小,更有利於充分利用叢集資源。在資源緊張時,會逐個pod進行驅逐,任務的穩定性會更高。

兩方案各有優劣勢,為什麼我們選擇了Spark on K8s?一方面因為Spark3.X是vivo當前及未來2~3年的主流離線引擎,另一方面vivo內部對K8s研發比較深入,能有力支援我們。基於以上原因,我們最終決定使用spark on K8s

3.2 三步走戰略

圖片

確定了方案選型,那在vivo我們是如何推進spark on K8s大規模的應用落地呢?回顧總結我們走過的路,可以大致歸納為三步走的戰略。

  • 第一,是任務跑通跑順的初期階段
  • 第二,是任務跑穩、跑穩的中期階段
  • 最後,是任務跑得智慧的成熟階段

接下來的內容,我們將對每個階段展開細說。

3.2.1 任務跑通跑順

圖片

在任務跑通、跑順的第一階段,我們要解決的是怎麼將任務提交K8s叢集,同時要求易用性、便利性方面能夠達到與on YARN 一致的使用者體驗。將我們最後採用的方案架構簡化一下,就如同這張圖所示。

首先,為了降低任務提交的複雜性、避免使用者改造任務的成本。我們在任務排程管理平臺做到了對原有Spark任務的相容,透過vivo內部的容器開放API-這個代理層,我們不需要維護額外的K8s client環境,就可以輕鬆實現任務提交,提交後也能近實時獲取任務的狀態和日誌資訊。

另外一個關鍵點是,我們選用了Spark Operator作為Spark任務容器化的方案。Spark Operator是谷歌基於K8s Operator模式開發的一款的工具,用於透過宣告式的方式向K8s叢集提交Spark作業。

Spark Operator的方式還有其他優點:

  • Operator方式對K8s更友好,支援更靈活、更全面的配置項
  • 使用上更簡單易用
  • 內建Metrics,有利於我們做集中管理

要達到階段一的目標,讓任務跑通、跑順。我們主要克服了哪些關鍵問題和挑戰

圖片

第一個是日誌檢視,因為Spark Operator方式並沒有提供已結束作業的日誌檢視方式,包括driver和executor日誌。在Driver側,我們透過定期請求容器開放API,能準實時地獲取Driver Pod狀態與日誌。在Executor側,我們參考了on yarn的方式,Executor Pod結束後,日誌上傳HDFS,與YARN日誌聚合類似。

另一方面,我們也在Spark HistoryServer做了二次開發工作,增加了on K8s方式的日誌檢視介面。使用者檢視已完成的Executor日誌時,不再請求JobHistory Server,而是請求Spark HistoryServer介面。在體驗上做到了基本與yarn一致。

在混部K8s叢集,我們也做了三方面能力的加強

  • 一是,確保分配能力能支援離線任務頻繁建刪pod的需求,在最佳化後我們離線Pod分配能力達到數百pod/秒。
  • 二是,在K8s側提升了spark內部的Driver優先順序,確保了在驅逐時Driver穩定性高於Executor。
  • 最後一個是,發現並修復了spark-operator的一個bug,這個bu是Operator在多副本部署時,slave副本webhook處理有一點機率出現pod 找不到的問題。

3.2.2 任務跑穩跑準

圖片

在第二階段,我們要保障的是任務跑穩,資料跑準,因此,我們有兩個關鍵的舉措

  • 大規模雙跑,目的是確保Spark任務遷移到K8s叢集后是相容的,任務成功率有保障;任務執行時長是穩定的,不會明顯變慢;資料是準確的,跟on YARN保持一致性。為此,我們需要對任務進行on YARN和on K8s兩種模式下的雙跑測試,我們分批次總共進行了7輪雙跑,覆蓋了2萬+的線上正式任務。最終也取得了我們想要的結果:我們雙跑最終達成的任務成功率超過了99.5%,絕大部分的任務在兩種模式下的時長波動在25%以內,資料一致性是100%。
  • 混部叢集的壓力聯調,目的是確保混部叢集的承載容量能夠支撐大規模的離線任務排程,透過模擬未來1年的任務量來給混部叢集做壓力測試,充分發現和檢測K8s叢集可能存在的效能問題。最終,透過我們多輪壓測和問題解決,我們在單個K8s叢集能夠支撐150+同時執行的Spark任務,1萬+同時在執行的Pod數量。

圖片

第二階段,我們主要面臨三個方面的問題和挑戰

首先是我們需要為Spark選擇一個外部的shuffle服務,經過技術選型和比較,我們最終選擇開源的celeborn作為我們的remote shuffle service元件。我們透過對機型和引數的測試調優,使celeborn的效能達到我們的預期需求。在大規模的應用場景中,我們還發現了會存在大任務會阻塞小任務,導致shufle read變慢的問題,我們對這種情況做了引數和程式碼上的最佳化,當前社群也針對shuffle read的問題有一些待最佳化的改進。另外celeborn進行了容器化部署,在提升自動化運維能力的同時,也可以為混部叢集提供額外的計算資源。

其次,在任務穩定性方面,我們也解決了一系列的問題。

  1. 在雙跑的時候,我們發現有不少任務在on K8s模式下很容易OOM,這是因為在on YARN模式下申請的container記憶體大小,不止是由Spark任務本身的記憶體引數決定,還會被YARN的資源粒度引數所影響。所以這塊要做一些適配對標工作。
  2. 在任務量比較大的情況下,Spark operator的吞吐能力會遇到瓶頸,需要我們將併發worker數量、佇列的相關引數調大。
  3. CoreDNS因為Spark任務頻繁的域名解釋請求,導致壓力增大,甚至可能影響線上服務。這個可以透過訪問ip而不是域名的方式來規避,比如namenode節點、driver和executor。
  4. 橫向擴充套件namespace,這樣可以避免單namespace的瓶頸,也防止etcd出現問題。
  5. 我們K8s apiserver的壓力隨著任務量增長壓力也會逐漸增大,這會影響整個叢集的穩定性。我們主要透過最佳化Spark driver list pod介面、使用hostnetwork方式兩個最佳化手段,有效降低了apiserver的壓力。

最後要說的是資料一致性,關鍵點是要做到行級記錄的MD5校驗,發現有不一致的Case,我們做到100%的分析覆蓋。排除了因為時間戳隨機函式等一些預期內的不一致,我們發現並修復兩種case會偶發導致不一致的問題:

  • celeborn Bug導致不一致,具體可參考CELEBORN-383解決
  • Java版本不一致導致的問題

3.2.3 任務跑得智慧

圖片

第三階段,我們需要解決的問題是讓任務跑得智慧,怎麼定義智慧,我想用三個詞來概括彈性、健壯、業務需求。這是我們彈性排程的架構圖,細節我就不講了,這裡我介紹下我們的排程系統重點支援的功能。

圖片

在彈性方面,我們需要做到實時根據混部叢集資源閒忙,智慧提交至混部叢集或者Hadoop叢集。在前期我們K8s叢集的資源相對Hadoop是小頭,透過合理的水位線控制,防止大量任務同時排程到K8s導致餓死。

健壯,就是要保證任務的高可用。

我們建設的能力包括:

  • 任務雙跑成功後再混部
  • 支援離線任務失敗自動回滾到Hadoop叢集執行
  • 支援使用者自主決定任務是否可排程至K8s叢集
  • 初期剔除重要核心任務、剔除不可重試任務

目的是在使用者任務遷移時做到讓使用者無感。

在滿足業務需求方面,我們支援優先排程本業務的離線任務, 優先滿足業務部門的離線任務資源需求;支援只在指定時間段裡排程離線任務,支援在出現異常情況下一鍵終止排程K8s。這些是為了確保線上服務的高可用性,免除線上業務的後顧之憂。

3.3 混部效果

圖片

克服了三步走過程中的磕磕碰碰,我們終於可以將離線任務大規模混布到K8s混部叢集了。但是我們很快發現,混部叢集的整體利用率並沒有達到我們的預期,主要有三方面的原因

  1. 初期的Spark任務不足,這個我們透過加快雙跑,遷移低版本的Spark任務,遷移Hive SQL任務來解決。
  2. 在混部的時候,我們也觀察到,離線任務的pod cpu利用率其實沒那麼高。比如我們申請一個核,通常只能利用0.6個核,存在浪費的情況。我們上線了CPU資源超分的能力,目前是靜態的固定比例超分,透過這個措施,我們能將pod的實際cpu利用率打到80%以上。
  3. 另外就是混部叢集中的機器配置不均,部分機器cpu資源充足,但是記憶體不夠。我們透過在排程側控制可排程任務的資源粒度,儘量排程對記憶體資源需求較小的任務。

透過我們在任務排程側,以及之前甘青提到過的其他措施。混部叢集利用率得到了進一步的提升。

圖片

最後,我向大家同步下,當前我們達成的混部效果

我們目前可供排程的任務接近2萬個,這些任務每天排程的次數已經超過了4萬次。在凌晨的高峰期,我們透過混部,能為離線任務額外增加2萬核、50TB記憶體的計算資源。這個收益是相當可觀的,我們也希望在未來的2到3年,將可排程的任務規模提升到6萬個,彈性資源能夠為離線計算總資源貢獻20%的份額。

透過繼續深度推進在離線混部技術,我們期望能夠為vivo增效降本工作持續地貢獻力量。

以上就是本次分享的全部內容。

相關文章