MapReduce工作原理詳解(學習筆記)

本文參考以下文章,作出歸納總結,以供日後參閱。
[1] https://blog.csdn.net/zhengwei223/article/details/78304764
[2] https://www.cnblogs.com/sharpxiajun/p/3151395.html
[3] http://www.cnblogs.com/ahu-lichang/p/6665242.html
[4] https://www.cnblogs.com/dream-to-pku/p/7249954.html (hadoop2.0 中的job tracker和task tracker哪裏去了)

一、MapReduce的參與者

     首先講講物理實體,參與mapreduce作業執行涉及4個獨立的實體:

  1. 客戶端(client):編寫mapreduce程序,配置作業,提交作業,這就是程序員完成的工作;
  2. JobTracker:初始化作業,分配作業,與TaskTracker通信,協調整個作業的執行;
  3. TaskTracker:保持與JobTracker的通信,在分配的數據片段上執行Map或Reduce任務,TaskTracker和JobTracker的不同有個很重要的方面,就是在執行任務時候TaskTracker可以有n多個,JobTracker則只會有一個(JobTracker只能有一個就和hdfs裏namenode一樣存在單點故障)
  4. HDFS:保存作業的數據、配置信息等等,最後的結果也是保存在hdfs上面

二、MapReduece的運行概述

     我們通過Client、JobTracker和TaskTracker的角度來分析MapReduce的工作原理:
在這裏插入圖片描述
     首先是客戶端client要編寫好mapreduce程序,配置好mapreduce的作業也就是job,接下來就是啓動job了,啓動job是告知JobTracker上要運行作業,這個時候JobTracker就會返回給客戶端一個新的job任務的ID值,接下來它會做檢查操作,這個檢查就是確定輸出目錄是否存在,如果存在那麼job就不能正常運行下去,JobTracker會拋出錯誤給客戶端,接下來還要檢查輸入目錄是否存在,如果不存在同樣拋出錯誤,如果存在JobTracker會根據輸入計算輸入分片(Input Split),如果分片計算不出來也會拋出錯誤,這些都做好了JobTracker就會配置Job需要的資源了。拿到jobID後,將運行作業所需要的資源文件複製到HDFS上,包括MapReduce程序打包的JAR文件、配置文件和計算所得的輸入分片信息。這些文件都存放在jobTracker專門爲該作業創建的文件夾中,文件夾名爲該作業的Job ID。JAR文件默認會有10個副本(mapred.submit.replication屬性控制);輸入分片信息告訴 JobTracker應該爲這個作業啓動多少個map任務等信息。當資源文件夾創建完畢後,客戶端會提交job告知jobTracker我已將所需資源寫入hdfs上,接下來請你幫我真正去執行job。
     分配好資源後,JobTracker接收提交job請求後就會初始化作業,初始化主要做的是將Job放入一個內部的隊列,等待作業調度器對其進行調度。當作業調度器根據自己的調度算法調度到該作業時,作業調度器會創建一個正在運行的job對象(封裝任務和記錄信息),以便JobTracker跟蹤job的狀態和進程。創建job對象時作業調度器會獲取hdfs文件夾中的輸入分片信息,根據分片信息爲每個input split創建一個map任務,並將map任務分配給tasktracker執行。對於map和reduce任務,tasktracker根據主機核的數量和內存的大小有固定數量的map槽和reduce槽。這裏需要強調的是:map任務不是隨隨便便地分配給某個tasktracker的,這裏涉及到後面要講的數據本地化。
     接下來就是任務分配了,這個時候tasktracker會運行一個簡單的循環機制定期發送心跳給jobtracker,心跳間隔是5秒,程序員可以配置這個時間,心跳就是jobtracker和tasktracker溝通的橋樑,通過心跳,jobtracker可以監控tasktracker是否存活,也可以獲取tasktracker處理的狀態和問題,同時tasktracker也可以通過心跳裏的返回值獲取jobtracker給它的操作指令。tasktracker會獲取運行job所需的資源,比如代碼等,爲真正執行做準備。
     任務分配好後就是執行任務了。在任務執行時候jobtracker可以通過心跳機制監控tasktracker的狀態和進度,同時也能計算出整個job的狀態和進度,而tasktracker也可以本地監控自己的狀態和進度。TaskTracker每隔一段時間會給JobTracker發送一個心跳,告訴JobTracker它依然在運行,同時心跳中還攜帶者很多的信息,比如當前map任務完成的進度等信息。當jobtracker獲得了最後一個完成指定任務的tasktracker操作成功的通知時候,jobtracker會把整個job狀態置爲成功,然後當客戶端查詢job運行狀態時候(注意:這個是異步操作),客戶端會查到job完成的通知的。如果job中途失敗,mapreduce也會有相應機制處理,一般而言如果不是程序員程序本身有bug,mapreduce錯誤處理機制都能保證提交的job能正常完成。

