Yarn FairScheduler 的資源預留機制導致的一次宕機事故分析

概述

我們線上2.7.2版本的Yarn集羣在一個週六的早上開始發生大量任務掛起。由於週末在家,未能及時發現。5個小時以後同事手動kill掉部分應用,系統立刻恢復正常應用。由於急於立刻恢復應用,因此未能保存當時yarn的應用堆棧和內存dump,只有INFO級別的日誌文件可供排查原因。我們通過hadoop社區與hadoop的開發者交流,同時根據日誌、結合代碼,最終確認問題原因來自於yarn的資源預留特性。本文一方面描述我們線上集羣發生問題的現象、日誌以及我們的解決方案,同時,我們從代碼層面去分析yarn的資源預留機制的實現原理;

線上事故描述、日誌分析和解決方案

事故現象描述

我們的yarn監控報警系統會每分鐘通過yarn的Rest API請求它的隊列狀態並保存爲日誌,同時,如果發現隊列發生嚴重的應用堆積將報警。通過分析yarn本身的ResourceManager日誌和我們的監控系統日誌,我們發現,集羣從上午09:00開始出現問題,直到下午14:00手動kill掉部分應用集羣恢復運行,發生以下主要現象:

  1. 全集羣範圍應用堆積:系統逐漸發生資源堆積,而且,應用堆積發生在所有隊列,即,不是某一個隊列的資源緊張問題;
  2. 集羣可用資源充裕:全集羣應用堆積發生過程中,集羣整體資源內存佔用率爲60%(650G/1100G);
  3. 集羣資源完全靜止:事故發生以後,我們的監控日誌顯示,yarn的資源佔用率完全不變了,即,沒有任何資源的釋放和資源的申請。觀察事故發生以後的yarn ResourceManager日誌,發現除了有新應用提交的日誌,沒有應用運行、資源釋放的任何日誌;
  4. ResourceManager虛擬機沒有Full GC:由於是整個集羣範圍內的應用堆積,我們懷疑ResourceManager可能會發生了長時間的GC導致虛擬機停頓。因此使用gcutil,發現gc正常。同時,部分現象也與Full GC的猜測不符合,比如,Full GC只會導致資源調度效率降低,但是不會導致停止資源調度。同時,所有的NodeManager與ResourceManager通信正常,這說明ResourceManager可以及時處理NodeManager心跳等請求,另外,故障期間新的應用能夠成功提交,只是一直處在accepted 狀態,無法變爲RUNNING .
  5. kill掉少量應用以後系統立刻恢復:故障過程中,yarn集羣持續保持60%的資源佔用率不變,所有隊列均有剩餘資源,沒有任何container的產生和釋放。當kill掉少量應用以後,5個小時內積累的大量的pending應用開始正常競爭系統可用資源,集羣的空閒資源立刻被充分利用,直到這些應用運行完畢。
  6. 故障期間應用幾乎沒有日誌:查看發生故障期間正在運行、提交卻等待運行的應用日誌,我們發現,故障發生的5個小時期間,應用幾乎沒有任何日誌。在此期間yarn的日誌也僅僅有新應用提交相關日誌,資源的分配、釋放從日誌層面看在故障期間幾乎沒發生。

如果從資源調度角度考慮,yarn的資源分配會讓任何一個應用申請的資源都不會超過其所在隊列的資源限制,即,如果是某一個應用導致的問題,那麼絕對不可能導致整個集羣應用均發生pending。因此,初步懷疑是Yarn ResourceManager 本身的問題,這個問題可能是由於錯誤的RM配置導致的。

