MapReduce--Input與Output規則

韓家小志發表於2020-12-04

1、MR五大階段

  • Input
    • 如果讓你寫一個負責讀取的類?
    • 實現讀取Mysql的資料:JDBC
    • 實現讀取HDFS:HDFS JavaAPI
  • Map:自定義
  • Shuffle:分割槽、排序、分組、Combiner
  • Reduce:自定義
  • Output
    • 如果讓你寫一個負責輸出的類?
    • 將Reduce處理好的資料寫入Mysql:JDBC
    • 將Reduce處理好的資料寫入HDFS:HDFS Java API

2、Input規則

所有的輸入都由Input的類來決定

job.setInputFormatClass(TextInputFormat.class)
  • 預設的Input類:TextInputFormat
    • 所有的Input類都要繼承自InputFormat
  • 本質上:就是將讀取的API進行了封裝
    • 如果我們要自定義,就要基於封裝的結構來填充API而已
  • MapReduce預設提供了一些封裝好的用於輸入的類
    • TextInputFormat:用於讀取HDFS檔案,返回一個KV
      • key:檔案的每一行對應個的偏移量
      • Value:這一行的內容
    • DBInputFormat:用於讀取MySQL中的資料,返回一個KV
      • Sqoop:用於實現基於MapReduce讀寫MySQL
    • TableInputFormat:用於讀取Hbase表中的資料
  • 功能:所有的InputFormat都要實現這兩個功能
    • 1-將讀取到的資料進行分片
    • 2-將每個分片的資料轉換為KeyValue

以TextInpuFomat為例

自帶的一些方法

  • createRecordReader:建立一個讀取器
    • 所有的InputFormat必須呼叫讀取器來真正實現資料的讀取轉為KV
    • 讀取器:真正負責讀取資料的類
      • LineRecordReader:一行一行的讀取檔案,並轉換為KV
  • isSplitable:當前讀取的資料是否可以分割【決定壓縮型別是否可以分割構建多個分片】

資料是如何分片的?

  • getSplits:TextInpuFormat呼叫父類FileInputFormat來實現了分片
  • splitSize:決定了分割的大小
    計算公式:computeSplitSize(blockSize, minSize, maxSize)
return Math.max(minSize, Math.min(maxSize, blockSize));
​
Max(最小分片數,Min(最大分片數,塊的大小))
|
Max(1,Min(256M,128M)) = 128M

最終的常見的規律:HDFS上一個檔案塊 = 一個分片 = 啟動一個MapTask

  • 假設要處理的檔案是300M
HDFS :	128M	128M	44M
Input: split1	split2 split3
Map: MapTask1 MapTask2 MapTask3
  • 設計的目的
    • 因為在HDFS中已經分好了
    • 只需要讓MapReduce根據分塊的大小,每個MapTask處理一個分塊的大小
    • 避免了HDFS上資料的再合併再分割

特殊情況:假設檔案130M

  • HDFS: 128M 2M
  • 允許有10%的溢位:如果檔案大小超過分片大小10%以內,作為一個分片
    • 130 / 128 > 1.1
  • 只要檔案小於140M,就會作為1個分片進行處理
    • Input: split1
    • Map: MapTask
    • 設計目的:避免一個MapTask處理的資料太小
  • minSize:最小分片數
    • 屬性: mapreduce.input.fileinputformat.split.minsize = 0
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
|
minSize = 1
  • maxSize:最大分片數
    • 屬性:mapreduce.input.fileinputformat.split.maxsize = 256M對應的位元組數
long maxSize = getMaxSplitSize(job);
|
maxSize = 256M

每個分片的資料如何轉換為KeyValue?

  • 每種InputFormat都要構建一個讀取器
  • 每個讀取器中:都有一個nextKeyValue,用於將讀取到分片的資料轉為KV

3、Output規則

  • 所有輸出都由Output的類來決定
job.setOutputFormatClass(TextOutputFormat.class);
  • 預設的Output類:TextOutputFormat
    • 所有的Output的類都要繼承自OutputFormat
  • 本質上:就是將寫入API進行了封裝而已
    • 如果我們要自定義,就要基於封裝的結構來填充API而已
  • MapReduce預設提供了一些封裝好的用於輸出的類
    • TextOutputFormat:用於將結果寫入HDFS
    • DBOutputFormat:用於將結果寫入MySQL
    • TableOutputFormat:用於將結果寫入Hbase
  • 功能將結果儲存

