Hadoop YARN:排程效能最佳化實踐

美團技術團隊發表於2022-12-05

總第345篇

2019年 第23篇

背景

YARN作為Hadoop的資源管理系統,負責Hadoop叢集上計算資源的管理和作業排程。

美團的YARN以社群2.7.1版本為基礎構建分支。目前在YARN上支撐離線業務、實時業務以及機器學習業務。

  • 離線業務主要執行的是Hive on MapReduce, Spark SQL為主的資料倉儲作業。

  • 實時業務主要執行Spark Streaming,Flink為主的實時流計算作業。

  • 機器學習業務主要執行TensorFlow,MXNet,MLX(美團點評自研的大規模機器學習系統)等計算作業。

YARN面臨高可用、擴充套件性、穩定性的問題很多。其中擴充套件性上遇到最嚴重的是叢集和業務規模增長帶來的排程器效能問題。從業務角度來看,假設叢集1000臺節點,每個節點提供100個CPU的計算能力。每個任務使用1個CPU,平均執行時間1分鐘。叢集在高峰期始終有超過10萬CPU的資源需求。叢集的排程器平均每分鐘只能排程5萬的任務。從分鐘級別觀察,叢集資源使用率是50000/(100*1000)=0.5,那麼叢集就有50%的計算資源因為排程能力的問題而無法使用。

隨著叢集規模擴大以及業務量的增長,叢集排程能力會隨著壓力增加而逐漸下降。假設排程能力依然保持不變,每分鐘排程5萬個任務,按照5000臺節點的規模計算,如果不做任何最佳化改進,那麼叢集資源使用率為:50000/(100*5000) = 10%,剩餘90%的機器資源便無法被利用起來。

這個問題解決後,叢集在有空餘資源的情況下,作業資源需求可以快速得到滿足,叢集的計算資源得到充分地利用。

下文會逐步將Hadoop YARN排程系統的核心模組展開說明,揭開上述效能問題的根本原因,提出系統化的解決方案,最終Hadoop YARN達到支撐單叢集萬級別節點,支援併發執行數萬作業的排程能力。

整體架構

YARN架構

YARN負責作業資源排程,在叢集中找到滿足業務的資源,幫助作業啟動任務,管理作業的生命週期。

YARN詳細的架構設計請參考Hadoop官方文件

資源抽象

YARN在CPU,Memory這兩個資源維度對叢集資源做了抽象。

class Resource{
  int cpu;   //cpu核心個數
  int memory-mb; //記憶體的MB數
}

作業向YARN申請資源的請求是:List[ResourceRequest]

class ResourceRequest{
  int numContainers; //需要的container個數
  Resource capability;//每個container的資源
}

YARN對作業響應是:List[Container]

class Container{
  ContainerId containerId; //YARN全域性唯一的container標示
  Resource capability;  //該container的資源資訊
  String nodeHttpAddress; //該container可以啟動的NodeManager的hostname
}

YARN排程架構

Hadoop YARN:排程效能最佳化實踐

名詞解釋

  • ResourceScheduler是YARN的排程器,負責Container的分配。

  • AsyncDispatcher是單執行緒的事件分發器,負責向排程器傳送排程事件。

  • ResourceTrackerService是資源跟蹤服務,主要負責接收處理NodeManager的心跳資訊。

  • ApplicationMasterService是作業的RPC服務,主要負責接收處理作業的心跳資訊。

  • AppMaster是作業的程式控制器,負責跟YARN互動獲取/釋放資源。

排程流程

  1. 作業資源申請過程:AppMaster透過心跳告知YARN資源需求(List[ResourceRequest]),並取回上次心跳之後,排程器已經分配好的資源(List[Container])。

  2. 排程器分配資源流程是:Nodemanager心跳觸發排程器為該NodeManager分配Container。

資源申請和分配是非同步進行的。ResourceScheduler是抽象類,需要自行實現。社群實現了公平排程器(FairScheduler)和容量排程器(CapacityScheduler)。美團點評根據自身業務模式的特點,採用的是公平排程器。

公平排程器

作業的組織方式

在公平排程器中,作業(App)是掛載如下圖的樹形佇列的葉子。

