大資料時代之hadoop(五):hadoop 分散式計算框架(MapReduce)

chaofanwei發表於2014-11-03

 

大資料時代之hadoop(一):hadoop安裝

大資料時代之hadoop(二):hadoop指令碼解析

大資料時代之hadoop(三):hadoop資料流(生命週期)

大資料時代之hadoop(四):hadoop 分散式檔案系統(HDFS)

 

        hadoop的核心分為兩塊,一是分散式儲存系統-hdfs,這個我已經在上一章節大致講了一下,另一個就是hadoop的計算框架-mapreduce

     

        mapreduce其實就是一個移動式的基於key-value形式的分散式計算框架

 

        其計算分為兩個階段,map階段和reduce階段,都是對資料的處理,由於其入門非常簡單,但是若想理解其中各個環節及實現細節還是有一定程度的困難,因此我計劃在本文中只是挑幾個mapreduce的核心來進行分析講解。


 

1、MapReduce驅動程式預設值

 

        編寫mapreduce程式容易入手的其中一個原因就在於它提供了一些了的預設值,而這些預設值剛好就是供開發環境設定而設定的。雖然容易入手,但還是的理解mapreduce的精髓,因為它是mapreduce的引擎,只有理解了mapreduce的核心,當你在編寫mapreduce程式的時候,你所編寫的程式才是最終穩重的,想要的程式。廢話少說,見下面程式碼:

public int run(String[] args) throws IOException {
    JobConf conf = new JobConf();
    

    /**
     *預設的輸入格式,即mapper程式要處理的資料的格式,hadoop支援很多種輸入格式,下面會詳細講解,
     *但TextInputFormat是最常使用的(即普通文字檔案,key為LongWritable-檔案中每行的開始偏移量,value為Text-文字行)。
     **/
    conf.setInputFormat(org.apache.hadoop.mapred.TextInputFormat.class);
    
    /**
     *真正的map任務數量取決於輸入檔案的大小以及檔案塊的大小
     **/
    conf.setNumMapTasks(1);
    
    /**
     *預設的mapclass,如果我們不指定自己的mapper class時,就使用這個IdentityMapper 類
     **/
    conf.setMapperClass(org.apache.hadoop.mapred.lib.IdentityMapper.class);
    
    /**
     * map 任務是由MapRunner負責執行的,MapRunner是MapRunnable的預設實現,它順序的為每一條記錄呼叫一次Mapper的map()方法,詳解程式碼  --重點
     */
    conf.setMapRunnerClass(org.apache.hadoop.mapred.MapRunner.class);
    
    /**
     * map任務輸出結果的key 和value格式
     */
    conf.setMapOutputKeyClass(org.apache.hadoop.io.LongWritable.class);
    conf.setMapOutputValueClass(org.apache.hadoop.io.Text.class);
    
    /**
     * HashPartitioner 是預設的分割槽實現,它對map 任務執行後的資料進行分割槽,即把結果資料劃分成多個塊(每個分割槽對應一個reduce任務)。
     * HashPartitioner是對每條 記錄的鍵進行雜湊操作以決定該記錄應該屬於哪個分割槽。
     * 
     */
    conf.setPartitionerClass(org.apache.hadoop.mapred.lib.HashPartitioner.class);
    
    /**
     * 設定reduce任務個數
     */
    conf.setNumReduceTasks(1);
    
    /**
    *預設的reduce class,如果我們不指定自己的reduce class時,就使用這個IdentityReducer 類
    **/
    conf.setReducerClass(org.apache.hadoop.mapred.lib.IdentityReducer.class);

    /**
     * 任務最終輸出結果的key 和value格式
     */
    conf.setOutputKeyClass(org.apache.hadoop.io.LongWritable.class);
    conf.setOutputValueClass(org.apache.hadoop.io.Text.class);

    /**
     * 最終輸出到文字檔案型別中
     */
    conf.setOutputFormat(org.apache.hadoop.mapred.TextOutputFormat.class);/*]*/
    
    JobClient.runJob(conf);
    return 0;
  }

 

