1. 程式人生 > >MapReduce框架學習(1)——輸入、輸出格式

MapReduce框架學習(1)——輸入、輸出格式

參考: JeffreyZhou的部落格園
《Hadoop權威指南》第四版

在前面的學習中,完成了幾件事:

  • 搭建並測試Hadoop完全分散式環境;
  • 在master節點上配置Hadoop的Eclipse開發環境

上一篇博文,Eclipse的開發環境搭建中,博文最後終於揭開了WordCount的原始碼程式,這是一個小程式,但其中也包括了Map/Reduce的大體框架,這個系列博文就來捋一捋整個Map/Reduce的流程及其作用。

一個MR作業,包括三點:

  • 輸入資料
  • MR程式
  • 配置資訊

0 Map/Reduce大致流程

  1. 輸入(input): 將輸入資料分成一個個split,並將spilt進一步拆成<key,value>形式;
  2. 對映(map):根據輸入的<key,value>進行處理,輸出list<key,value>;
  3. 合併(combiner):合併(單個節點上)中間相同的key值;
  4. 分割槽(partition):將<key,value>分成N分,分別送到下一環節;
  5. 化簡(reduce):將中間結果合併,得到最終結果;
  6. 輸出(output):指定輸出最終結果格式。

接下來我們對各個環節進行理解和應用,還是以煮爛了的栗子(WordCount)開刀:

1 .1 輸入分片與記錄

  1. 輸入格式(InputFormat)用於描述整個MapReduce作業的資料輸入規範。
  2. 先對輸入的檔案進行格式規範檢查,如輸入路徑,字尾等檢查;
  3. 然後對資料檔案進行輸入分塊(split),一個分片(split)就是一個由單個map操作來處理的輸入塊,每個Map操作只處理一個split;每個split被劃分若干個記錄,每個記錄就是一個<key,value>對,map一個接一個的處理記錄。
  4. 分片和記錄都是邏輯概念,不必對應到檔案,儘管其常見形式都是檔案。

從一般的文字檔案到資料庫,Hadoop可以處理很多不同型別的資料格式。一圖以蔽之(來源:《Hadoop權威指南》):

InputFormat類的層次結構
圖 InputFormat類的層次結構

1.2 FileInputFormat類

FIleInputFormat類是所有使用檔案作為其資料來源的InputFormat實現的基類,它提供兩個功能:

  1. 指出作業的輸入檔案位置(選擇作為輸入的檔案或物件);
  2. 為輸入檔案生成分片的程式碼實現(定義把檔案劃分到任務的InputSplits)。
  3. 把分片分割成記錄的作業則由其具體的子類來完成(為RecordReader讀取檔案提供了一個工程方法)。

1.3 FIleInputFormat的輸入路徑

  • 提供四種靜態方法來設定job的輸入路徑:
// 單個路徑
public static void addInputPath(Job job, Path path)  
// 注意,下面三個函式名多了 s,用於多個路徑
public static void addInputPaths(Job job, String commaSeparatedPaths)
public static void setInputPaths(Job job, Path... inputPaths)
public static void setInputPaths(Job job, String commaSeparatedPaths)

預設存在一個過濾器,排除隱藏檔案(名稱中以“.”和"_"開頭的檔案),也可以使用setInputPathFilter()方法設定一個過濾器。預設的過濾器只能看到非隱藏檔案。

1.4 常用輸入格式

當然,最常用的還是兩種:

  • TextInputFormat:系統預設的資料輸入格式。將檔案分塊,並逐行讀入,沒一行記錄成為一對<key,value>,其中,key為當前行在整個檔案中的位元組偏移量,LongWritable型別,value為這一行的文字內容,不包括任何行終止符(換行和回車符),它被打包成Text物件。
  • KeyValueTextInputFormat:通常情況下,檔案中的鍵值對形式並非以位元組偏移量表示(用處不大),一般是Text形式的key,使用某個分界符進行分隔,例如Hadoop預設的OutputFormat產生的TextOutputFormat就是這種形式,此時用KeyValueTextInputFormat處理比較合適。
輸入格式 描述
TextInputFormat 預設格式,讀取檔案的行 行的位元組偏移量 行的內容
KeyValueInputFormat 把行解析為鍵值對 第一個tab字元前的所有字元 行剩下內容

還是煮個栗子來的比較實在,如下兩個檔案:
其中第二個檔案,以製表符分割。

inputfile

使用TextInputFormat處理第一個檔案,得到以下3條記錄:

<0, hello world, i am xiaozhou>
<27, stay hungey, stay foolish>
<54, bye game, bye boring>