我們從jira上試圖尋找問題答案,找到與我們遇到的問題類似的一些issue,最終,我把可能的原因開始縮小到yarn的資源預留上。

  1. YARN-4618:這裏提到的問題,發生在系統有大量的pending的container在等待資源,因此計算系統總的pending 資源的時候發生了整型溢出,從而永遠不再進行資源分配,pending container永遠無法運行;我們的集羣是由12臺配置爲(128G,32vCores)的節點組成,不存在這種大量存在的container的現象,因此排除;
  2. YARN-4270:提出了對資源預留的一個improvement,目的是防止集羣的全部節點的資源全部預留導致整個集羣的資源不可用,因此添加了改進方案,任何時候只能允許集羣一定比例的節點被預留,默認是1;
  3. YARN-4477:它指出YARN-4270中的改進方案本身帶來了新的bug,會導致yarn的FairScheduler進行調度的時候陷入死循環無法退出,因此自然所有後續調度都無法進行了

我通過直接google首先看到的是YARN-4618,在確認與我遇到的問題不匹配。沒辦法,只好在官方jira上反饋了我的問題,在回覆裏別人懷疑我遇到的問題可能是YARN-4477提到的問題。但是我很快發現,YARN-4477中的問題是2.8.0版本的hadoop的問題,是由於YARN-4270對資源預留的改進方案導致的,而我的hadoop版本是2.7.2,不存在這個改進方案。因此,我開始將注意力轉向YARN-4270,也就是說,是否我遇到的問題剛好就是YARN-4270提到了這個問題,即系統全部節點被預留導致即使集羣有空閒資源,新的應用也無法使用,即應用被餓死的情況。

重新回到Yarn ResourceManager的日誌上。

日誌分析

通過搜索Yarn ResourceManager中的reserve關鍵字,得到以下日誌:

2017-11-11 09:00:30,343 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.106 app_id=application_1507795051888_183354
2017-11-11 09:00:30,346 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.105 app_id=application_1507795051888_183354
2017-11-11 09:00:30,401 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.84 app_id=application_1507795051888_183354
2017-11-11 09:00:30,412 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.85 app_id=application_1507795051888_183354
2017-11-11 09:00:30,535 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.102 app_id=application_1507795051888_183354
2017-11-11 09:00:30,687 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.86 app_id=application_1507795051888_183354
2017-11-11 09:00:30,824 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.108 app_id=application_1507795051888_183354
2017-11-11 09:00:30,865 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.104 app_id=application_1507795051888_183354
2017-11-11 09:00:30,991 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.103 app_id=application_1507795051888_183354
2017-11-11 09:00:31,232 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.107 app_id=application_1507795051888_183354
2017-11-11 09:00:31,249 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.101 app_id=application_1507795051888_183354
2017-11-11 09:00:34,547 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.100 app_id=application_1507795051888_183358
2017-11-11 09:01:06,277 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.100 app_id=application_1507795051888_183342
2017-11-11 09:01:16,525 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.100 app_id=application_1507795051888_183342
2017-11-11 09:01:25,348 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.100 app_id=application_1507795051888_183342
2017-11-11 09:01:28,351 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.100 app_id=application_1507795051888_183342
2017-11-11 09:02:29,658 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.100 app_id=application_1507795051888_183342
2017-11-11 09:04:14,788 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.100 app_id=application_1507795051888_183376
2017-11-11 09:04:26,307 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.100 app_id=application_1507795051888_183380
2017-11-11 09:04:51,200 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FSAppAttempt: Making reservation: node=10.120.117.100 app_id=application_1507795051888_183383

從日誌上可以看到,在事故發生以前的幾分鐘內,我們由12臺服務器組成的集羣,全部被預定,其中,有11臺機器被application_1507795051888_183354這個應用所預定預定,剩下的一臺10.120.117.100服務器被另外幾個應用多次預定,最終,所有服務器全部被預定,我們集羣的每一臺服務器在滿足當前被預定的應用的運行條件以前,不再分配資源給任何其它的應用或者新提交的應用。這樣,我們整個yarn集羣陷入無限等待的死鎖狀態,即,在集羣尚有剩餘資源的情況下,由於當前還沒有任何一臺機器能夠運行某個大應用的container,因此yarn對這臺服務器的資源進行只釋放,不分配的操作,以期待最終這臺服務器能夠容納這個大應用的container運行,但是可惜大應用的資源始終無法得不到滿足,新提交的應用也不再被分配資源,系統資源分配停止。

