MapReduce 的工作機制

解剖MapReduce job的運行機制

你可以在一個Job對象上調用submit()方法來運行一個MapReduce Job(或者也可以調用waitForCompletion()方法,如果job還沒有提交,它可以在提交job後等待job的完成),但是這些方法的調用在後臺隱藏了大量的處理細節。

下圖展示了MapReduce Job運行的整個過程:


從較高層次來看,可以分爲5個獨立的實體:

  • 客戶端,提交MapReduce Job。
  • YARN資源管理器,協調分配集羣計算資源。
  • YARN node manager,啓動和監控集羣節點上的計算容器(container)。
  • MapReduce application master,主要負責協調MapReduce Job的task的運行。application master和MapReduce task運行在container中,並且受resource manager的調度和node manager的管理。
  • 分佈式文件系統(通常是HDFS),主要在各實體間共享Job文件。
Job Submission

在Job對象上調用submit()方法會創建一個內部的JobSubmitter的實例,並調用它的submitJobInternal()方法(圖中第1步),提交Job後,waitForCompletion()方法會每秒一次檢查job的處理進度,並且如果自上次的報告有變更,它會報告進度給控制檯。此外,導致Job失敗的錯誤也會被記錄到控制檯。

Job的提交過程是由JobSubmitter實現的,主要執行了以下操作:

  • 請求resource manager提供一個應用ID(application ID),作爲MapReduce Job的ID(第2步)。
  • 檢查Job的輸出規範。例如,如果輸出目錄沒有指定或者已經存在,則該Job不能被提交,並且會向MapReduce程序拋出一個錯誤。
  • 計算Job的輸入切片。如果切片不能被計算(比如,因爲輸入路徑不存在),則該Job不能被提交,並且會向MapReduce程序拋出一個錯誤。
  • 複製運行Job需要的資源,包括jar文件、配置文件和計算好的輸入切片,到共享文件系統的一個以Job ID命名的目錄裏(第3步)。Job JAR文件的複製具有較高的複製因子(它由the mapreduce.client.submit.file.replication屬性控制,默認爲10),所以,運行Job的task時,在集羣的node manager節點中中會有很多副本可以被訪問。
  • 在resource manager上通過調用submitApplication()方法來提交job。
Job Initialization

當resource manager接收到submitApplication()調用時,它會將請求轉給YARN調度器來處理,調度器分配一個container,然後resource manager在container上啓動application master進程,並被node manager節點管理(第5a和5b步)。

MapReduce Job 的application master是一個Java 應用程序,它的main class 是MRAPPMaster。它初始化job是通過創建若干簿記(bookkeeping)對象來跟蹤job的進度,因爲它會接收到來自task的進度和完成情況的報告(第6步)。下一步,它接收來自共享文件系統的輸入切片(第7步),然後它爲每個輸入切片創建一個map task,以及由mapreduce.job.reduces屬性(或設置job的setNumReduceTasks()方法)決定的若干reduce task,同時爲每個task指定一個ID。

application master 決定如何運行這些task。如果job比較小,application master也許會選擇在同一個JVM中運行這些task。這發生在它判斷在新的container中分配和運行這些task的開銷,相比在一個節點上依次運行它們低於並行運行它們。這樣的job被稱作“超級任務”。

那麼,怎麼判斷是一個小的Job?默認情況下,一個小的Job是低於10個mapper,只有一個reducer,和輸入大小小於HDFS塊的大小(注意:對於job來說,這些值是可以通過設置mapreduce.job.ubertask.maxmaps,apreduce.job.ubertask.maxreduces和mapreduce.job.ubertask.maxbytes這些屬性值改變的),“超級任務”必須通過設置mapreduce.job.ubertask.enable屬性值爲true來顯示啓用的。

最後,在運行任何task之前,application master會調用OutputCommitter.的setupJob()方法。默認爲FileOutputCommitter,它會爲Job創建最終的輸出目錄和task輸出的臨時工作空間。

Task Assignment(任務分配)

如果一個Job沒有以超級任務運行,那麼application master將會爲job的map task和reduce task從resource manager中請求更多的container(第8步)。對map task的請求的優先級要高於對reduce task的請求優先級,因爲所有的map task必須在reduce task的sort 階段(shuffle和sort)開始之前完成。對reduce task的請求需要直到完成5%的map task,才能開始(Reduce low start)。

reduce task可以運行在集羣中的任何地方,但是對於map task有數據本地化的約束,這是調度器努力追求的。在優化的情況下,map task是數據本地化的--也就是說,和運行任務所需的輸入切片是在同一個幾點上的。或者任務是機架本地化:和切片在同一個機架,但不是相同的節點。有些task既不是數據本地化也不是機架本地化,而是從不同的機架上接收數據。對於特定的job,你可以通過查看job的計數器來決定在每一個本地化層級上運行 task的數量。