看吧,在實際應用中,位元組偏移量作為key可能真的沒啥卵用。。。
使用KeyValueTextInputFormat處理第二個檔案,得到以下3條記錄:

<one, hello world, i am xiaozhou>
<two, stay hungry, stay foolish>
<three, bye game, bye boring>

以上,就是常用的兩個inputFormat的區別。

1.5 設定輸入格式

那我們怎麼選擇使用那種輸入格式呢?很簡單,面向物件的思想,你只要呼叫你所使用的輸入格式封裝好的物件就行了。只要在job函式中呼叫

job.setInputFormatclass(MyInputFormat.class)

至於怎麼去建立自己的MyInputFormat,參照上面InputFormat類的層次結構,進行繼承和複寫就行了:

  1. 如果資料來源是檔案,則可以繼承FIleInputFormat:
public class MyInputFormat extends FileInputFormat<Text,Text> {
   @Override
   public RecordReader<Text, Text> createRecordReader(InputSplit split,
         TaskAttemptContext context) throws IOException, InterruptedException {
      // TODO Auto-generated method stub
      return null;
   }
}
  1. 如果資料來源是非檔案,如關係資料,則繼承:
public class MyInputFormat extends InputFormat<Text,Text> {
 
 // 將spilt輸出成<key,value>
   @Override 
   public RecordReader<Text, Text> createRecordReader(InputSplit arg0,
         TaskAttemptContext arg1) throws IOException, InterruptedException {
      // TODO Auto-generated method stub
      return null;
   }
 
 // 拆分為spilt
   @Override 
   public List<InputSplit> getSplits(JobContext arg0) throws IOException,
         InterruptedException {
      // TODO Auto-generated method stub
      return null;
   }
 
}

1.6 輸出格式

資料輸出格式(OutputFormat)用於描述MR作業的資料輸出規範,Hadoop提供了豐富的內建資料輸出格式。最常的資料輸出格式是TextOutputFormat,也是系統預設的資料輸出格式,將結果以"key+\t+value"的形式逐行輸出到文字檔案中。還有其它的,如來源:《Hadoop權威指南》:

mapreduce_output

1.7 設定輸出格式

預設的輸出格式是TextOutputFormat,把每條記錄寫為文字行,鍵值可以是任意型別,因為TextOutputFormat會呼叫toString()方法把它們轉換為字串,每個鍵值對由製表符(tab)進行分隔(當然也可以設定分隔符),與其對應的輸入格式是KeyValueOutputFormat。
若要自定義輸出格式,如下:

public class MyOutputFormat extends OutputFormat<Text,Text> {

   @Override
   public void checkOutputSpecs(JobContext arg0) 
   throws IOException, InterruptedException {
      // TODO Auto-generated method stub
 
   }
 
   @Override
   public OutputCommitter getOutputCommitter(TaskAttemptContext arg0) 
    throws IOException, InterruptedException {
      // TODO Auto-generated method stub
      return null;
   }
 
   @Override
   public RecordWriter<Text, Text> getRecordWriter(TaskAttemptContext arg0)
         throws IOException, InterruptedException {
      // TODO Auto-generated method stub
      return null;
   }
}

1.8 複合鍵

從前面的整個過程中可以看到,都是採用key-value的方式進行傳入傳出,而這些key或者value型別大多是單一的字串或者整型,也就是基本資料型別。如果我的key中需要包含多個資訊怎麼辦?用字串直接拼接麼? 太不方便了,最好能夠自己定義一個類,作為這個key,這樣就方便了。
要自定義一個類作為key或value的型別,就要實現WriableComparable類,複寫其中三個函式如下:

public class MyType implements WritableComparable<MyType> {
 
   private float x,y;
   public float GetX(){return x;}
   public float GetY(){return y;}
 // 讀緩衝
      @Override
      public void readFields(DataInput in) throws IOException {
         x = in.readFloat();
         y = in.readFloat();
      }
 // 序列化
      @Override
      public void write(DataOutput out) throws IOException {
         out.writeFloat(x);
         out.writeFloat(y);
      }
 
 // 比較器
      @Override
      public int compareTo(MyType arg0) {
         //輸入:-1(小於) 0(等於) 1(大於)
         return 0;
      }
   }

關於複合鍵,在本節內容學習的最後,會寫一個倒排索引的程式例子,就會使用複合鍵。
注:關於writable還有很多細節上的知識,《Hadoop權威指南》上也沒系統性的講解,待後面遇到了實際問題再解決吧。

1.x 後記

目前能用到的關於檔案格式的知識大概也就這些了,看《Hadoop權威指南》上還有很多細節上的東西,等以後用到了再回來查吧,不然學了也記不住。