我們最終找到了application_1507795051888_183354這個應用的來源,這是一個我們配置的crontab任務,該crontab會在每週六9:00提交一個pyspark應用到yarn上執行,我們直接看到了這個提交應用的參數:

from pyspark.sql import HiveContext

conf = SparkConf()\
.set("spark.driver.maxResultSize", "2g")\
.set('spark.driver.memory', '64g')\
.set('spark.executor.memory','64g')\
.set('spark.driver.cores','4')

無論是driver還是executor,都設置爲64g,同時,我們每一臺NodeManager的相關配置如下:

        <property>
                <name>yarn.nodemanager.resource.memory-mb</name>
                <value>102400</value>
        </property>

         <property>
                <name>yarn.scheduler.maximum-allocation-mb</name>
                <value>64000</value>
        </property>

yarn.nodemanager.resource.memory-mb配置了每臺服務器提供給yarn的內存資源,而yarn.scheduler.maximum-allocation-mb配置了單個Container的最大資源量。由於pyspark設置的executor和driver memory都在yarn.scheduler.maximum-allocation-mb範圍之內,因此,yarn在運行隊列爲executor創建的container將是64g,由於集羣每一臺服務器的剩餘資源都不足64g,因此這個spark應用的任何一個driver和executor都無法運行,因此進行預訂,最終將我們的集羣全部預定完,集羣陷入死鎖。

簡單解決方案

對於2.7.2版本的hadoop,還沒有實現YARN-4270提出的防止集羣全部被預留的improvement,因此,爲了避免這樣的情況發生,我們至少可以進行以下工作:

1.選擇關閉資源預留機制;

2.最大container的大小設置得越大,某個container預定但永遠無法得到執行的概率就越大,因此,我們應該根據我們單臺集羣的資源容量以及我們集羣平時的運行負載,將yarn.scheduler.maximum-allocation-mb設置爲一個較小的合理值,防止資源預留導致的死鎖發生。

3.當然,在資源提交這一層,我們也必須做好提交審查。導致問題的這段python代碼是數據挖掘工程師寫的,他們對集羣的資源管理不是很瞭解,對分佈式計算也不甚清楚,自然很難設置合適的提交參數,更不用說在應用運行起來以後進行調優了。
4.如果服務器的物理資源可以控制,那麼單臺服務器的內存和CPU與container的最大允許值相比應該足夠大,比如,如果集羣每臺服務器運行時負載都在50%以上,最大Container是92G,而每臺服務器的物理內存是100G,那麼服務器被預定就成爲大概率事件,而同樣的平均負載和最大Container值,如果我們每臺服務器的物理內存是200G,服務器被預留的概率則會大大降低,我在上文中討論的服務器全部節點都被預定的情況也就幾乎不會發生了。

Yarn資源預留機制原理解析

資源預留概述

資源預留髮生在應用向Yarn進行資源申請的時候。yarn的資源預留機制是一種資源保證機制,它發生在應用程序申請的資源無法得到滿足的時候。即,當應用程序申請的資源此刻無法滿足但是未來只要有一定的資源釋放就可以得到滿足的情況下,yarn會面臨兩種選擇:

  1. 優先爲這個應用程序預留一個節點上的資源,直到累計釋放的空閒資源滿足了應用的需求。這種資源的分配方式叫做增量資源分配,即Incremental placement;

  2. 暫時放棄當前節點資源,直到某個節點的資源一次性滿足應用程序的需求。這種分配方式叫做一次性資源分配,即all-or-nothing;

兩種分配方式各有優缺點,對於增量資源分配而言,資源預留機制會導致資源浪費,集羣資源利用率低,而一次性資源分配雖然在發現資源無法滿足某個應用需求的時候及時放棄,但是,這個應用有可能永遠得不到自己請求的資源因而永遠無法運行,即餓死現象