4、自定義一個Input

問題:如果我們要處理的資料都是很多小檔案怎麼辦?

  • MapReduce是不適合於處理小檔案
    • file1:2KB
    • file2:2KB
  • 一個檔案就會作為一個分片,兩個小檔案就有兩個分片,就會啟動兩個MapTask
  • 這樣是及其浪費資源,可能處理的時間還沒啟動的時間長
  • 解決:自定義一個讀取器,將每個檔案的內容作為一個KV傳遞給Map,將所有檔案合併輸出成為一個檔案
  • 需求:自定義一個InputFormat:用於將每個小檔案的內容作為一個KV
    • 將每個檔案和內容合併為一個檔案:構建一個檔案集合
    • SequenceFIle:這種檔案用於儲存多個檔案,裡面的每條資料KV就是一個檔案,記錄檔案的名稱檔案的內容
    • MapReduce中的有SquenceOutputFormat
    • 要求輸出的Key必須為Text型別的檔名
    • 要求輸出的Value必須為Byteswritable型別的檔案資料
    • 每一個檔案在SequenceFIle中都是一個KV

實現

  • 自定義InputFormat
package bigdata.hanjiaxiaozhi.cn.mr.input;import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.JobContext;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import java.io.IOException;/**
 * @ClassName MRUserInputFormat
 * @Description TODO 自定義的輸入的類,用於將每個檔案的資料變成一個KV
 * K;不儲存任何東西
 * V:每個檔案的資料作為一個Value
 * @Date 2020/6/2 15:28
 * @Create By     hanjiaxiaozhi
 */
public class MRUserInputFormat  extends FileInputFormat<NullWritable, BytesWritable> {/**
     * 返回一個讀取器,實現資料的轉換
     * @param split
     * @param context
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public RecordReader<NullWritable, BytesWritable> createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
        //構建讀取器
        MRUserRecordReader mrUserRecordReader = new MRUserRecordReader();
        //呼叫初始化方法
        mrUserRecordReader.initialize(split,context);
        //返回讀取器
        return mrUserRecordReader;
    }/**
     * 是否可分割
     * @param context
     * @param filename
     * @return
     */
    @Override
    protected boolean isSplitable(JobContext context, Path filename) {
        //一個檔案作為一個KV ,不分割
        return false;
    }
}
  • 自定義讀取器
package bigdata.hanjiaxiaozhi.cn.mr.input;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;import java.io.IOException;/**
 * @ClassName MRUserRecordReader
 * @Description TODO 自定義的讀取器,用於實現將每個分片【就是每個檔案】變成一個KV返回
 *
 * 通過HDFSAPI,讀取檔案資料,封裝成KV
 *  將每個檔案的內容做Value返回
 *
 * @Date 2020/6/2 15:34
 * @Create By     hanjiaxiaozhi
 */
