文章目錄
一、前言
可以看這幾個視頻,挺棒的,可以快速上手MR開發。
先看這個鏈接的視頻,非常短,會大題有一個瞭解:
1、深入淺出講解MapReduece
再看這兩個,以經典的wordcount爲例,講解原理與編程:
2、wordcount程序原理以及代碼實現
3、mapreduce編程模型和具體實現框架之間的概念關係
有時間還可以看看3所在這個視頻集合裏的其他案例。
如果嫌視頻慢的話,那就看我下面的代碼以及文字吧。
我先以wordcount爲例,來學習。據前輩說,好多的業務邏輯本質上都是wordcount的邏輯。
可以大概看看下面的第二部分,直接看代碼,我在代碼裏面加了很多註釋,對整個邏輯寫的非常詳細啦。
二、wordcount原理及分析
1、如何統計單詞數?
2、MapReduce數據處理邏輯
要實現一個業務邏輯,關鍵在於:看我map這邊產生寫什麼key,value。相同key的會通過reduce把對應的那些value聚合起來。
3、MapReduce架構(可略過)
我早年博客,可以加深理解:
https://blog.csdn.net/u013317445/article/details/51769986
在寫代碼之前,一定要把wordcount的業務邏輯搞清楚。知道mapper的輸入輸出是啥,知道reduce的輸入輸出是啥。知道map()實現啥,reduce()實現啥。
三、wordcount code
code主要分爲三部分:
Mapper部分:核心是重寫裏面的map()方法,把map階段的業務邏輯寫到map()方法裏面。
Reducer部分:核心是重寫裏面的reduce()方法,把reduce階段的業務邏輯寫到reduce()方法裏面。
Driver部分:在此封裝我們的MR程序相關運行參數。整個程序需要一個Drvier來進行提交。
WordcountMapper.java
package wc;/**
* Created by cc on 2019/11/26.
*/
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* @program: WordC
* @description: map
* @author: chen
* @create: 2019-11-26 16:41
*
* Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> 輸出輸入類型:
* KEYIN:默認下,是mr框架所讀到的一行文本的起始偏移量,Long。
* 但在hadoop裏面有更加精簡的接口,不直接用Long,而用LongWritable
* VALUEIN:默認下,是mr框架所讀到的一行文本內容,String
* 同上,用Text(org.apache.hadoop.io裏面的)
* KEYOUT:是用戶自定義邏輯處理完成後輸出數據中的key,在此處是單詞,String。同上,用Text
* VALUEOUT:用戶自定義邏輯完成之後輸出數據中的value,在此處是單詞詞頻,Integer
* 同上,用IntWritable
**/
public class WordcountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
/**
* 把map階段的業務邏輯寫到map()方法裏面
* map task會對每一行輸入數據調用一次我們自定義的map()方法。原來是每一行數據調一次map!!!
* 之後,每調一次map()會有一個輸出(一行數據處理完了有一個輸出)。map task會把這些輸出收集收集到一起。然後根據單詞的不同給不同的區域,再發給不同的reduce task
* 實際上,有多個map tasks,每個去處理一部分文本
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//super.map(key, value, context);
/**
* map task會對每一行輸入數據調用一次map()
* 那麼map()內要對這一行讀入的文本作何處理呢?
* 1、將文本內容轉爲字符串
* 2、按空格分割字符串,獲得每一個單詞
* 3、每一個單詞,構建一個<word,1>,輸出<word,1>,寫到context裏
**/
String line= value.toString();
String[] words= line.split(" ");
for(String word:words){
//context.write(word,1); 錯誤,Mapper的輸出類型是Text,IntWritable
context.write(new Text(word), new IntWritable(1));
}
}
}
WordcountReducer.java
package wc;/**
* Created by cc on 2019/11/27.
*/
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* @program: WordC
* @description: reduce
* @author: chen
* @create: 2019-11-27 10:33
*
* Reducer<KEYIN, VALUEIN ,KEYOUT, VALUEOUT >輸入輸出類型:
* KEYIN, VALUEIN對應Mapper輸出類型Text,IntWritable
* KEYOUT, VALUEOUT是自定義Reducer的輸出數據類型
* KEYOUT是單詞,類型Text
* VALUEOUT是詞頻,類型IntWritable
**/
public class WordcountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
/**
* 把reduce階段的業務邏輯寫到reduce()方法裏面
* reduce task拿到一組一組的數據,對每一組數據調一次reduce():
* 如對<sun,1><sun,1><sun,1><sun,1><sun,1><sun,1>處理一次,調一次reduce();
* 再下一個對<moon,1><moon,1><moon,1><moon,1>處理一次,調一次reduce(),......。
*
* reduce task數量可以指定,默認是1個
*
* 具體每一次reduce():
* 對數據塊,key爲同一個,對應的value們串成了一個迭代器
* 對value疊加,出count
* 輸出<key,count>到context,整合出了次數
* */
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//super.reduce(key, values, context);
int count=0;
for(IntWritable value: values){
count += value.get();//.get()轉爲int
}
// Iterator<IntWritable> it= values.iterator();
// while(it.hasNext()){
// count += it.next().get();
// }
context.write(key, new IntWritable(count));//輸出
}
}
WordcountDriver.java
剛開始學,Driver這個就當成一個固定模板照着寫吧,只需要改一下參數和路徑。
主要在上面Mapper裏面的map()方法重寫,Reduecer裏面的reduce()方法重寫,把咱們的業務邏輯算法邏輯梳理清,就可以用代碼表達啦。
package wc;/**
* Created by cc on 2019/11/27.
*/
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
/**
* @program: WordC
* @description: 寫main()
* @author: chen
* @create: 2019-11-27 12:04
*
* 相當於yarn集羣的客戶端
* 需要在此封裝我們的MR程序相關運行參數,這頂jar包
* 最後提交給yarn
**/
/**
* map:本質是拆解,汽車拆成零件
* reduce:本質是組合,汽車零件+其它各種機械零件組合成變形金剛
* input(取數據)+split(切分)+map()+ shuffle(歸類數據)+reduce(組裝)+finalize(交付)
* */
public class WordcountDriver {
public static void main(String[] args) throws Exception {
Configuration conf= new Configuration();
Job job= Job.getInstance(conf); //創建一個job
//指定本程序的jar包所在的本地路徑(告訴yarn jar包在哪裏)
//job.setJar("path");//寫死了路徑
job.setJarByClass(WordcountDriver.class);//不寫死,就在jar包所在路徑
//指定本業務job要使用的mapper/reduce業務類
job.setMapperClass(WordcountMapper.class);
job.setReducerClass(WordcountReducer.class);
//指定mapper輸出數據類型,kv的
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//指定最終輸出數據類型,kv的
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//如果以後我們要自定義輸入輸出格式的話需要加上:
//job.setInputFormatClass(MyInputFormat.class);
//job.setOutputFormatClass(MyOutputFormat.class);
//指定job的輸入原始文件目錄
FileInputFormat.setInputPaths(job, new Path("F:\\ideaWorkspace\\WordC\\input"));//我把路徑寫死了
//指定job的輸出結果所在目錄
FileOutputFormat.setOutputPath(job, new Path("F:\\ideaWorkspace\\WordC\\output2"));
//提交:將job中配置的相關參數,以及job所用的java類所在的jar包,提交給yarn去運行
//job.submit();
boolean res= job.waitForCompletion(true);
System.exit(res? 0:1);//退出個0:job跑成功了
}
}
四、梳理一下MR編程規範
- 1、用戶編寫的程序分爲三個部分:Mapper、Reducer、Driver(提交運行mr程序的客戶端)
- 2、Mapper的輸入數據是KV對的形式(KV類型可自定義)
- 3、Reducer的輸出數據是KV對的形式(KV類型可自定義)
- 4、Mapper中的業務邏輯寫在map()方法中
- 5、map()方法(maptask進程)對每一個<K,V>調用一次。(處理一次<K,V>調一次map)
- 6、Reducer的輸入數據類型對應Mapper的輸出數據類型,也是KV
- 7、Reducer的業務邏輯寫在reduce()方法中
- 8、Reduce()方法對每一組相同K的<K,V>組調用一次
- 9、用戶的Mapper和Reducer都要繼承各自的類
- 10、整個程序需要一個Drvier來進行提交,提交的是一個描述了各種必要信息的job對象
map task們全部輸出完了,reduce task們纔開始去map的機器上去拿數據。
map task們輸出數據時候,有outputCollector在收集<k1,v>,<k1,v><k1,v><k1,v>…
reduce task拿到一組一組的數據(相同key的爲一組)
map task可能和reduce task在不同的機器上。
五、補充一下hadoop mapreduce內置數據類型
在考慮KV的輸入輸出類型的時候需要到這個知識點。
- BooleanWritable 標準布爾型數值
- ByteWritable 單字節數值
- DoubleWritable 雙字節數
- FloatWritable 浮點數
- IntWritable 整型數
- LongWritable 長整型數
- Text 使用UTF-8格式存儲的文本
- NullWritable 當<key, value>中的key或value爲空時使用
這些內置數據類型,都實現了WritableComparable接口,以便用這些類型定義的數據可以被序列化進行網絡傳輸和文件存儲,以及進行大小比較。
我現在是能用內置類型就用內置類型,不用自己寫了。
我們也可以自定義數據類型,不過要自己寫一些接口。具體的話暫時沒用到。
六、進一步,寫Partitioner,寫Combiner
https://www.cnblogs.com/frankdeng/p/9256254.html 可以看一下這個博客後面的部分。寫了Partitioner把單詞按照ASCII碼奇偶分區,寫了Combiner對每一個maptask的輸出局部彙總。
好啦,就分享到這裏吧。the end~