當前位置︰技術分享 > 技術參考 > 正文

Hadoop YARN︰調度性能優化實踐2019-08-06 10:24:33 | 編輯︰hely | 查看︰ | 評論︰0

背景YARN 作為 Hadoop 的資源管理系統,負責 Hadoop 集群上計算資源的管理和作業調度。美團的 YARN 以社區 2 7 1 版本為基礎構建

背景

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 這兩個資源維度對集群資源做了抽象。

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

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


 

YARN 調度架構

 

 

YARN 調度器

名詞解釋

ResourceScheduler 是 YARN 的調度器,負責 Container 的分配。

AsyncDispatcher 是單線程的事件分發器,負責向調度器發送調度事件。

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

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

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

調度流程

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

調度器分配資源流程是︰Nodemanager 心跳觸發調度器為該 NodeManager 分配 Container。

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

公平調度器

作業的組織方式

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

 

 

隊列結構

核心調度流程

 

 

核心調度流程

調度器鎖住 FairScheduler 對象,避免核心數據結構沖突。

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

對于每層隊列進行如下流程︰

隊列預先檢查︰檢查隊列的資源使用量是否已經超過了隊列的 Quota。

排序子隊列 /App︰按照公平調度策略,對子隊列 /App 進行排序。

遞歸調度子隊列 /App。

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

偽代碼

 

公平調度器是一個多線程異步協作的架構,而為了保證調度過程中數據的一致性,在主要的流程中加入了 FairScheduler 對象鎖。其中核心調度流程是單線程執行的。這意味著 Container 分配是串行的,這是調度器存在性能瓶頸的核心原因。

 

 

公平調度器架構

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 表示集群中作業有效的資源需求量。

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)。

 

 

調度壓力模擬器

如上圖,左側是開源 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。

 

 

性能優化時間線

優化排序比較函數

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

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

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

優化策略︰將現場計算優化為提前計算。

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

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

 

 

優化排序比較函數效果

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

優化作業跳過時間

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

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

優化效果︰這個指標從 20 秒下降到幾乎可以忽略不計。詳細代碼參考︰ YARN-3547

 

 

優化作業跳過時間

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

隊列並行排序優化

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

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

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

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

綜上分析,優化策略是排序過程與調度過程並行化。要點如下︰

調度過程不再進行排序的步驟。

獨立的線程池處理所有隊列的排序,其中每個線程處理一個隊列的排序。

排序之前,通過深度克隆隊列 / 作業中用于排序部分的信息,保證排序過程中隊列 / 作業的數據結構不變。

 

 

並行排序優化

優化效果如下︰

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

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

 

 

上圖中,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 集群公平調度器的性能優化實踐。

做性能優化,首先要定義宏觀的性能指標,從而能夠評估系統的性能。

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

工欲善其事,必先利其器。高效的壓力測試工具是性能優化必備的利器。

優化算法的思路主要有︰降低算法時間復雜度;減少重復計算和不必要的計算;並行化。

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

代碼上線需謹慎,做好防御方案。

單個 YARN 集群調度器的性能優化總是有限的,目前我們可以支持 1 萬節點的集群規模,那麼未來 10 萬,100 萬的節點我們如何應對?

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

作者介紹︰世龍、廷穩,美團用戶平台大數據與算法部研發工程師。

上一篇︰深入理解 Spark SQL 的 Catalyst 優化器 民生銀行數據中台體系的構建與實踐下一篇︰