因此,yarn最終還是採用了增量資源分配機制,儘管會造成一定的資源浪費,但是不會出現餓死現象,大小應用都有平等的運行機會;

Yarn的一次資源預留,精確來講,是將 某一個container某個服務器節點上 做資源預留;它發生在服務器節點的可用資源無法滿足container所需要的資源的情況下。資源預留髮生在Yarn對應的調度器爲應用的資源請求進行分配的過程中,如果大家對Yarn的資源分配原理和機制感興趣,可以參考我的博客:《Yarn資源請求處理和資源分配原理解析》,下文不再對Yarn資源分配的過程和原理做講解。

注意,請將集羣節點資源和隊列資源區分開。我們通過隊列的配置文件fair-scheduler.xml/capacity-scheduler.xml定義了資源隊列,這個隊列的資源實際上是對應到各個實際的服務器節點上的內存和CPU資源的。比如,一個隊列的容量是100G,代表提交到這個隊列的應用所使用的內存最大不可以超過100G,但是,隊列對於這些應用具體運行在哪些服務器上、在每臺服務器上消耗了多少資源是不限制的,有可能,所有的container都運行在了某一臺服務器上,佔用了這臺服務區100G資源,或者,這些應用運行在了10臺服務器上,消耗每臺服務器10G資源。

任何一個Yarn應用都提交的container請求如果超過了所在隊列的剩餘可用資源,這個container是不會被分配也不會被預留的,只有當這個container請求滿足其所在隊列的剩餘資源,卻無法在某個節點上運行,纔會發生資源預留。

比如,我們的隊列配置是這樣的:

這裏寫圖片描述

簡單起見我們只考慮內存,忽視cpu資源。

爲滿足預留條件的Container進行資源預留

如果我們發現當前服務器節點的剩餘資源無法滿足這個container的資源請求,這時候就需要爲這個container進行資源預留。注意,只有當這個container的資源請求滿足隊列的剩餘可用資源,即,所在隊列可用資源能夠運行這個container,但是,當前服務器節點的剩餘資源卻不夠用來運行這個container。

因此,如果這個container直接超過了隊列的剩餘資源,是不會進行資源預留的,直接就分配失敗了

當yarn進行資源分配的時候,如果某個請求滿足預留條件,就會調用FSAppAttempt.reserve()爲這個container在這個服務器上進行預留:

  private void reserve(Priority priority, FSSchedulerNode node,
      Container container, boolean alreadyReserved) {
    //查看FSAppAttempt.assignReservedContainer(),可以看到,對於一個已經進行了reservation的節點,會試圖將這個 reservation變成allocation,
    if (!alreadyReserved) { //如果這不是一個已經reserve過的attempt,即這個attempt從來都沒有做過reservation
      getMetrics().reserveResource(getUser(), container.getResource());
      RMContainer rmContainer =
          super.reserve(node, priority, null, container);
      node.reserveResource(this, priority, rmContainer);//爲這個節點進行reserve操作
    } else { //如果這是已經已經處在預留狀態的container,則繼續保持預留狀態
      RMContainer rmContainer = node.getReservedContainer();
      super.reserve(node, priority, rmContainer, container);
      node.reserveResource(this, priority, rmContainer);//如果已經進行了reserve操作,則這裏相當於更新一下節點的reserve操作
    }
  }

FSAppAttemt是FairScheduler層面的應用抽象,每一個FSAppAttemt對象代表了一個被調度的應用,每一個FSAppAttempt對象存放了對調度器FairScheduler的引用,以及當前自己佔用的資源量(fair share),已經搶佔的資源等等,每一個FSAppAttempt同時都是一個SchedulerApplicationAttempt,SchedulerApplicationAttempt則存放了當前application的所有container的詳細信息(活躍的container的信息、處於預定狀態的container的信息、ApplicationMaster的信息)等等;

