覺得有幫助的,請多多支持博主,點贊關注哦~
文章目錄
MapReduce框架詳解
1、Job提交源碼分析
waitForCompletion()
submit();
// 1 建立連接
connect();
// 1)創建提交 job 的代理
new Cluster(getConfiguration());
// (1)判斷是本地 yarn 還是遠程
initialize(jobTrackAddr, conf);
// 2 提交 job
submitter.submitJobInternal(Job.this, cluster)
// 1)創建給集羣提交數據的 Stag 路徑
Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);
// 2)獲取 jobid ,並創建 job 路徑
JobID jobId = submitClient.getNewJobID();
// 3)拷貝 jar 包到集羣
copyAndConfigureFiles(job, submitJobDir);
rUploader.uploadFiles(job, jobSubmitDir);
// 4)計算切片,生成切片規劃文件
writeSplits(job, submitJobDir);
maps = writeNewSplits(job, jobSubmitDir);
input.getSplits(job);
// 5)向 Stag 路徑寫 xml 配置文件
writeConf(conf, submitJobFile);
conf.writeXml(out);
// 6)提交 job,返回提交狀態
status = submitClient.submitJob(jobId,submitJobDir.toString(),job.getCredentials());
2、輸入端InputFormat
2.1、FilelnputFormat切片原則(默認)
2.1.1、切片的原則
FileInputFormat 中默認的切片機制:
- 簡單地按照文件的內容長度進行切片
- 切片大小,默認等於 block 大小
- 切片時不考慮數據集整體,而是逐個針對每一個文件單獨切片
比如待處理數據有兩個文件:
file1.txt 320M
file2.txt 10M
經過 FileInputFormat 的切片機制運算後,形成的切片信息如下:
file1.txt.split1-- 0~128
file1.txt.split2-- 128~256
file1.txt.split3-- 256~320
file2.txt.split1-- 0~10M
2.1.2、修改切片大小
通過分析源碼,在 FileInputFormat 中,計算切片大小的邏輯:Math.max(minSize,Math.min(maxSize, blockSize));
切片主要由這幾個值來運算決定
mapreduce.input.fileinputformat.split.minsize=1 默認值爲 1
mapreduce.input.fileinputformat.split.maxsize= Long.MAXValue 默認值 Long.MAXValue
因此,默認情況下,切片大小=blocksize。
maxsize(切片最大值):參數如果調得比 blocksize 小,則會讓切片變小,而<就等於配置的這個參數的值。
minsize(切片最小值):參數調的比 blockSize 大,則可以讓切片變得比 blocksize 還大。
詳解:
計算每片的大小,三個數據:minSize,maxSize,blockSize
Math.max(minSize, Math.min(maxSize, blockSize));
minSize:10m maxSize:200m--->每片大小:128M
minSize:10m maxSize:100m--->每片大小:100M
maxSize<blockSize: 每片的大小 小於128M
hello.txt :10byte -->分片:1片
修改了每片的maxSize:4Byte,minSize-->分片:3片
每片size >128m?????
minSize:200m maxSize:250m ?-->200m
修改每片的大小:結論
1)maxSize<blockSize: 每片的大小 小於128M
2)minSize >blockSize: 每片的大小 大於 128m
分片數:影響 mapTask的並行度
思考:是不是並行度越高,就越好?
並不是。需要考慮數據量的多少及機器的配置。如果數據量很少,可能任務啓動的時間都遠遠超過數據的處理時間。同樣也不是越少越好。
2.1.3、獲取切片信息 API
// 根據文件類型獲取切片信息
FileSplit inputSplit = (FileSplit) context.getInputSplit();
// 獲取切片的文件名稱
String name = inputSplit.getPath().getName();
2.1.4、代碼測試
package com.biubiubiu.demo;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
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 java.io.IOException;
public class TestSplit {
static class CustomMapper extends Mapper<Object, Text,Text, Text>{
Text text= new Text();
@Override
protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
text.set(value.toString());
context.write(text,text);
}
}
static class CustomReducer extends Reducer<Text, Text,Text, Text>{
Text text= new Text();
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
text.set(key.toString());
context.write(text,text);
}
}
//第3部分,編寫Driver(main方法)
public static void main(String[] args) {
try{
// if (args.length<4){
// System.out.println("至少4個參數");
// System.exit(0);
// }
//1)創建Configuration對象,指明namespace的路徑
Configuration conf = new Configuration();
conf.set("dfs.defaultFS","hdfs://192.168.153.231:9000");
conf.set("mapreduce.input.fileinputformat.split.minsize","1");//最小片大小設置
conf.set("mapreduce.input.fileinputformat.split.maxsize","30");//最大片大小設置
//
//2)創建Job
Job job =Job.getInstance(conf,"testSplit");
job.setJarByClass(TestSplit.class);
//3)自定義Mapper進行輸出參數(key,value)的配置
job.setMapperClass(CustomMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
//4)自定義Reducer進行參數的配置
job.setReducerClass(CustomReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//5)配置處理的文件的路徑(input)以及處理結果存放的路徑(output)
FileInputFormat.addInputPath(job,new Path("d_split/hello.txt"));
FileOutputFormat.setOutputPath(job,new Path("d_split/2"));
//6)讓程序執行
boolean result=job.waitForCompletion(true);
if(result){
System.out.println("執行正確!!!");
}else{
System.out.println("執行失敗.....");
}
}catch(Exception ex){
System.out.println("執行出錯:"+ex.getMessage());
ex.printStackTrace();
}
}
}
2.2、CombineTextinputFomat切片原則
2.2.1、關於大量小文件的優化策略
2.2.1.1、缺點
默認情況下 TextInputformat 對任務的切片機制是按文件規劃切片,不管文件多小,都會是一個單獨的切片,都會交給一個 maptask,這樣如果有大量小文件,就會產生大量的 maptask,處理效率極其低下。
2.2.1.2、優化策略
- 最好的辦法,在數據處理系統的最前端(預處理/採集),將小文件先合併成大文件,再上傳到 HDFS 做後續分析。
- 補救措施:如果已經是大量小文件在 HDFS 中了,可以使用另一種 InputFormat來做切片(CombineTextInputFormat),它的切片邏輯跟 TextFileInputFormat 不同:它可以將多個小文件從邏輯上規劃到一個切片中,這樣,多個小文件就可以交給一個 maptask。
- 優先滿足最小切片大小,不超過最大切片大小
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m
舉例:0.5m+1m+0.3m+5m=2m + 4.8m=2m + 4m + 0.8m
具體實現步驟
// 如果不設置 InputFormat,它默認用的是 TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class)
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m
因爲Hadoop設計就是適合大文件,不適合小文件,所以應該儘量避免大量小文件,不生成小文件。
總之,處理思路就是將小文件合併。
2.2.2、說明
- 是對集羣中大量小文件的優化策略
- 文件:在hdfs中
- 它可以將多個小文件從邏輯上規劃到一個切片中,這樣,多個小文件就可以交給一個maptask
- 設置切片大小原則(先滿足minsp1it,再滿足maxSplit)job.setInputFormatclass(CombineTextInputFormat.class)
SPLIT_MAXSIZE=Long.MAX_VALUE SPLIT_MINSIZE=1
CombineTextInputFormat.setMaxInputSplitsize(job,12810241024);//最大
合併時,考慮:
1)n個小文件,合併成【1】個文件
job.setInputFormatClass(CombineTextInputFormat.class)
默認:TextInputFormat
2)合併,希望每個合併後的文件是128M
job.setInputFormatClass(CombineTextInputFormat.class)
CombineTextInputFormat.setMaxInputSplitSize(job, 128*1024*1024); //最大
2.2.3、案例
代碼如下:
package com.biubiubiu.demo;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.CombineTextInputFormat;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
/**
* 合併追加文件
* /input/1/2 中有多個文件
* [hadoop01@biubiubiu01 hadoop_data]$ hadoop jar MapredTest-1.0-SNAPSHOT.jar com.biubiubiu.demo.TestSplit2 /input/1/2 /output/split
* [hadoop01@biubiubiu01 hadoop_data]$ hdfs dfs -cat /output/split/*
*/
public class TestSplit2 {
static class CustomMapper extends Mapper<Object, Text,Text, NullWritable>{
Text text= new Text();
@Override
protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
text.set(value.toString());
context.write(text,NullWritable.get());
context.getInputSplit().getLength();
context.getInputSplit().getLocationInfo();
context.getInputSplit().getLocations();
}
}
//第3部分,編寫Driver(main方法)
public static void main(String[] args) {
try{
// if (args.length<2){
// System.out.println("至少2個參數");
// System.exit(0);
// }
//1)創建Configuration對象,指明namespace的路徑
Configuration conf = new Configuration();
conf.set("dfs.defaultFS","hdfs://192.168.153.231:9000");
//
//2)創建Job
Job job =Job.getInstance(conf,"CombineTextInputFormat");
job.setJarByClass(TestSplit2.class);
//3)自定義Mapper進行輸出參數(key,value)的配置
job.setMapperClass(CustomMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
job.setNumReduceTasks(0);
//4)小文件優化
job.setInputFormatClass(CombineTextInputFormat.class);
CombineTextInputFormat.setMaxInputSplitSize(job, 40*1024*1024); //最大
//5)配置處理的文件的路徑(input)以及處理結果存放的路徑(output)
FileInputFormat.setInputPaths(job,new Path("d_in"));//"args[0]"
FileOutputFormat.setOutputPath(job,new Path("d_out/3"));//"args[1]"
//6)讓程序執行
boolean result=job.waitForCompletion(true);
if(result){
System.out.println("執行正確!!!");
}else{
System.out.println("執行失敗.....");
}
}catch(Exception ex){
System.out.println("執行出錯:"+ex.getMessage());
ex.printStackTrace();
}
}
}
- 不使用合併優化,沒有進行小文件合併前,會切割爲4個
- 使用合併,但未指定每片的最大值,會切割爲1個,一個maptask處理
- 設定了每個文件的大小(最大值),按照最大值合併切割
執行結果如下:
3、程序過程分析
3.1、Task任務分析
3.1.1、Maplask工作原理
1)MapTask的並行度決定map階段的任務處理併發度,進而影響到整個job的處理速度.
2)一個job的map階段MapTask並行度(個數),由客戶端提交job時的切片個數決定
3.1.2、ReduceTask工作原理
1)設置ReduceTask並行度(個數)reducetask的並行度同樣影響整個job的執行併發度和執行效率,但與maptask的併發數由切片數決定不同,Reducetask數量的決定是可以直接手動設置:
//默認值是1,手動設置爲4
job.setNumReduceTasks(4);
2)規則:
(1)reducetask=0,表示沒有reduce階段,輸出文件個數和map個數一致。
(2)reducetask默認值就是1,所以輸出文件個數爲一個。
(3)如果數據分佈不均勻,就有可能在reduce階段產生數據傾斜(4)reducetask數量並不是任意設置,還要考慮業務邏輯需求,有些情況下,需要計算全局彙總結果,就只能有1個
reducetask。
(5)具體多少個reducetask,需要根據集羣性能而定。
(6)如果分區數不是1,但是reducetask爲1,是否執行分區過程。
答案是:不執行分區過程。因爲在maptask的源碼中,執行分區的前提是先判斷reduceNum個數是否大於1。
不大於1肯定不執行。
3.2、自定義分區Partition
3.2.1、使用場合
使用場景:要求將統計結果按照條件輸出到不同文件中(分區)。
比如:將統計結果按照年份不同輸出到不同文件中(分區)
3.2.2、自定義分區步驟
1)自定義分區類,重寫getPartition方法
//自定義分區類
public static class Custompartitioner extends Partitioner<Intwritable,Doublewritable>{
@override
public int getpartition(Intwritable intwritable,Doublewritable doublewritable,int numpartitions){
int year=intwritable.get);
if(year==2016){
return 0;
}else if(year==2017){
return 1;
}else if(year==2018){
return 2;
}else{
return 3;
}
}
}
3.3、實現本地化合井Combiner
3.3.1、Combiner原因
1)combiner:每一個maptask的輸出進行局部彙總,以減小網絡傳輸量
2)combiner組件的父類就是Reducer
3.3.2、Combiner與Reducer的區別
【所處的執行位置不同】
Combiner是在每一個maptask所在的節點運行(本地執行);
Reducer是接收全局所有Mapper的輸出結果;
3.3.3、Combiner案例
1)自定義Combiner
2)給job配置Combiner
3.4、自定義排序TopN
3.5、自定義分組GroupingComparator
3.6、多文件的Join操作
4、輸出端Outputformat
未完待續~
覺得有幫助的,請多多支持博主,點贊關注哦~