Hadoop YARN:排程效能最佳化實踐

核心排程流程

Hadoop YARN:排程效能最佳化實踐

核心排程流程

  1. 排程器鎖住FairScheduler物件,避免核心資料結構衝突。

  2. 排程器選取叢集的一個節點(Node),從樹形佇列的根節點ROOT開始出發,每層佇列都會按照公平策略選擇一個子佇列,最後在葉子佇列按照公平策略選擇一個App,為這個App在Node上找一塊適配的資源。

對於每層佇列進行如下流程:

  1. 佇列預先檢查:檢查佇列的資源使用量是否已經超過了佇列的Quota。

  2. 排序子佇列/App:按照公平排程策略,對子佇列/App進行排序。

  3. 遞迴排程子佇列/App。

例如,某次排程的路徑是ROOT ->  ParentQueueA -> LeafQueueA1 -> App11,這次排程會從Node上給App11分配Container。

虛擬碼

class FairScheduler{
  /* input:NodeId
   *  output:Resource 表示分配出來的某個app的一個container的資源量
   *  root 是樹形佇列Queue的根
   */

  synchronized Resource attemptScheduling(NodeId node){
    root.assignContainer(NodeId); 
  }
}

class Queue{
  Resource assignContainer(NodeId node){
    if(! preCheck(node) ) return;  //預先檢查
      sort(this.children);  //排序
    if(this.isParent){
      for(Queue q: this.children)
        q.assignContainer(node);  //遞迴呼叫
    }else{
      for(App app: this.runnableApps)
        app.assignContainer(node); 
    }
  }
}

class App{
  Resource assignContainer(NodeId node){
    ......
  }
}

公平排程器架構

公平排程器是一個多執行緒非同步協作的架構,而為了保證排程過程中資料的一致性,在主要的流程中加入了FairScheduler物件鎖。其中核心排程流程是單執行緒執行的。這意味著Container分配是序列的,這是排程器存在效能瓶頸的核心原因。

Hadoop YARN:排程效能最佳化實踐

公平排程器架構

  • Scheduler Lock:FairScheduler物件鎖。

  • AllocationFileLoaderService:負責公平策略配置檔案的熱載入,更新佇列資料結構。

  • Continuous Scheduling Thread:核心排程執行緒,不停地執行上節的核心排程流程。

  • Update Thread:更新佇列資源需求,執行Container搶佔流程等。

  • Scheduler Event Dispatcher Thread: 排程器事件的處理器,處理App新增,App結束,Node新增,Node移除等事件。

效能評估

上文介紹了公平排程器的架構,在大規模的業務壓力下,這個系統存在效能問題。從應用層的表現看,作業資源需求得不到滿足。從系統模組看,多個模組協同工作,每個模組多多少少都存在效能問題。如何評估系統效能已經可以滿足線上業務的需求?如何評估系統的業務承載能力?我們需要找到一個系統的效能目標。因此在談效能最佳化方案之前,需要先說一說排程系統效能評估方法。

一般來說,線上業務系統的效能是用該系統能夠承載的QPS和響應的TP99延遲時間來評估,而排程系統與線上業務系統不同的是:排程系統的效能不能用RPC(ResourceManager接收NodeManager和AppMaster的RPC請求)的響應延遲來評估。原因是:這些RPC呼叫過程跟排程系統的排程過程是非同步的,因此不論排程效能多麼差,RPC響應幾乎不受影響。同理,不論RPC響應多麼差,排程效能也幾乎不受影響。

業務指標:有效排程

首先從滿足業務需求角度分析排程系統的業務指標。排程系統的業務目標是滿足業務資源需求。指標是:有效排程(validSchedule)。在生產環境,只要validSchedule達標,我們就認為目前排程器是滿足線上業務需求的。

定義validSchedulePerMin表示某一分鐘的排程效能達標的情況。達標值為1,不達標值為0。