三、詳解MapReduece的運行機制

在這裏插入圖片描述
      文件File:文件要存儲在HDFS中,每個文件切分成多個一定大小(默認64M)的Block(默認3個備份)存儲在多個節點(DataNode)上。文件數據內容:We are studying at school.\n We are studying at school.\n…
      1. 輸入和拆分:不屬於map和reduce的主要過程,但屬於整個計算框架消耗時間的一部分,該部分會爲正式的map過程準備數據。
     分片(split)操作:MapReduce框架使用InputFormat基礎類做map前的預處理,比如驗證輸入的格式是否符合輸入定義;然後,將輸入文件切分爲邏輯上的多個input split,input split是MapReduce對文件進行處理和運算的輸入單位,只是一個邏輯概念。在進行map計算之前,mapreduce會根據輸入文件計算輸入分片(input split),每個輸入分片(input split)針對一個map任務,輸入分片(input split)存儲的並非數據本身,而是一個分片長度和一個記錄數據的位置的數組。split操作知識將源文件的內容分片形成一系列的input split,每個input split中存儲着該分片的數據信息(例如,文件塊信息、起始位置、數據長度、所在節點列表…),並不是對文件實際分割成多個小文件,每個input split都由一個map任務進行後續處理。
     輸入分片(input split)往往和hdfs的block(塊)關係很密切,假如我們設定hdfs的塊的大小是64mb,如果我們輸入有三個文件,大小分別是3mb、65mb和127mb,那麼mapreduce會把3mb文件分爲一個輸入分片(input split),65mb則是兩個輸入分片(input split)而127mb也是兩個輸入分片(input split),換句話說我們如果在map計算前做輸入分片調整,例如合併小文件,那麼就會有5個map任務將執行,而且每個map執行的數據大小不均,這個也是mapreduce優化計算的一個關鍵點。
     實際上每個input split包含後一個Block中開頭部分的數據(解決記錄跨Block問題)。比如記錄“we are studying at school.\n”跨越存儲在兩個Block中,那麼這條記錄屬於前一個Block對應的input split。這裏以“\n”分割每條記錄,以空格區分一個目標單詞。
     數據格式化(Format)操作:將劃分好的input split格式化成鍵值對形式的數據。其中key爲偏移量,value爲每一行的內容。值得注意的是,在map任務執行過程中,會不停地執行數據格式化操作,每生成一個鍵值對就會將其傳入map進行處理。所以map和數據格式化操作並不存在前後時間差,而是同時進行的。這裏具體涉及到RecordReader類,其作用是從分片中每讀取一條記錄,就調用一次map函數。因爲input split是邏輯切分而非物理切分,所以還需通過RecordReader根據input split中的信息來處理input split中的具體記錄,加載數據並轉換爲適合map任務讀取的鍵值對,輸入給map任務。例如記錄“we are studying at school.”作爲參數v,調用map(v),然後繼續這個過程,讀取下一條記錄直到input split尾部。