因此,如果一個container變爲預定狀態,即一個container在某個服務器節點上進行預留,那麼在應用層面和在服務器節點層面,都會有狀態的改變:

  • 在應用本身層面,SchedulerApplicationAttempt會通過SchedulerApplicationAttempt.reserve()方法更新這個container的狀態、預定信息等:
 public synchronized RMContainer reserve(SchedulerNode node, Priority priority,
      RMContainer rmContainer, Container container) {
    // Create RMContainer if necessary
    if (rmContainer == null) {
      rmContainer = 
          new RMContainerImpl(container, getApplicationAttemptId(), 
              node.getNodeID(), appSchedulingInfo.getUser(), rmContext);
      //將這個預留的container的信息增加到currentReservation中,currentReservation中記錄了當前
      //這個application所有處於預留狀態的資源總量
      Resources.addTo(currentReservation, container.getResource());
      // Reset the re-reservation count
      resetReReservations(priority);
    } else {
      // Note down the re-reservation
      addReReservation(priority);
    }
    //產生一個RMContainerReservedEvent,通知這個處於reserve狀態的container
    rmContainer.handle(new RMContainerReservedEvent(container.getId(), 
        container.getResource(), node.getNodeID(), priority));
    //更新reservedContainers,reservedContainers保存了每一個Priority的預留信息,即預留節點和預留的container之間的對應關係
    Map<NodeId, RMContainer> reservedContainers = 
        this.reservedContainers.get(priority);
    if (reservedContainers == null) {
      reservedContainers = new HashMap<NodeId, RMContainer>();
      this.reservedContainers.put(priority, reservedContainers);
    }
    reservedContainers.put(node.getNodeID(), rmContainer);
    return rmContainer;
  }

在這裏, RMContainer的實現類是RMContainerImpl,每一個RMContainerImpl對象是ResourceManager用來管理的一個container的抽象。RMContainer從創建、到運行到最後運行完畢銷燬,會經歷各種狀態,因此,每一個RMContainer同時也是一個EventHanlder,它維護了一組狀態機,狀態機定義了相應container事件發生的時候響應的處理行爲以及狀態的切換。