validPending = min(queuePending, QueueMaxQuota)
if  (usage / total  > 90% || validPending == 0):   validSchedulePerMin = 1 //叢集資源使用率高於90%,或者叢集有效資源需求為0,這時排程器效能達標。
if (validPending > 0 &&  usage / total < 90%) : validSchedulePerMin = 0;//叢集資源使用率低於90%,並且叢集存在有效資源需求,這時排程器效能不達標。
  • validPending表示叢集中作業有效的資源需求量。

  • queuePending表示佇列中所有作業的資源需求量。

  • QueueMaxQuota表示該佇列資源最大限額。

  • Usage表示叢集已經使用的資源量。

  • Total表示叢集總體資源。

設定90%的原因是:資源池中的每個節點可能都有一小部分資源因為無法滿足任何的資源需求,出現的資源碎片問題。這個問題類似Linux記憶體的碎片問題。由於離線作業的任務執行時間非常短,資源很快可以得到回收。在離線計算場景,排程效率的重要性遠遠大於更精確地管理叢集資源碎片,因此離線排程策略暫時沒有考慮資源碎片的問題。

validSchedulePerDay表示排程效能每天的達標率。
validSchedulePerDay =  ΣvalidSchedulePerMin /1440  

目前線上業務規模下,業務指標如下:
validSchedulePerMin > 0.9; validSchedulePerDay > 0.99

系統效能指標:每秒排程Container數

排程系統的本質是為作業分配Container,因此提出排程系統效能指標CPS--每秒排程Container數。

在生產環境,只要validSchedule達標,表明目前排程器是滿足線上業務需求的。而在測試環境,需要關注不同壓力條件下的CPS,找到當前系統承載能力的上限,並進一步指導效能最佳化工作。

CPS與測試壓力相關,測試壓力越大,CPS可能越低。從上文公平排程器的架構可以看到,CPS跟如下資訊相關:

  • 叢集總體資源數:叢集資源越多,叢集可以併發執行的的Container越多,對排程系統產生越大的排程壓力。目前每臺物理機的CPU、Memory資源量差距不大,因此叢集總體資源數主要看叢集的物理機節點個數。

  • 叢集中正在執行的App數:作業數越多,需要排程的資訊越多,排程壓力越大。

  • 叢集中的佇列個數:佇列數越多,需要排程的資訊越多,排程壓力越大。

  • 叢集中每個任務的執行時間:任務執行時間越短會導致資源釋放越快,那麼動態產生的空閒資源越多,對排程系統產生的壓力越大。

例如,叢集1000個節點,同時執行1000個App,這些App分佈在500個Queue上,每個App的每個Container執行時間是1分鐘。在這樣的壓力條件下,排程系統在有大量資源需求的情況下,每秒可以排程1000個Container。那麼在這個條件下,排程系統的CPS是1000/s。

排程壓力模擬器

線上上環境中,我們可以透過觀察上文提到的排程系統的指標來看當前排程效能是否滿足業務需求。但我們做了一個效能最佳化策略,不能直接到線上上環境去試驗,因此我們必須有能力線上下環境驗證排程器的效能是滿足業務需求的,之後才能把試驗有效的最佳化策略推廣到線上環境。

那我們線上下也搭建一套跟線上規模一樣的叢集,是否就可以進行排程器效能最佳化的分析和研究呢?理論上是可以的,但這需要大量的物理機資源,對公司來說是個巨大的成本。因此我們需要一個排程器的壓力模擬器,在不需要大量物理機資源的條件下,能夠模擬YARN的排程過程。

社群提供了開源排程器的壓力模擬工具——Scheduler Load Simulater(SLS)。

Hadoop YARN:排程效能最佳化實踐

如上圖,左側是開源SLS的架構圖,整體都在一個程式中,ResourceManager模組裡面有一個用執行緒模擬的Scheduler。App和NM(NodeManager)都是由執行緒模擬。作業資源申請和NM節點心跳採用方法呼叫。

開源架構存在的問題有:

  • 模擬大規模APP和NM需要開啟大量的執行緒,導致排程器執行緒和NM/App的模擬執行緒爭搶cpu資源,影響排程器的評估。

  • SLS的Scheduler Wapper中加入了不合理的邏輯,嚴重影響排程器的效能。

  • SLS為了通用性考慮,沒有侵入FairScheduler的排程過程獲取效能指標,僅僅從外圍獲取了Queue資源需求,Queue資源使用量,App資源需求,App資源使用量等指標。這些指標都不是效能指標,無法利用這些指標分析系統效能瓶頸。