public class MRUserRecordReader extends RecordReader<NullWritable, BytesWritable> {
    //構建需要返回的KV
    NullWritable key = NullWritable.get();
    BytesWritable value = new BytesWritable();//將每個檔案的內容作為這個物件的值
    //構建全域性的Conf物件
    Configuration conf = null;
    //構建全域性的分片資訊
    FileSplit fileSplit = null;
    //設定標誌變數
    boolean flag = false;
​
​
    /**
     * 初始化方法,只被呼叫一次
     * @param split:分片中記錄了這個分片的對應的檔案資訊
     * @param context:上下文物件,獲取到當前程式的conf物件
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
        conf = context.getConfiguration();
        fileSplit = (FileSplit) split;
    }/**
     * 這個方法,用於獲取分片【就是一個檔案】中的資料,將分片的資料變成KV
     *      這個方法的返回值
     *          true:還有下一條,儲存當前條,繼續對下一條進行處理
     *          false:沒有下一條了
     *          注意:每個分片第一次被呼叫 時,必須返回true,不然當前KV會被丟掉
     *          由於我們一個分片就是一個檔案,只構建一個KV,第二次必須返回false
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public boolean nextKeyValue() throws IOException, InterruptedException {
        //todo:核心的目標,讀取當前分片就是檔案的內容,塞到BytesWritable中,讓value有值
        if(!flag){
            //構建一個HDFS檔案系統
            FileSystem hdfs = FileSystem.get(conf);
            //開啟當前這個分片的檔案
            FSDataInputStream open = hdfs.open(fileSplit.getPath());
            //將這個輸入流【這個小檔案的資料】放入位元組陣列中
            byte[] bytes = new byte[(int) fileSplit.getLength()];
            IOUtils.readFully(open,bytes,0, (int) fileSplit.getLength());
            //給Value賦值:讓Value得到這個檔案的所有資料
            this.value.set(bytes,0, (int) fileSplit.getLength());
            //關閉資源
            open.close();
            hdfs.close();
            //修改標記
            flag = true;
            //返回
            return true;
        }
        //第二次返回false,表示整個分片讀取完畢
        return false;
    }//返回Key
    @Override
    public NullWritable getCurrentKey() throws IOException, InterruptedException {
        return key;
    }//返回Value
    @Override
    public BytesWritable getCurrentValue() throws IOException, InterruptedException {
        return value;
    }//獲取進度方法
    @Override
    public float getProgress() throws IOException, InterruptedException {
        return 0;
    }//用於關閉釋放資源
    @Override
    public void close() throws IOException {}
}
  • MR
package bigdata.hanjiaxiaozhi.cn.mr.input;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.LongWritable;
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.FileSplit;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;import java.io.IOException;/**
 * @ClassName MRDriver
 * @Description TODO 實現將多個小檔案合併為一個SequenceFIle
 * @Date 2020/5/30 10:34
 * @Create By     hanjiaxiaozhi
 */
public class MRUserInput extends Configured implements Tool {/**
     * 用於將Job的程式碼封裝
     * @param args
     * @return
     * @throws Exception
     */
    @Override
    public int run(String[] args) throws Exception {
        //todo:1-構建一個Job
        Job job = Job.getInstance(this.getConf(),"model");//構建Job物件,呼叫父類的getconf獲取屬性的配置
        job.setJarByClass(MRUserInput.class);//指定可以執行的型別
        //todo:2-配置這個Job
        //input
        job.setInputFormatClass(MRUserInputFormat.class);//設定輸入的類的型別,預設就是TextInputFormat
        Path inputPath = new Path("datas/inputformat");//用程式的第一個引數做為第一個輸入路徑
        //設定的路徑可以給目錄,也可以給定檔案,如果給定目錄,會將目錄中所有檔案作為輸入,但是目錄中不能包含子目錄
        MRUserInputFormat.setInputPaths(job,inputPath);//為當前job設定輸入的路徑//map
        job.setMapperClass(MRMapper.class);//設定Mapper的類,需要呼叫對應的map方法
        job.setMapOutputKeyClass(Text.class);//設定Mapper輸出的key型別
        job.setMapOutputValueClass(BytesWritable.class);//設定Mapper輸出的value型別//shuffle
//        job.setPartitionerClass(HashPartitioner.class);//自定義分割槽
//        job.setGroupingComparatorClass(null);//自定義分組的方式
//        job.setSortComparatorClass(null);//自定義排序的方式//reduce
//        job.setReducerClass(MRReducer.class);//設定Reduce的類,需要呼叫對應的reduce方法
        job.setOutputKeyClass(Text.class);//檔名
        job.setOutputValueClass(BytesWritable.class);//檔案內容
        job.setNumReduceTasks(1);//設定ReduceTask的個數,預設為1//output:輸出目錄預設不能提前存在
        job.setOutputFormatClass(SequenceFileOutputFormat.class);//設定輸出的類,預設我誒TextOutputFormat
        Path outputPath = new Path("datas/output/inputformat");//用程式的第三個引數作為輸出
        //解決輸出目錄提前存在,不能執行的問題,提前將目前刪掉
        //構建一個HDFS的檔案系統
        FileSystem hdfs = FileSystem.get(this.getConf());
        //判斷輸出目錄是否存在,如果存在就刪除
        if(hdfs.exists(outputPath)){
            hdfs.delete(outputPath,true);
        }
        SequenceFileOutputFormat.setOutputPath(job,outputPath);//為當前Job設定輸出的路徑//todo:3-提交執行Job
        return job.waitForCompletion(true) ? 0:-1;
    }/**
     * 程式的入口,呼叫run方法
     * @param args
     */
    public static void main(String[] args) throws Exception {
        //構建一個Configuration物件,用於管理這個程式所有配置,工作會定義很多自己的配置
        Configuration conf = new Configuration();
        //t通過Toolruner的run方法呼叫當前類的run方法
        int status = ToolRunner.run(conf, new MRUserInput(), args);
        //退出程式
        System.exit(status);
    }
​
​
    /**
     * @ClassName MRMapper
     * @Description TODO 這是MapReduce模板的Map類
     *      輸入的KV型別:由inputformat決定,預設是TextInputFormat
     *      輸出的KV型別:由map方法中誰作為key,誰作為Value決定
     */
    public static class MRMapper extends Mapper<NullWritable, BytesWritable, Text,BytesWritable> {
​
        Text outputKey = new Text();/**
         * 通過自定義的InputFormat返回的型別
         * @param key
         * @param value:每一個Value,就是每一個檔案的所有內容
         * @param context
         * @throws IOException
         * @throws InterruptedException
         */
        @Override
        protected void map(NullWritable key, BytesWritable value, Context context) throws IOException, InterruptedException {
            //檔名作為Key
            FileSplit fileSplit = (FileSplit) context.getInputSplit();
            //獲取這條資料對應的檔名
            String name = fileSplit.getPath().getName();
            this.outputKey.set(name);
            //檔案的內容作為value
            //輸出
            context.write(this.outputKey,value);
        }
    }
​
​
​
    /**
     * @ClassName MRReducer
     * @Description TODO MapReduce模板的Reducer的類
     *      輸入的KV型別:由Map的輸出決定,保持一致
     *      輸出的KV型別:由reduce方法中誰作為key,誰作為Value決定
     */
    public static class MRReducer extends Reducer<NullWritable,NullWritable,NullWritable,NullWritable> {
        @Override
        protected void reduce(NullWritable key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {/**
             * 實現reduce處理的邏輯
             */
        }
    }
​
​
}

5、自定義一個Output