關於RM端container的狀態定義和狀態轉換關係,大家可以參考這篇博客:[RMContainer狀態機分析](“http://dongxicheng.org/mapreduce-nextgen/yarnmrv2-resource-manager-rmcontainer-state-machine/“)。

rmContainer.handle(new RMContainerReservedEvent(container.getId(), container.getResource(), node.getNodeID(), priority))生成了一個RMContainerReservedEvent事件交給RMContainer進行處理,RMContainerReservedEvent的事件類型是RMContainerEventType.RESERVED,RMContainer通過預先定義的狀態機,規定了對這個事件的處理邏輯:

    .addTransition(RMContainerState.NEW, RMContainerState.RESERVED,
        RMContainerEventType.RESERVED, new ContainerReservedTransition())
    // Transitions from RESERVED state
    .addTransition(RMContainerState.RESERVED, RMContainerState.RESERVED,
        RMContainerEventType.RESERVED, new ContainerReservedTransition())

可以看到,當發生RMContainerEventType.RESERVED類型的事件,RMContainer的狀態會從NEW狀態轉換成RESERVED狀態(即從新建狀態變成預留狀態),或者從RESERVED狀態變成RESERVED狀態(即一個已經處在預留狀態的container,調度器會不斷嘗試對其進行分配以擺脫預留,當然,如果分配失敗,則還是處於RESERVED狀態),同時,通過ContainerReservedTransition()這個狀態轉換器進行這個事件的處理邏輯:

  private static final class ContainerReservedTransition extends
  BaseTransition {

    @Override
    public void transition(RMContainerImpl container, RMContainerEvent event) {
      RMContainerReservedEvent e = (RMContainerReservedEvent)event;
      container.reservedResource = e.getReservedResource();
      container.reservedNode = e.getReservedNode();
      container.reservedPriority = e.getReservedPriority();
    }
  }

可以看到,ContainerReservedTransition()轉換處理器只是更新了當前container的相關預留信息。

  • 在服務器節點層面,如果該服務器節點container預留,會調用FSSchedulerNode.reserveResource()方法來更新這個節點的相關信息。FSSchedulerNode是RM層面對某個節點的抽象,一個FSSchedulerNode對象對應了一個服務器節點:
  @Override
  public synchronized void reserveResource(
      SchedulerApplicationAttempt application, Priority priority,
      RMContainer container) {
    // Check if it's already reserved
    RMContainer reservedContainer = getReservedContainer();
    if (reservedContainer != null) {//如果這個節點上已經有reservation
      // Sanity check
      if (!container.getContainer().getNodeId().equals(getNodeID())) {//如果這個container試圖再次在這個節點上進行reservation,則拋出異常
        //略
      }
       //一個節點不可以爲超過兩個application進行reservation
      if (!reservedContainer.getContainer().getId().getApplicationAttemptId()
          .equals(container.getContainer().getId().getApplicationAttemptId())) {
        //略
      }
    //略
    } 
    setReservedContainer(container);
    this.reservedAppSchedulable = (FSAppAttempt) application;
  }

從代碼中可以看到,如果節點被預留,會首先檢查這個節點是否已經被預留,因爲yarn規定:一個服務器在同一時間只可以被一個container預留。如果校驗成功,可以預留,則更新節點對象的相關預留信息。

這樣,一個container在某臺服務器節點上進行了預留,並且container的狀態被更新,被預留的服務器節點關於預留的信息被設置。在這個預留取消之前,這個服務器節點上將不會再分配任何新的container。

同時,每一次資源分配,都會檢查這個服務器上的可用資源是否可以滿足被預留的container的資源需求,如果滿足,則結束預留,container從RESERVED狀態變爲ALLOCATED狀態,然後,經過與ApplicationMaster的通信,這個container最終將在這個服務器上得到運行。

結束預留,開始運行

那麼,當一個資源被預留的節點上的部分container運行完畢,資源被釋放,使得它上面之前進行資源預留的container可以運行了,即,一個container的狀態什麼時候可以從預留狀態轉換爲運行狀態呢?對於連續調度和心跳調度,container狀態從RESERVEDALLOCATED的狀態轉換都是通過調用FSAppAttemtp.assignReservedContainer()實現的。assignReservedContainer()只是對已經處於預定狀態的container進行一次嘗試分配,分配可能會失敗,如果失敗,container依然保持爲RESERVED狀態。

我的博客《Yarn資源請求處理和資源分配原理解析》中講解Yarn資源分配的兩種模式,即,(1)FairScheduler使用一個獨立的線程、不需要等待服務器節點通過心跳彙報自身狀態的連續調度方式,以及,(2)只有當服務器通過心跳彙報自身狀態時纔會觸發對這個節點資源的調度的心跳調度方式。兩種調度方式下資源預留的運行邏輯沒有任何不同,只是將一個container變爲預留狀態、或者從預留狀態變爲分配狀態的時機不同:對於連續調度,當調度時機到來,他們都會調用這個被調度的應用的 FSAppAttempt.attemptScheduling(FSSchedulerNode node)方法嘗試進行資源分配,兩種調度方式的判斷邏輯是一致的,即:

  • 如果這個節點不是一個預留的節點,即沒有被container預留,則會進行正常的資源分配;即,從多個請求的FSAppAttempt中通過我們FairScheduler配置的policy,選擇一個FSAppAttempt進行運行;
  • 如果這個節點被某個container所預留,則嘗試將這個已經預留的container在這個節點上進行分配;爲某個處於預留狀態的container進行資源分配是通過調用FSAppAttempt.assignReservedContainer()進行的。顯然,這次分配只會將這個預留的container這它預留的節點上進行嘗試分配,不會嘗試其他節點。
  /**
   * 當一個container已經處於預留狀態,這個方法被調用,用來嘗試對這個處於預留狀態的container進行分配,
   * 即,嘗試將這個container從reserved狀態變成allocation狀態
   * @param node
   *     Node that the application has an existing reservation on
   */
  public Resource assignReservedContainer(FSSchedulerNode node) {
    RMContainer rmContainer = node.getReservedContainer(); //獲取這個處於預留狀態的container
    Priority priority = rmContainer.getReservedPriority();

    // 確認當前這個priority還有請求的資源,如果沒有了,這個預留也沒必要了,需要取消預留
    if (getTotalRequiredResources(priority) == 0) {
      unreserve(priority, node); //進行unreserve操作
      return Resources.none();
    }

    // Fail early if the reserved container won't fit.
    // Note that we have an assumption here that there's only one container size
    // per priority.
    if (!Resources.fitsIn(node.getReservedContainer().getReservedResource(),
        node.getAvailableResource())) { //如果發現這個節點的可用資源還是滿足不了這個預定的container的資源需求,則此次分配失敗
      return Resources.none();
    }

    //發現節點可用資源已經大於這個container的資源請求,則可以嘗試進行資源預留
    return assignContainer(node, true);
  }

assignReservedContainer()的核心工作,是檢查當前節點的剩餘資源是否依然小於這個預留的container的請求資源,如果是,那麼這次分配直接快速失敗,而如果發現節點的可用資源已經大於container的請求資源,那麼就調用assignContainer(FSSchedulerNode node, boolean reserved)進行分配。

FSAppAttempt.assignContainer(FSSchedulerNode node, boolean reserved)方法進行資源分配是不區分當前container的狀態是RESERVED還是NEW的狀態的,它是對資源請求進行container分配(無論是否已經預定)的方法,使用參數reserved區分是對一個預留的container進行分配還是對一個新的container進行分配。對於處於預留狀態的container,對如果分配成功,Container將從RESERVED 變爲 ALLOCATED狀態,如果分配失敗,則RESERVED可能依然保持爲RESERVED狀態,或者回到NEW狀態,具體的狀態流轉,可以參考下圖:

Container狀態轉換

我在我的博客《Yarn資源請求處理和資源分配原理解析》以FairScheduler爲例詳細介紹了Yarn的資源分配原理,大家可以看到 FSAppAttempt.assignContainer(FSSchedulerNode node, boolean reserved)方法的詳細解析。

總結

Yarn的資源預留機制可能在某些情況下造成集羣的資源死鎖,即,雖然隊列有剩餘資源,但是,由於單個container請求的資源量較大,所有服務器的剩餘資源都無法滿足其運行需求,因此發生預留。如果這樣的container很多,極端情況下,可能所有服務器都處於預定狀態,造成服務器資源死鎖,Yarn雖然看似狀態正常,但是已經停止服務。這種狀態下,我們分析停止服務過程中的日誌,不會看到任何異常信息,只有分析停止服務前的日誌才能看到原因,即每一個服務器節點都已經被某個container預留,新的 應用提交以後無法申請到資源,而這些預留的container又永遠無法擺脫預留狀態。

Yarn對預留的處理,只是一種標記,即,將某個服務器標記爲被某個container預留。任何一個被某個container預留的服務器,在取消預留或者被預留的container被成功分配之前,這個節點不會爲其它container分配資源,只會一次次嘗試爲這個預留的container分配資源。而正常的沒有被預留的服務器節點,則是根據我們定義的優先級(一個資源隊列節點的孩子節點之間的優先級、同一個隊列節點中應用的優先級、應用內container的優先級)選擇出一個資源請求,在這個節點上分配一個container運行這個資源請求。

總之,當集羣的資源利用率一直處於較高狀態,Yarn的很多邊界情況被觸發的概率會大大增加,集羣會出現較多的奇怪問題,變得非常不穩定。70%或以下左右的平均資源利用率會讓集羣處於比較好的運行狀態。我們的FairScheduler調度器默認使用單一資源調度,進行資源調度的時候不考慮CPU消耗,如果集羣負載較多,會經常發生服務器的CPU負載極高,甚至導致服務器宕機。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章