我要說的大部分都包含在了程式碼的註釋裡面,除此之外,還有一點:由於java的泛型機制有很多限制:型別擦除導致執行過程中型別資訊並非一直可見,所以hadoop需要明確設定map,reduce輸入和結果型別

 

上面比較重要的就是MapRunner這個類,它是map任務執行的引擎,預設實現如下:

public class MapRunner<K1, V1, K2, V2>
    implements MapRunnable<K1, V1, K2, V2> {
  
  private Mapper<K1, V1, K2, V2> mapper;
  private boolean incrProcCount;

  @SuppressWarnings("unchecked")
  public void configure(JobConf job) {
  //通過反射方式取得map 例項
    this.mapper = ReflectionUtils.newInstance(job.getMapperClass(), job);
    //increment processed counter only if skipping feature is enabled
    this.incrProcCount = SkipBadRecords.getMapperMaxSkipRecords(job)>0 && 
      SkipBadRecords.getAutoIncrMapperProcCount(job);
  }

  public void run(RecordReader<K1, V1> input, OutputCollector<K2, V2> output,
                  Reporter reporter)
    throws IOException {
    try {
      // allocate key & value instances that are re-used for all entries
      K1 key = input.createKey();
      V1 value = input.createValue();
      
      while (input.next(key, value)) {
        // map pair to output
	//迴圈呼叫map函式
        mapper.map(key, value, output, reporter);
        if(incrProcCount) {
          reporter.incrCounter(SkipBadRecords.COUNTER_GROUP, 
              SkipBadRecords.COUNTER_MAP_PROCESSED_RECORDS, 1);
        }
      }
    } finally {
      mapper.close();
    }
  }

  protected Mapper<K1, V1, K2, V2> getMapper() {
    return mapper;
  }
}


 

要相信,有些時候還是看原始碼理解的更快!

 

 

2、shuffle

          shuffle過程其實就是從map的輸出到reduce的輸入過程中所經歷的步驟,堪稱mapreduce的“心臟”,分為3個階段,map端分割槽、reduce端複製、reduce排序(合併)階段。

 

2.1、map端分割槽

         由於在mapreduce計算中,有多個map任務和若干個reduce任何,而且各個任務都可能處於不同的機器裡面,所以如何從map任務的輸出到reduce的輸入是一個難點。

 


        map函式在產生輸出時,並不是簡單的寫到磁碟中,而是利用緩衝的形式寫入到記憶體,並出於效率進行預排序,過程如下圖:

 

 

 

       寫磁碟之前,執行緒首先根據reduce的個數將輸出資料劃分成響應的分割槽(partiton)。在每個分割槽中,後臺執行緒按鍵進行內排序,如果有個一combiner,它會在排序後的輸出上執行。

 

2.2、reduce端複製階段

    

    由於map任務的輸出檔案寫到了本地磁碟上,並且劃分成reduce個數的分割槽(每一個reduce需要一個分割槽),由於map任務完成的時間可能不同,因此只要一個任務完成,reduce任務就開始複製其輸出,這就是reduce任務的複製階段。如上圖所示。

 

2.3、reduce端排序(合併)階段

 

     複製完所有map輸出後,reduce任務進入排序階段(sort phase),這個階段將合併map輸出,維持其順序排序,如上圖所示。

 


3、輸入與輸出格式

       隨著時間的增加,資料的增長也是指數級的增長,且資料的格式也越來越多,對大資料的處理也就越來越困難,為了適應能夠處理各種各樣的資料,hadoop提供了一系列的輸入和輸出格式控制,其目的很簡單,就是能夠解析各種輸入檔案,併產生需要的輸出格式資料


       但是不管處理哪種格式的資料,都要與mapreduce結合起來,才能最大化的發揮hadoop的有點。

    這部分也是hadoop的核心啊!

 