在這裏插入圖片描述       2. map階段: 就是程序員編寫好的map函數了,因此map函數效率相對好控制,而且一般map操作都是本地化操作也就是在數據存儲節點上進行。在HDFS 中,文件數據是被複制多份的,所以計算將會選擇擁有此數據的最空閒的節點。比如記錄“we are studying at school”,調用執行一次map(“we are studying at school”),在內存中增加數據:{“we”:1},{“are”:1},{“studying”:1},{“at”:1},{“school”:1}。
      3. shuffle階段:Shuffle 過程是指map產生的直接輸出結果,經過一系列的處理,成爲最終的reduce直接輸入的數據爲止的整個過程。這是mapreduce 的核心過程。該過程可以分爲兩個階段:
      3.1 map端的shuffle:由map處理後的結果並不會直接寫入到磁盤中,而是會在內存裏開啓一個環形內存緩衝區,先將map的處理結果寫入緩衝區中,這個緩衝區默認大小是100mb,並且在配置文件裏爲這個緩衝區設定了一個閥值,默認是0.80(這個大小和閥值都是可以在配置文件裏進行配置的),同時map還會爲輸出操作啓動一個守護線程,如果緩衝區的內存達到了閥值的80%時候,這個守護線程就會把內容寫到磁盤上,這個過程叫spill,另外的20%內存可以繼續寫入要寫進磁盤的數據,寫入磁盤和寫入內存操作是互不干擾的,如果緩存區被撐滿了,那麼map就會阻塞寫入內存的操作,讓寫入磁盤操作完成後再繼續執行寫入內存操作。每次spill操作也就是寫入磁盤操作時候就會寫一個溢出文件,也就是說在做map輸出有幾次spill就會產生多少個溢出文件,隨着數據不斷讀入內存緩衝區,會溢出產生多個小文件,等map輸出全部做完後,map會合並這些輸出文件成一個大文件(這個合併用到歸併排序)。同時,在數據溢出轉儲到磁盤這一過程是複雜的,並不是直接寫入磁盤,而是在寫入磁盤前map會對數據執行分區partition,排序sort,合併combiner等操作。
      3.1.1 分區partition:在數據寫入內存時,決定數據由哪個reduce處理,從而分區。比如採用Hash法,有n個reducer,那麼數據{“are”,1}的key"are"對n進行取模,返回m,從而生成{partition,key,value}。其實Partitioner操作和map階段的輸入分片(Input split)很像,一個Partitioner對應一個reduce作業,如果我們mapreduce操作只有一個reduce操作,那麼Partitioner就只有一個,如果我們有多個reduce操作,那麼Partitioner對應的就會有多個,Partitioner因此就是reduce的輸入分片。注意,每個map的處理結果和partition處理的key value鍵值對結果都保存在緩存MemoryBuffer中。緩衝區中的數據:partition key value三元組數據:{“1”,“are”:1},{“2”,“at”:1},{“1”,“we”:1}。
      3.1.2 排序sort: 在溢出的數據寫入磁盤前會對數據按照key進行排序操作,默認算法爲快速排序,第一關鍵字爲分區號,第二關鍵字爲key。這個是在寫入磁盤操作時候進行,不是在寫入內存時候進行的。例如緩衝區數據{“1”,“are”:1},{“2”,“at”:1}…{“1”,“are”:1},{“1”,“we”:1}排序後爲{“1”,“are”:1}{“1”,“are”:1}{“1”,“we”:1}…{“2”,“at”:1}。


在這裏插入圖片描述

      3.1.3 合併combiner:combiner階段是程序員可以選擇的。數據合併,在reduce計算前,對相同的key的數據,value值合併,減少輸出傳輸量,Combiner函數事實上是本地化的reducer函數。但是combiner操作是有風險的,使用它的原則是combiner的輸入不會影響到reduce計算的最終輸入,例如:如果計算只是求總數,最大值,最小值可以使用combiner,但是做平均值或求中值計算使用combiner的話,最終的reduce計算結果就會出錯。


在這裏插入圖片描述

      3.1.4 歸併merge:每次溢寫會生成一個溢寫文件,這些溢寫文件最終需要被歸併成一個大文件。歸併的意思:生成key和對應的value-list。 在Map任務全部結束之前進行歸併,歸併得到一個大的文件,放在本地磁盤。
     合併(Combine)和歸併(Merge)的區別:
     兩個鍵值對<“a”,1>和<“a”,1>,如果合併,會得到<“a”,2>,如果歸併,會得到<“a”,<1,1>>。
在這裏插入圖片描述
     3.2 reduce端的shuffle:由於map和reduce往往不在同一個節點上運行,所以reduce需要從多個節點上下載map的結果數據,多個節點的map裏相同分區內的數據被複制到同一個reduce上,並對這些數據進行處理,然後才能作爲reduce的輸入數據被reduce處理。
在這裏插入圖片描述在這裏插入圖片描述
     3.2.1 Copy階段:reduce端可能從n個map的結果中獲取數據,而這些map的執行速度不盡相同,當其中一個map運行結束時,reduce就會從JobTracker中獲取該信息。map運行結束後TaskTracker會得到消息,進而將消息彙報給 JobTracker,reduce定時從JobTracker獲取該信息,reduce端默認有5個數據複製線程從map端複製數據。Reduce任務通過RPC向JobTracker詢問Map任務是否已經完成,若完成,則複製數據。
     3.2.2 Sort階段:
     Reduce複製數據先放入緩存,來自不同Map機器,與map一樣,內存緩衝區滿時,也通過sort和combiner,將數據溢寫到磁盤文件中。如果形成了多個磁盤文件還會進行merge歸併,最後一次歸併的結果作爲reduce的輸入而不是寫入到磁盤中。文件中的鍵值對是排序的。
     當數據很少時,不需要溢寫到磁盤,直接在緩存中歸併,然後輸出給Reduce。注意:當Reducer的輸入文件確定後,整個Shuffle操作才最終結束。之後就是Reducer的執行了,最後Reducer會把結果存到HDFS上。
     3.3 reduce階段:和map函數一樣也是程序員編寫的,最終結果是存儲在hdfs上的。每個reduce進程會對應一個輸出文件,名稱以 part-開頭。

四、總結MapReduece的工作原理

