MapReduce框架詳解

覺得有幫助的,請多多支持博主,點贊關注哦~

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 中默認的切片機制:

  1. 簡單地按照文件的內容長度進行切片
  2. 切片大小,默認等於 block 大小
  3. 切片時不考慮數據集整體,而是逐個針對每一個文件單獨切片

比如待處理數據有兩個文件:

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、優化策略
  1. 最好的辦法,在數據處理系統的最前端(預處理/採集),將小文件先合併成大文件,再上傳到 HDFS 做後續分析。
  2. 補救措施:如果已經是大量小文件在 HDFS 中了,可以使用另一種 InputFormat來做切片(CombineTextInputFormat),它的切片邏輯跟 TextFileInputFormat 不同:它可以將多個小文件從邏輯上規劃到一個切片中,這樣,多個小文件就可以交給一個 maptask。
  3. 優先滿足最小切片大小,不超過最大切片大小
    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、說明

  1. 是對集羣中大量小文件的優化策略
  2. 文件:在hdfs中
  3. 它可以將多個小文件從邏輯上規劃到一個切片中,這樣,多個小文件就可以交給一個maptask
  4. 設置切片大小原則(先滿足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();
       }


    }
}

  1. 不使用合併優化,沒有進行小文件合併前,會切割爲4個
    在這裏插入圖片描述
  2. 使用合併,但未指定每片的最大值,會切割爲1個,一個maptask處理
    在這裏插入圖片描述
  3. 設定了每個文件的大小(最大值),按照最大值合併切割
    在這裏插入圖片描述

執行結果如下:
在這裏插入圖片描述

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

未完待續~
覺得有幫助的,請多多支持博主,點贊關注哦~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章