淺解MapReduce與簡單MapReduce程式出包---Hadoop學習筆記(2)
淺略理解MapReduce的概念機制是開始真正使用Hadoop開發Mapreduce程式的第一步,是一個充分條件。理解和實踐並進才能讓更多的問題暴露對理論的理解的不夠。繼續學習《Hadoop基礎教程》。
1.Map與Reduce
Hadoop將資料分成不小於64MB的塊,因此每個資料塊都有一個對應的鍵,而資料塊就作為值,由此形成鍵值對,就是所說的Map,對映。Reduce將Map輸出的鍵值對進行彙集和縮減。有一個清晰的描述:
第一個鍵值對是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。
然後是演算法演算法演算法和寫程式碼寫程式碼寫程式碼功底啊,要加油!