在這裏插入圖片描述
     下面再從map端和reduce端具體分析:
     Map端流程:

  1. 每個輸入分片會讓一個map任務來處理,map輸出的結果會暫時放在一個環形內存緩衝區中(該緩衝區的大小默認爲100M),當該緩衝區快要溢出時(默認爲緩衝區大小的80%),會在本地文件系統中創建一個溢出文件,將該緩衝區中的數據寫入這個文件。
  2. 在寫入磁盤之前,線程首先根據reduce任務的數目將數據劃分爲相同數目的分區,也就是一個reduce任務對應一個分區的數據。這樣做事爲了避免有些reduce任務分配到大量數據,而有些reduce任務卻分到很少數據,甚至沒有分到數據的尷尬局面。其實分區就是對數據進行hash的過程。然後對每個分區中的數據進行排序(默認快速排序),如果此時設置了combiner,將排序後的結果進行combiner操作,這樣做的目的是讓儘可能少的數據寫入到磁盤。
  3. 當map任務輸出最後一個記錄時,可能會有很多的溢出文件,這時需要將這些文件歸併。歸併的過程中會不斷地進行排序(歸併排序)和combiner操作,目的有兩個:一是儘量減少每次寫入磁盤的數據量;二是儘量減少reduce複製階段網絡傳輸的數據量。最後歸併成了一個已分區且已排序的文件。爲了減少網絡傳輸的數據量,這裏可將數據壓縮,只要將mapred.compress.map.out設置爲true就可以了。
  4. 將分區中的數據拷貝給相對應的reduce任務。分區中的數據怎麼知道它對應的reduce是哪個呢?其實map任務一直和其父TaskTracker保持聯繫,而TaskTracker又一直和JobTracker保持心跳。所以JobTracker中保存了整個集羣的宏觀信息。只要reduce任務向JobTracker獲取對應的map輸出位置就可以了。

     Reduce端流程:

  1. Reduce會接收到不同map任務傳來的數據,並且每個map傳來的數據都是有序的。如果reduce端接受的數據量相當小,則直接存儲在內存中(緩衝區大小由mapred.job.shuffle.input.buffer.percent屬性控制,表示用作此用途的堆空間的百分比),如果數據量超過了該緩衝區大小的一定比例(由mapred.job.shuffle.merge.percent決定),則對數據合併後溢寫到磁盤中。
  2. 隨着溢寫文件的增多,後臺線程會將它們合併成一個更大的有序的文件,這樣做事爲了給後面的合併節省時間。其實不管在map端還是reduce端,MapReduce都是反覆地執行排序,合併操作,所以排序是hadoop的靈魂。
  3. 合併的過程中會產生許多的中間文件(寫入磁盤了),但MapReduce會讓寫入磁盤的數據儘可能地少,並且最後一次合併的結果並沒有寫入磁盤,而是直接輸入到reduce函數。
         在map處理數據後,到reduce得到數據之前,這個流程在MapReduce中可以看做是一個shuffle過程。在經過map的運行後,我們得知map的輸出是這樣一個key/value對。到底當前的key應該交由哪個reduce去做呢,是需要現在決定的。MapReduce提供Partitioner接口,它的作用就是根據key或value及reduce的數量來決定當前的這對輸出數據最終應該交由哪個reduce task處理。默認對key做hash後再對reduce task數量取模。默認的取模方式只是爲了平均reduce的處理能力,如果用戶自己對Partitioner由需求,可以定製並設置到job上。

五、MapReduece數據本地化

     首先,HDFS和MapReduce是Hadoop的核心設計。對於HDFS,是存儲基礎,在數據層面上提供了海量數據存儲的支持。而MapReduce,是在數據的上一層,通過編寫MapReduce程序對海量數據進行計算處理。
     在HDFS中,NameNode是文件系統的名字節點進程,DataNode是文件系統的數據節點進程。MapReduce計算框架中負責計算任務調度的JobTracker對應HDFS的NameNode角色,只不過一個負責計算任務調度,一個負責存儲任務調度。MapReduce中負責真正計算任務的TaskTracker對應到HDFS的DataNode角色,一個負責計算,一個負責管理存儲數據。
     考慮到“本地化原則”,一般地,將NameNode和JobTracker部署到同一臺機器上,各個DataNode和TaskTracker也同樣部署到同一臺機器上。
在這裏插入圖片描述
     這樣做的目的是將map任務分配給含有該map處理的數據塊的TaskTracker上,也就是在input split所對應的數據塊所在的存儲節點上,由該節點的tasktracker執行map任務,同時將程序JAR包複製到該TaskTracker上來運行,這叫“計算移動,數據不移動”。而分配reduce任務時並不考慮數據本地化。

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