對resource manager的請求也指定了運行task所需的內存和CPU。默認情況下,每個map和task 任務會被分配1024MB的內存和一個虛擬內核,這些值對於每個job都是可配置的,通過指定mapreduce.map.memory.mb, mapreduce.reduce.memory.mb, mapreduce.map.cpu.vcores 和 mapreduce.reduce.cpu.vcores.屬性值。

Task Execution

一旦一個任務由resource manager的調度器在一個節點的container中爲其分配了資源,那麼application master就可以聯繫node manager來啓動container(第9a和9b步)。task是由一個Java 應用程序YarnChild來執行的,在運行task之前,YarnChild會本地化task所需的資源,包括job的配置文件、jar文件和來自分佈式緩存的任何文件(第10步)。最後,YarnChild運行map或reduce task(第11步)。

YarnChild是運行在一個專用的JVM中,所以在用戶定義的map 函數或 reduce函數(甚至YarnChild)中有任何bug,都不會影響到node manager。

Streaming

Steaming是指使用用戶提供的可執行文件或腳本來運行特定的map和reduce任務的一種技術。

如下圖:


Streaming task使用標準的輸入/輸出和外部的Streaming process(其可以用任何語言編寫)交互。在task運行期間,java 進程傳遞key-value鍵值對作爲外部Streaming proces進程的輸入,然後Streaming process進程運行用戶自定義的map或reduce函數處理輸入,並將處理結果以key-values鍵值對作爲輸出傳遞給java 進程。從node manager的角度來看,Streaming process就像是運行map或reduce代碼的子進程。

Progress and Status Updates(進度和狀態更新)

MapReduce Job是長時間運行的批處理作業,從數十秒到數小時不等。因爲這可能是相當長的一段時間,所以對用戶來說,能夠獲得job的執行進度的反饋是比較重要的。一個Job和其每個task都是有狀態的,包括Job或task的狀態(例如:正在運行、成功完成、失敗等),map和reduce的進度,job計數器的值,和狀態消息或描述(這可以有用戶代碼設置)等。這些狀態的改變發生job的運行過程中,那麼怎麼把狀態的變化信息反饋給客戶端?

當一個task運行時,它會跟蹤其進展情況(即task的完成比例)。對於map task來說,就是已被處理的輸入的比例。對reduce task來說,有點複雜,但是系統扔可以估算出reduce已處理的輸入的比例。它是這樣做的,通過將總的進展分爲三個部分,對應Shuffle的三個階段。例如,如果task已經運行了reducer一半的輸入,那麼task的進度是5/6,因爲它已經完成了copy和sort階段(各1/3)和一半是通過reduce階段(1/6)。

MapReduce Job進度由什麼組成?
Progress(進度)並不總是可以測量的,但儘管如此,它告訴了Hadoop,一個task正在做某事。
進度由以下操作組成:
  • 讀取一個輸入記錄(在map和reduce函數中)
  • 寫一個輸出記錄(在map和reduce函數中)
  • 設置狀態描述(通過Reporter 或者 TaskAttemptContext的setStatus()方法)
  • 增加一個計數器(使用Reporter的 incrCounter() 方法或者 Counter的 increment()方法)
  • 調用Reporter或TaskAttemptContext的progress()方法
task也有一組計數器,用於統計在task運行時發生的各種事件,它可能被內置到框架內,例如統計map輸出的記錄的數量,或者用戶定義。

在map或reducetask運行時,子進程通過一個umbilical interface(臍帶接口)和它的父application master交互。task 報告它的進度和狀態(包括計數器)給application master,它有一個job的聚合視圖,每隔3秒調用一次臍帶接口。

resource manager的web UI 顯示了每個正在運行的應用,每個應用都有一個鏈接到其application master的URI,進一步詳細的展示了MapReduce job的進度。

在Job運行過程中,客戶端會每秒(間隔時間可以通過apreduce.client.progressmonitor.pollinterva屬性值設置l)通過輪詢application master來接收最新的狀態。客戶端也可以調用getStatus()方法獲得JobStatus的實例,它包含了job的狀態信息。

整個過程如下圖所示:


Job Completion

當application master接收到job最後一個task完成的通知,它會改變job的狀態爲“successful”。然後,當Job處於輪詢狀態時,它得知job已經成功完成,它會打印一條消息給用戶,然後從waitForCompletion()方法中返回。這時,Job的統計信息和計數器也會打印到控制檯。

最後,Job完成,application master和task container清除它們的工作狀態(如:刪除中間的輸出),OutputCommitter 調用commitJob()方法提交完成job。該Job的信息會被Job歷史服務器存檔,供以後用戶想查看是使用。



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