3.1、輸入分片與記錄

 

        在講HDFS的時候,說過,一個輸入分片就是由單個map任務處理的輸入塊一個分片的大小最好與hdfs的塊大小相同

 

        每個分片被劃分成若干個記錄,每個記錄就是一個鍵值對,map一個接一個的處理每條記錄


             資料庫常見中,一個輸入分片可以對應一個表的若干行,而一條記錄對應一行(DBInputFormat)。


 

        輸入分片在hadoop中表示為InputSplit介面,有InputFormat建立的


        InputFormat負責產生輸入分片並將他們分割成記錄,其只是一個介面,具體任務有具體實現去做的

 

 

3.2、FileInputFormat

           FileInputFormat是所有使用檔案作為其資料來源的InputFormat實現的基類,它提供了兩個功能:一個定義哪些檔案包含在作業的輸入中一個為輸入檔案產生分片的實現把分片割成基類的作業有其子類實現,FileInputFormat是個抽象類

 

   FileInputFormat實現了把檔案分割槽的功能,但它是怎麼來實現了呢?需要先說三個引數:

屬性名稱

型別

預設值

描述

mapred.min.split.size

Int

1

一個檔案分片的最小位元組數

mapred.max.split.size

Long

Long.MAX_VALUE

一個檔案分片的最大位元組數

dfs.block.size

long

64M

HDFS中塊大小

 

   分片的大小有一個公式計算(參考FileInputFomat類的computeSplitSize()方法)


                     max(minimumSize,min(maximumSize,blockSize))


 預設情況下: minimumSize  <  blockSize < maximumSize

 

 

   FileInputFormat只分割大檔案,即檔案大小超過塊大小的檔案


   FileInputFormat生成的InputSplit是一整個檔案(檔案太小,未被分割槽,整個檔案當成一個分割槽,供map任務處理)或該檔案的一部分(檔案大,被分割槽)
 

3.3、常用的InputFormat實現

 

 

小檔案與CombineFileInputFormat


      雖然hadoop適合處理大檔案,但在實際的情況中,大量的小檔案處理是少不了的,因此hadoop提供了一個CombineFileInputFormat,它針對小檔案而設計的,它把多個檔案打包到一個分片中一般每個mapper可以處理更多的資料

 

 

TextInputFormat


     hadoop預設的InputFormat,每個記錄的鍵是檔案中行的偏移量,值為行內容

 


KeyValueInputFormat


     適合處理配置檔案,檔案中行中為key value格式的,如key=value型別的檔案  ,key即為行中的key,value即為行中的value

 


NLineInputFormat


     也是為處理文字檔案而開發的,它的特點是為每個map任務收到固定行數的輸入,其他與TextInputFormat相似。

 


SequenceFileInputFormat(二進位制輸入)


     hadoop的順序檔案格式儲存格式儲存二進位制的鍵值對序列,由於順序檔案裡面儲存的就是map結構的資料,所以剛好可以有SequenceFileInputFormat 來進行處理。

 


DBInputFormat


     顧名思義,用於使用jdbc從關聯式資料庫中讀取資料。

 

 

多種輸入


        MultipleInputs類可以用來處理多種輸入格式的資料,如輸入資料中包含文字型別和二進位制型別的,這個時候就可以用 MultipleInputs來指定某個檔案有哪種輸入型別和哪個map函式來解析。

 

3.4、輸出格式

     既然有輸入格式,就有輸出格式,與輸入格式對應。


     預設的輸出格式是TextOutputFormat,它把記錄寫成文字行,鍵值對可以是任意型別, 鍵值對中間預設用製表符分割



 

 

3.5、hadoop特性

 

       除了上面幾點之外,還有計數器、排序、連線等需要關注,詳細待後續吧。。。

 

 


 

相關文章