1. 程式人生 > >淺解MapReduce與簡單MapReduce程式出包---Hadoop學習筆記(2)

淺解MapReduce與簡單MapReduce程式出包---Hadoop學習筆記(2)

淺略理解MapReduce的概念機制是開始真正使用Hadoop開發Mapreduce程式的第一步,是一個充分條件。理解和實踐並進才能讓更多的問題暴露對理論的理解的不夠。繼續學習《Hadoop基礎教程》。
1.Map與Reduce
Hadoop將資料分成不小於64MB的塊,因此每個資料塊都有一個對應的鍵,而資料塊就作為值,由此形成鍵值對,就是所說的Map,對映。Reduce將Map輸出的鍵值對進行彙集和縮減。有一個清晰的描述:
{K1,V1}>{K2,List<V2>}>{K3,V3}


第一個鍵值對是Map的輸入,Map的輸出是第二個鍵值對所表示的有一個鍵及對應的值列表組成,其形成的過程是一個叫shuffle的方法。第三個鍵值對由Reduce縮減輸出,也是MapReduce的最終輸出。
2.MapReduce的Java API
實踐出真知,先貼上總結的示例,再逐步分析。

import org.apache.hadoop.conf.*;
import org.apache.commons.io.IOExceptionWithCause;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
import java.io.IOException; import java.io.InterruptedIOException; public class WordCount { public static class WordCountMapper extends Mapper<Object,Text,Text,IntWritable> { private final static IntWritable one =new IntWritable(1); private Text word=new Text(); public void map(Object key,Text value,Context context) { String words[]=value.toString().split("\\W"); try { for (String str : words) { word.set(str); context.write(word, one); } }catch (IOException e) { e.printStackTrace(); }catch (InterruptedException e) { e.printStackTrace(); } } } public static class WordCountReducer extends Reducer<Text,IntWritable,Text,IntWritable> { public void reduce(Text key, Iterable<IntWritable> values, Context context) { int total = 0; for (IntWritable val : values) { total+=val.get(); } try { context.write(key, new IntWritable(total)); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String...args)throws Exception { Configuration conf=new Configuration(); Job job=new Job(conf,"word count"); job.setJarByClass(WordCount.class); job.setMapperClass(WordCountMapper.class); job.setReducerClass(WordCountReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); job.setCombinerClass(WordCountReducer.class); FileInputFormat.addInputPath(job,new Path(args[0])); FileOutputFormat.setOutputPath(job,new Path(args[1])); System.exit(job.waitForCompletion(true)?0:1); } }

首先說明一下用的IDE是IntelliJ,要在intelliJ上寫MapReduce要新增Hadoop的依賴。以防以後忘記,步驟如下:
1.在project Struct下的Modules中新增MapReduce所需要的jar包
2.用的包有

  $ hadoop-2.6.0/share/hadoop/mapreduce
  $ hadoop-2.6.0/share/hadoop/common
  $ hadoop-2.6.0/share/hadoop/common/lib

這三個目錄下的jar包。
3.製作好modules後再進入artifacts將Modeles打包成jar包即可
這樣我們就完成了MapReduce程式開發的前期準備,我們所夢寐以求的API就都有啦,終於可以寫MapReduce程式啦!(好吃力的樣子)。
我們自定義的Mapper類和Reducer類都要繼承Hadoop的Mapper和Reducer類,分別需要過載實現

void map(K1 key,V1 value,Mapper.Context context) //Context提供與Hadoop框架通訊的基本機制
    throws IOException,InterruptedException
{
//TODO
}

void reduce(K1 key,Iterable<V2> values,Reducer.Context context) 
    throws IOException,InterruptedException
{
//TODO
}

map:
在WordCount(字數統計),TextInputFormat提供了以行號為鍵的對映,map由此形成鍵值對。
context.write(word, one);語句負責將map方法中的鍵值對進行輸出。
reduce:
負責對map形成的鍵和一組值的組合進行字數統計。
Map的輸入:{IntWritable,Text}
輸出:{Text,IntWritable}
Reduce的輸入:{Text,IntWritable}
輸出:{Text,IntWritable}
main函式是程式的驅動,其引數指明程式的輸入和輸出的檔案位置,即一個Input檔案和一個OutPut檔案,這兩個引數由執行MapReduce時傳入。
3.執行MapReduce程式
首先,確保Hadoop已經開啟
檢查Java虛擬機器狀態:

4048 Main
5316 DataNode
5800 NameNode
6142 Jps
5487 SecondaryNameNode

首先看一下IntelliJ生成Jar包的位置,然後我們先鍵入命令:

bin/hadoop jar ~/IdeaProjects/WordCount-Hadoop/out/artifacts/WordCount_Hadoop_jar/WordCount-Hadoop.jar WordCount test.text output

噹噹,很榮幸地出錯了,所以盲目地鍵入程式碼而不搞清楚含義很蛋疼啊。我們一步步分析一下以上的命令吧。
bin/hadoop jar “jar”這個是hadoop的命令列中的執行jar命令,我們要進入bin/hadoop才可以執行hadoop的命令,這裡我沒有深入去了解,以後好慚愧。
jar後面的是我們的jar包的目錄,那麼我們想,既然有了命令,有了包,為啥後面還跟著三串似曾相識又說不出所以然來的命令呢?
那麼我們再發散一下思維,突然想到前面寫程式的時候好像也提到引數?往前一看,發現main作為一個驅動,必須傳入輸入檔案位置和輸出檔案位置。那麼這個推理加上猜測,(再加上一次次一堆堆報錯),我可以順理成章地知道,後面三個引數:第一個是WordCount的入口類,第二個是Input檔案,第三個是Output檔案。那麼問題來了,Iuput的位置怎麼搞?在那個目錄下?隨便什麼都行嗎?
於是我隨便創了個input檔案放在使用者目錄下,得到數次報錯如下:

Exception in thread "main" org.apache.hadoop.mapreduce.lib.input.InvalidInputException: Input path does not exist: hdfs://localhost:9000/user/coder-z/test.text
    at org.apache.hadoop.mapreduce.lib.input.FileInputFormat.singleThreadedListStatus(FileInputFormat.java:321

吸取實踐教訓,我發現其實這個input檔案需要先上傳到hadoop的hdfs檔案系統中,而我剛開始配置的時候貌似沒有建立相應的hdfs本地檔案系統。所以,乾脆,直接上傳檔案到hadoop檔案系統中試試,

bin/hadoop fs -copyFromLocal ~/test.text hdfs://localhost:9000/

使用hadoop檔案系統中的copyFromLocal命令,將我的input檔案傳到hdfs中。(這裡我也沒有去深入瞭解Hadoop的檔案系統,不好意思)
接下來,
鍵入我們一開始令我懵懵懂懂的命令

bin/hadoop jar ~/IdeaProjects/WordCount-Hadoop/out/artifacts/WordCount_Hadoop_jar/WordCount-Hadoop.jar WordCount hdfs://localhost:9000/test.text output

相應地,對input檔案位置做了修改。
值得注意的是,前面的報錯說是在hdfs的使用者目錄下未找到檔案,也就是hdfs預設在這個目錄中尋找input檔案,而我在上傳到該使用者目錄下時卻被告知未找到該目錄?這個目錄是要自己建立的嗎?下一次試試看。所以乾脆指定input目錄在hdfs://localhost:9000下。由於我配置的時候是9000埠,所以是localhost:9000。
好了,鍵入命令,大功告成(?)。出來的結果令人興奮,但是,我要的結果呢?
上面提到,我們的最後一個引數是輸出位置,那麼我們檢視一下hadoop檔案系統中我們定義為output的

bin/hadoop fs -ls output

首先確認output檔案裡的內容,應該有兩個,一個是_SUCCESS,另一個就是結果output/part-r-00000,後面的00000是reducer輸出寫入的序號。
檢視結果:

bin/hadoop fs -cat output/output/part-r-00000

呼,大功告成(?)

下面一次筆記準備寫MapReduce的執行過程(淺略)和初步的MapReudce程式的開發,發現如果要深入理解MapReduce,看教程肯定不夠啊,找個時間泛讀一下google Mapreduce。
然後是演算法演算法演算法和寫程式碼寫程式碼寫程式碼功底啊,要加油!