什麼是MapReduce
MapReduce是大數據時代最強悍的數據處理方式,靈活,可完全自主定製。但是很遺憾,其功能和複雜度一樣強大。開發中基本不具有實用性。但是後續很多處理框架都是基於MapReduce的思想而來,它仍是大數據處理的基礎思想。
大數據系統中很核心的一個問題是分佈式存儲在各個節點服務器上的數據過於龐大,如果運算的時候調動服務器上的數據在線路上流轉,線路是根本扛不住的。就算能抗住,如此海量數據要匯聚在一起來進行運算效率仍然會很低下。
所以爲了解決這個問題MapReduce應運而生,它的思路總結起來很簡單:
1.數據待在各個服務器上不要動。
2.分發計算任務,在各節點上就地統計。然後彙總。
就像做民意調查,不可能叫所有人都集中在一起來詢問,最好的方式就是分治法,分發調查問卷,最後收起來統計問卷數據就好。
接下來以單詞統計爲例來完整展示MapReduce的過程。
假設有一份文本進來,會被分爲幾個塊,分佈式的存儲在服務器集羣中。
一個運算流程叫一個job,首先一個job啓動時,mapping任務會被分發到集羣中各個節點上去。然後在各節點上以KV對的形式統計出各單詞的數量。然後在一個節點上(一般都是管理節點)彙總合併,最後啓動Reduce任務統計出總的結果。
JAVA開發MapReduce
下載好Hadoop後,所需的jar包都在Hadoop的目錄下面可以找到。導入即可。
mapper
import java.io.IOException;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
//四個泛型中,前兩個指定Mapper拿數據的<key,value>類型,後兩個指定輸出的<key,value>類型
//默認情況下,框架傳給mapper的數據類型爲<Long,String> ,Long是文本中一行的起始偏移量,String是這一行的內容。
//又因爲這四個泛型需要在網絡中傳輸,所以需要序列化,但是JDK自帶的序列化太冗餘,會附加大量無用信息,所以在java基本類的基礎上封裝除了一些數據類
//主要就是自定義了一套序列化規則。
//LongWritable對應Long
public class WCMapper extends Mapper<LongWritable, Text, Text, LongWritable>{
//MapReduce框架每讀一次,就調用一次該方法
@Override
protected void map(LongWritable key, Text value,Context context)throws IOException, InterruptedException {
//需要處理的數據已經被傳遞進來,存在Key、value中,具體業務邏輯寫在此方法裏
//取得一行的內容
String line=value.toString();
//以空格分割拿到的內容
String[] words=StringUtils.split(line, " ");
//以<Text,LongWrite>格式傳給reduce
for (String word : words) {
context.write(new Text(word), new LongWritable(1));
}
}
}
reducer
import java.io.IOException;
import java.util.Iterator;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
//四個泛型分別是輸入輸出類型
public class WCReducer extends Reducer<Text, LongWritable, Text, LongWritable>{
//框架在map處理完成之後將所有kv對緩存起來,然後分組,傳遞一個組,調用一次reduce方法
@Override
protected void reduce(Text key, Iterable<LongWritable> values,Context context) throws IOException, InterruptedException {
long count=0;
Iterator<LongWritable> it=values.iterator();
//遍歷累加求和
while(it.hasNext()){
LongWritable value=it.next();
count+=Integer.parseInt(value.toString());
}
context.write(key, new LongWritable(count));
}
}
runner
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
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;
public class WCRunner {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//讀配置文件
Configuration conf=new Configuration();
Job job=Job.getInstance(conf);
//設置整個job的所用類在哪個jar包
job.setJarByClass(WCRunner.class);
//指定Mapper、reducer類
job.setMapperClass(WCMapper.class);
job.setReducerClass(WCReducer.class);
//指定reducer的輸出數據類型kv類型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
//指定mapper的輸出數據類型kv類型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(LongWritable.class);
//指定輸入數據的存放路徑
FileInputFormat.setInputPaths(job, new Path("/wc/srcdata/"));
//指定處理結果的輸出數據存放路徑
FileOutputFormat.setOutputPath(job, new Path("/wc/output/"));
//將jar包提交給集羣運行
job.waitForCompletion(true);
}
}
運行jar包
首先建好輸入路徑,在輸入路徑下面建好需要計算的文本
不要建輸出路徑,否則會報錯。
hadoop jar jar包名稱 runner類全類名