針對存在的問題,我們進行了架構改造。右側是改造後的架構圖,從SLS中剝離Scheduler Wapper的模擬邏輯,用真實的ResourceManager代替。SLS僅僅負責模擬作業的資源申請和節點的心跳彙報。ResourceManager是真實的,線上生產環境和線下壓測環境暴露的指標是完全一樣的,因此線上線下可以很直觀地進行指標對比。詳細程式碼參考:YARN-7672

細粒度監控指標

利用排程壓力模擬器進行壓測,觀察到validSchedule不達標,但依然不清楚效能瓶頸到底在哪裡。因此需要細粒度指標來確定效能的瓶頸點。由於排程過程是單執行緒的,因此細粒度指標獲取的手段是侵入FairScheduler,在排程流程中採集關鍵函式每分鐘的時間消耗。目標是找到花費時間佔比最多的函式,從而定位系統瓶頸。例如:在preCheck函式的前後加入時間統計,就可以收集到排程過程中preCheck消耗的時間。

基於以上的思路,我們定義了10多個細粒度指標,比較關鍵的指標有:

  • 每分鐘父佇列preCheck時間。

  • 每分鐘父佇列排序時間。

  • 每分鐘子佇列preCheck時間。

  • 每分鐘子佇列排序時間。

  • 每分鐘為作業分配資源的時間。

  • 每分鐘因為作業無資源需求而花費的時間。

關鍵最佳化點

第一次做壓測,給定的壓力就是當時線上生產環境峰值的壓力情況(1000節點、1000作業併發、500佇列、單Container執行時間40秒)。經過最佳化後,排程器效能提升,滿足業務需求,之後透過預估業務規模增長來調整測試壓力,反覆迭代地進行最佳化工作。

下圖是效能最佳化時間線,縱軸為排程效能CPS。

Hadoop YARN:排程效能最佳化實踐

最佳化排序比較函式

在核心排程流程中,第2步是排序子佇列。觀察細粒度指標,可以很清楚地看到每分鐘排程流程總共用時50秒,其中排序時間佔用了30秒,佔了最大比例,因此首先考慮最佳化排序時間。

排序本身用的快速排序演算法,已經沒有最佳化空間。進一步分析排序比較函式,發現排序比較函式的時間複雜度非常高。

計算複雜度最高的部分是:需要獲取佇列/作業的資源使用情況(resourceUsage)。原演算法中,每2個佇列進行比較,需要獲取resourceUsage的時候,程式都是現場計算。計算方式是遞迴累加該佇列下所有作業的resourceUsage。這造成了巨大的重複計算量。

最佳化策略:將現場計算最佳化為提前計算。

提前計算演算法:當為某個App分配了一個Container(資源量定義為containerResource),那麼遞迴調整父佇列的resourceUsage,讓父佇列的resourceUsage += containerResource。當釋放某個App的一個Container,同樣的道理,讓父佇列resourceUsage -= containerResource。利用提前計算演算法,佇列resourceUsage的統計時間複雜度降低到O(1)。

最佳化效果:排序相關的細粒度指標耗時明顯下降。

Hadoop YARN:排程效能最佳化實踐

紅框中的指標表示每分鐘排程器用來做佇列/作業排序的時間。從圖中可以看出,經過最佳化,排序時間從每分鐘30G(30秒)下降到5G(5秒)以內。詳細程式碼參考:YARN-5969

最佳化作業跳過時間

從上圖看,最佳化排序比較函式後,藍色的線有明顯的增加,從2秒增加到了20秒。這條藍線指標含義是每分鐘排程器跳過沒有資源需求的作業花費的時間。從時間佔比角度來看,目前最佳化目標是減少這條藍線的時間。

分析程式碼發現,所有佇列/作業都會參與排程。但其實很多佇列/作業根本沒有資源需求,並不需要參與排程。因此最佳化策略是:在排序之前,從佇列的Children中剔除掉沒有資源需求的佇列/作業。

最佳化效果:這個指標從20秒下降到幾乎可以忽略不計。詳細程式碼參考:YARN-3547

Hadoop YARN:排程效能最佳化實踐