  • 需求:將給定的資料按照差評和好評拆分到不同的檔案中
  • 自定義分割槽能不能實現?
    • 可以
  • 自定義OutputFormat
package bigdata.hanjiaxiaozhi.cn.mr.output;import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import java.io.IOException;
​
​
/**
 * @ClassName MRUserOutputFormat
 * @Description TODO 自定義一個輸出的類
 * @Date 2020/6/2 16:16
 * @Create By     hanjiaxiaozhi
 */
public class MRUserOutputFormat extends FileOutputFormat<Text, NullWritable> {/**
     * 返回一個輸出器物件
     * @param context
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public RecordWriter<Text, NullWritable> getRecordWriter(TaskAttemptContext context) throws IOException, InterruptedException {
        //構建HDFS物件
        FileSystem hdfs = FileSystem.get(context.getConfiguration());
        //構建兩個輸出流
        FSDataOutputStream badContent = hdfs.create(new Path("datas/output/badcontent/bad.txt"));
        FSDataOutputStream goodContent = hdfs.create(new Path("datas/output/goodcontent/good.txt"));
        //構建輸出器物件
        MrUserRecordWriter mrUserRecordWriter = new MrUserRecordWriter(badContent,goodContent);
        return mrUserRecordWriter;
    }
}

  • 自定義一個輸出器:RecordWrite
package bigdata.hanjiaxiaozhi.cn.mr.output;import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;import java.io.IOException;/**
 * @ClassName MrUserRecordWriter
 * @Description TODO 負責真正實現將資料進行儲存
 * @Date 2020/6/2 16:17
 * @Create By     hanjiaxiaozhi
 */
public class MrUserRecordWriter extends RecordWriter<Text, NullWritable> {
    FSDataOutputStream badContent = null;
    FSDataOutputStream goodContent = null;public MrUserRecordWriter(FSDataOutputStream badContent, FSDataOutputStream goodContent) {
        this.badContent = badContent;
        this.goodContent = goodContent;
    }/**
     * 真正負責將每條keyvalue輸出的方法
     * 只需要在這個方法中構建輸出的API,將每條KeyValue輸出即可
     * @param key
     * @param value
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public void write(Text key, NullWritable value) throws IOException, InterruptedException {
        //獲取資料中的評價
        String content = key.toString().split("\t")[9];
        //如果是差評,就寫入一個檔案
        if("2".equals(content)){
            badContent.write(key.toString().getBytes());
            badContent.write("\r\n".getBytes());//新增換行
        }else {
            //如果不是差評,寫入另外一個檔案
            goodContent.write(key.toString().getBytes());
            goodContent.write("\r\n".getBytes());
        }
    }/**
     * 釋放資源的方法,最後執行的方法
     * @param context
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public void close(TaskAttemptContext context) throws IOException, InterruptedException {
        badContent.close();
        goodContent.close();
    }
}
  • MR實現
package bigdata.hanjiaxiaozhi.cn.mr.output;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;import java.io.IOException;/**
 * @ClassName MRDriver
 * @Description TODO 實現使用者自定義輸出
 * @Date 2020/5/30 10:34
 * @Create By     hanjiaxiaozhi
 */
public class MRUserOutput extends Configured implements Tool {/**
     * 用於將Job的程式碼封裝
     * @param args
     * @return
     * @throws Exception
     */
    @Override
    public int run(String[] args) throws Exception {
        //todo:1-構建一個Job
        Job job = Job.getInstance(this.getConf(),"model");//構建Job物件,呼叫父類的getconf獲取屬性的配置
        job.setJarByClass(MRUserOutput.class);//指定可以執行的型別
        //todo:2-配置這個Job
        //input
//        job.setInputFormatClass(TextInputFormat.class);//設定輸入的類的型別,預設就是TextInputFormat
        Path inputPath = new Path("datas/outputformat/ordercomment.csv");//用程式的第一個引數做為第一個輸入路徑
        //設定的路徑可以給目錄,也可以給定檔案,如果給定目錄,會將目錄中所有檔案作為輸入,但是目錄中不能包含子目錄
        TextInputFormat.setInputPaths(job,inputPath);//為當前job設定輸入的路徑//map
        job.setMapperClass(MRMapper.class);//設定Mapper的類,需要呼叫對應的map方法
        job.setMapOutputKeyClass(Text.class);//設定Mapper輸出的key型別
        job.setMapOutputValueClass(NullWritable.class);//設定Mapper輸出的value型別//shuffle
//        job.setPartitionerClass(HashPartitioner.class);//自定義分割槽
//        job.setGroupingComparatorClass(null);//自定義分組的方式
//        job.setSortComparatorClass(null);//自定義排序的方式//reduce
        job.setReducerClass(MRReducer.class);//設定Reduce的類,需要呼叫對應的reduce方法
        job.setOutputKeyClass(Text.class);//設定Reduce輸出的Key型別
        job.setOutputValueClass(NullWritable.class);//設定Reduce輸出的Value型別
        job.setNumReduceTasks(1);//設定ReduceTask的個數,預設為1//output:輸出目錄預設不能提前存在
        job.setOutputFormatClass(MRUserOutputFormat.class);//設定輸出的類,預設我誒TextOutputFormat
        Path outputPath = new Path("datas/output/outputformat");//用程式的第三個引數作為輸出
        //解決輸出目錄提前存在,不能執行的問題,提前將目前刪掉
        //構建一個HDFS的檔案系統
        FileSystem hdfs = FileSystem.get(this.getConf());
        //判斷輸出目錄是否存在,如果存在就刪除
        if(hdfs.exists(outputPath)){
            hdfs.delete(outputPath,true);
        }
        MRUserOutputFormat.setOutputPath(job,outputPath);//為當前Job設定輸出的路徑//todo:3-提交執行Job
        return job.waitForCompletion(true) ? 0:-1;
    }/**
     * 程式的入口,呼叫run方法
     * @param args
     */
    public static void main(String[] args) throws Exception {
        //構建一個Configuration物件,用於管理這個程式所有配置,工作會定義很多自己的配置
        Configuration conf = new Configuration();
        //t通過Toolruner的run方法呼叫當前類的run方法
        int status = ToolRunner.run(conf, new MRUserOutput(), args);
        //退出程式
        System.exit(status);
    }
​
​
    /**
     * @ClassName MRMapper
     * @Description TODO 這是MapReduce模板的Map類
     *      輸入的KV型別:由inputformat決定,預設是TextInputFormat
     *      輸出的KV型別:由map方法中誰作為key,誰作為Value決定
     */
    public static class MRMapper extends Mapper<LongWritable, Text, Text,NullWritable> {
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            //將每一行的每條資料作為Key
            context.write(value,NullWritable.get());
        }
    }
​
​
​
    /**
     * @ClassName MRReducer
     * @Description TODO MapReduce模板的Reducer的類
     *      輸入的KV型別:由Map的輸出決定,保持一致
     *      輸出的KV型別:由reduce方法中誰作為key,誰作為Value決定
     */
    public static class MRReducer extends Reducer<Text,NullWritable,Text,NullWritable> {
        @Override
        protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
            context.write(key,NullWritable.get());
        }
    }
​
​
}

相關文章