這時,從上圖中可以明顯看到有一條線呈現上升趨勢,並且這個指標占了整個排程時間的最大比例。這條線對應的指標含義是確定要排程的作業後,排程器為這個作業分配出一個Container花費的時間。這部分邏輯平均執行一次的時間在0.02ms以內,並且不會隨著叢集規模、作業規模的增加而增加,因此暫時不做進一步最佳化。

佇列並行排序最佳化

從核心排程流程可以看出,分配每一個Container,都需要進行佇列的排序。排序的時間會隨著業務規模增加(作業數、佇列數的增加)而線性增加。

架構思考:對於公平排程器來說,排序是為了實現公平的排程策略,但資源需求是時時刻刻變化的,每次變化,都會引起作業資源使用的不公平。即使分配每一個Container時都進行排序,也無法在整個時間軸上達成公平策略。

例如,叢集有10個CPU,T1時刻,叢集只有一個作業App1在執行,申請了10個CPU,那麼叢集會把這10個CPU都分配給App1。T2時刻(T2 > T1,叢集中新來一個作業App2,這時叢集已經沒有資源了,因此無法為App2分配資源。這時叢集中App1和App2對資源的使用是不公平的。從這個例子看,僅僅透過排程的分配演算法是無法在時間軸上實現公平排程。

目前公平排程器的公平策略是保證叢集在某一時刻資源排程的公平。在整個時間軸上是需要搶佔策略來補充達到公平的目標。因此從時間軸的角度考慮,沒有必要在分配每一個Container時都進行排序。

綜上分析,最佳化策略是排序過程與排程過程並行化。要點如下:

  1. 排程過程不再進行排序的步驟。

  2. 獨立的執行緒池處理所有佇列的排序,其中每個執行緒處理一個佇列的排序。

  3. 排序之前,透過深度克隆佇列/作業中用於排序部分的資訊,保證排序過程中佇列/作業的資料結構不變。

Hadoop YARN:排程效能最佳化實踐

最佳化效果如下:

佇列排序效率:利用執行緒池對2000個佇列進行一次排序只需要5毫秒以內(2ms-5ms),在一秒內至少可以完成200次排序,對業務完全沒有影響。

在並行執行1萬作業,叢集1.2萬的節點,佇列個數2000,單Container執行時間40秒的壓力下,排程CPS達到5萬,在一分鐘內可以將整個叢集資源打滿,並持續打滿。

Hadoop YARN:排程效能最佳化實踐
Hadoop YARN:排程效能最佳化實踐
Hadoop YARN:排程效能最佳化實踐

上圖中,15:26分,Pending值是0,表示這時叢集目前所有的資源需求已經被排程完成。15:27分,resourceUsage達到1.0,表示叢集資源使用率為100%,叢集沒有空閒資源。Pending值達到4M(400萬 mb的記憶體需求)是因為沒有空閒資源導致的資源等待。

穩定上線的策略

線下壓測的結果非常好,最終要上到線上才能達成業務目標。然而穩定上線是有難度的,原因:

  • 線上環境和線下壓測環境中的業務差別非常大。線下沒問題,上線不一定沒問題。

  • 當時YARN叢集只有一個,那麼排程器也只有一個,如果排程器出現異常,是整個叢集的災難,導致整個叢集不可用。 

除了常規的單元測試、功能測試、壓力測試、設定報警指標之外,我們根據業務場景提出了針對叢集排程系統的上線策略。

線上回滾策略

離線生產的業務高峰在凌晨,因此凌晨服務出現故障的機率是最大的。而凌晨RD同學接到報警電話,執行通常的服務回滾流程(回滾程式碼,重啟服務)的效率是很低的。並且重啟期間,服務不可用,對業務產生了更長的不可用時間。因此我們針對排程器的每個最佳化策略都有引數配置。只需要修改引數配置,執行配置更新命令,那麼在不重啟服務的情況下,就可以改變排程器的執行邏輯,將執行邏輯切換回最佳化前的流程。

這裡的關鍵問題是:系統透過配置載入執行緒更新了排程器某個引數的值,而排程執行緒也同時在按照這個引數值進行工作。在一次排程過程中可能多次檢視這個引數的值,並且根據引數值來執行相應的邏輯。排程執行緒在一次排程過程中觀察到的引數值發生變化,就會導致系統異常。

處理辦法是透過複製資源的方式,避免多執行緒共享資源引起資料不一致的問題。排程執行緒在每次排程開始階段,先將當前所有效能最佳化引數進行復制,確保在本次排程過程中觀察到的引數不會變更。

資料自動校驗策略

最佳化演算法是為了提升效能,但要注意不能影響演算法的輸出結果,確保演算法正確性。對於複雜的演算法最佳化,確保演算法正確性是一個很有難度的工作。

在“最佳化排序比較時間”的研發中,變更了佇列resourceUsage的計算方法,從現場計算變更為提前計算。那麼如何保證最佳化後演算法計算出來的resourceUsage是正確的呢?

即使做了單元策略,功能測試,壓力測試,但面對一個複雜系統,依然不能有100%的把握。另外,未來系統升級也可能引起這部分功能的Bug。

演算法變更後,如果新的resourceUsage計算錯誤,那麼就會導致排程策略一直錯誤執行下去。從而影響佇列的資源分配。會對業務產生巨大的影響。例如,業務拿不到原本的資源量,導致業務延遲。

透過原先現場計算的方式得到的所有佇列的resourceUsage一定是正確的,定義為oldResourceUsage。演算法最佳化後,透過提前計算的方式得到所有佇列的resourceUsage,定義為newResourceUsage。

在系統中,定期對oldResourceUsage和newResourceUsage進行比較,如果發現資料不一致,說明最佳化的演算法有Bug,newResourceUsage計算錯誤。這時系統會向RD傳送報警通知,同時自動地將所有計算錯誤的資料用正確的資料替換,使得錯誤得到及時自動修正。

總結與未來展望

本文主要介紹了美團點評Hadoop YARN叢集公平排程器的效能最佳化實踐。

  1. 做效能最佳化,首先要定義宏觀的效能指標,從而能夠評估系統的效能。

  2. 定義壓測需要觀察的細粒度指標,才能清晰看到系統的瓶頸。

  3. 工欲善其事,必先利其器。高效的壓力測試工具是效能最佳化必備的利器。

  4. 最佳化演算法的思路主要有:降低演算法時間複雜度;減少重複計算和不必要的計算;並行化。

  5. 效能最佳化是永無止境的,要根據真實業務來合理預估業務壓力,逐步開展效能最佳化的工作。

  6. 程式碼上線需謹慎,做好防禦方案。

單個YARN叢集排程器的效能最佳化總是有限的,目前我們可以支援1萬節點的叢集規模,那麼未來10萬,100萬的節點我們如何應對?

我們的解決思路是:基於社群的思路,設計適合美團點評的業務場景的技術方案。社群Hadoop 3.0研發了Global Scheduling,完全顛覆了目前YARN排程器的架構,可以極大提高單叢集排程效能。我們正在跟進這個Feature。社群的YARN Federation已經逐步完善。該架構可以支撐多個YARN叢集對外提供統一的叢集計算服務,由於每個YARN叢集都有自己的排程器,這相當於橫向擴充套件了排程器的個數,從而提高叢集整體的排程能力。我們基於社群的架構,結合美團點評的業務場景,正在不斷地完善美團點評的YARN Federation。

作者簡介

世龍廷穩美團使用者平臺大資料與演算法部研發工程師。

About團隊

資料平臺資源排程團隊隸屬美團使用者平臺大資料與演算法部,目標是建設超大規模、高效能、支援異構計算資源和多場景的資源排程系統。目前管理的計算節點接近 3 萬臺,在單叢集節點過萬的規模下實現了單日數十萬離線計算作業的高效排程,資源利用率超過 90%。資源排程系統同時實現了對實時計算作業、機器學習模型 Serving 服務等高可用場景的支援,可用性超過 99.9%。系統也提供了對 CPU/GPU 等異構資源的排程支援,實現了數千張 GPU卡的高效排程,以及 CPU 資源的離線與訓練混合排程,目前正在引入 NPU/FPGA 等更多異構資源,針對機器學習場景的特點實現更高效合理的排程策略。

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

相關文章