一、MapReduce简单概述


在Hadoop中有两个核心的模块,一个是大数据量文件的存储HDFS,另一个是能够做快速的数据分析,则为MapReduce。


百度百科介绍:




大数据离线数仓架构图 大数据离线计算框架_大数据


二、MapReduce的特点


它适合做“离线”(存储在本地)的海量数据计算,通常计算的数据量在PB级别或者ZB级别


MapReduce的主要特点如下:


  1. 易于编程
  2. 扩展性良好
  3. 高容错性

 


三、MapReduce的应用场景


    MapReduce对于离线的大数据量分析计算还是需要一定时间的,不能实时做计算分析,它的典型应用有以下一些:


  1. 简单的数据统计,比如网站的pv、uv
  2. 搜索引擎建立索引
  3. 在搜索引擎中统计最流行的搜索词
  4. 统计搜索的词频率
  5. 复杂的数据分析算法实现

    MapReduce也有不适用的的场景


        1. 实时计算


        2. 流式计算(MapReduce对于数据集要求是静态的,不能是动态的)


  1. DAG计算,当多个应用程序存在依赖关系,且后一个应用的输入来自于前一个输出这个情况是不适合用MapReduce的

 


四、MapReduce是如何运行的(通过实例来理解这个过程)


        单词统计,分析的过程如下图所示


        

大数据离线数仓架构图 大数据离线计算框架_hadoop_02


        图中主要分为五个过程


            Split:把大文件进行切割成多份


            Map:分别针对切出来的小文件,解析出每一个字符并在后面记上数字1


            Shuffle:每一份中的字符进行分组到一起并做统计


            Reduce:把对应的字符进行累加


            Final result:输出最终的结果


    
        上面我们总结出了大致的执行过程,接下来看看具体的执行过程如何


            1. 把数据切割成数据片段


            2. 数据片段以key和value的形式被读进来处理,默认情况是以行的下标作为key,行的内容做为value


            3. 数据传入Map中进行处理(处理逻辑由用户定义),在Map中处理完成后还是以key和value的形式输出


            4. 输出的数据给到Shuffle,它进行数据的排序合并等操作,但是它不会修改传入的数据,这个时候还是key2和value
            5. 数据接下来传给Reduce进行处理,它处理完后,生成key3和value3


            6. Reduce处理完的数据会被写到HDFS的某个目录中


        接下来针对 Split、Map、Reduce及Shuffle做分别的简单介绍


        1. Split(MapReduce的文件切片)


            第一个问题,它按多大的大小进行切片?


            默认与block对应,这个大小也可以由用户自行控制,MapReduce的Split大小计算公式如下: max(min.split, min(max.split, block))


             max.split = totalSize/numSplit(totalSize:文件大小;numSplit:用户设定的map task的个数,默认为1)


            min.split = InputSplit的最小值(可以在配置文件中进行配置参数,mapred.min.split.size,在不配置时默认为1B,block是HDFS中块的大小)


 


        2. Map过程与Reduce过程


            Map和Reduce的实现逻辑都是由程序员完成的.


            Map个数与Split的个数对应起来,一个Split切片对应一个Map任务


            Reduce的默认数是1,这个是可以自行设置的。


            注意:一个程序可能只有一个Map任务却没有Reduce任务,也可能是多个MapReduce程序串连起来,比如,第一个MapReduce的输出结果作为第二个MapReduce的输入,第二个MapReduce的输出又是第三个MapReduce的输入,最终所有的MapReduce完成才完成一个任务。


 


        3. Shuffle过程


            它又叫“洗牌”,它起到了连接Map任务与Reduce任务的作用。


            Shuffle分为两个部分,一部分在Map端,另一部分在Reduce端


            Map处理后的数据会以key、value的形式存在缓冲区中,这个缓冲区大小是128MB,当这个缓冲区快要溢出时(默认为80%),会把数据写到磁盘中生成文件,这个过程叫做溢写操作。


            溢写磁盘的工作由一个线程来完成,溢写之前包括Partition(分区)、Sort(排序)都有默认的实现


            Partition:默认按hash值%reduce数量进行分区


            Sort:默认按字符顺序进行排序


            在完成溢写后,在磁盘上会生成多个文件,多个文件会通过merge线程完成文件合并


            合并完成后的数据(以key和value的形式存在),会基于Partition被发送到不同的Reduce上


 


五、MapReduce实例


 


实例一:对文件中的单词进行统计,字符之间使用空格进行分隔


相关的JAR包


    hadoop-3.1.3\share\hadoop\common 下的jar及hadoop-3.1.3\share\hadoop\common\lib下的jar包


    hadoop-3.1.3\share\hadoop\mapreduce下的jar及hadoop-3.1.3\share\hadoop\mapreduce\lib下的jar包


    hadoop-3.1.3\share\hadoop\hdfs下及hadoop-3.1.3\share\hadoop\hdfs\lib下的jar包


    一个完整的MapReduce分为两个部分:一个是Mapper;另一个是Reducer


    


    Mapper


    它是用来对文件进行切割并且循环输出给到Reducer


    


public class WordCountMapper extends Mapper<LongWritable,Text,Text,IntWritable>{
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //使用空隔进行分词
        String[] str = value.toString().split(" ");
        //for循环处理
        for(int i = 0; i < str.length; i++){
            //new Text,new IntWritable进行可序列化
            context.write(new Text(str[i]),new IntWritable(1));
        }
    }
}

 


    Readucer


    它将从Map转入的词汇进行分组合并,并通过文本和单词统计量的方式输出


public class WordCountReducer extends Reducer<Text,IntWritable,Text,IntWritable> {
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        //数据进行分组合并输出
        int sum = 0;
        for(IntWritable i:values){
            sum = sum+i.get();
        }
        context.write(key,new IntWritable(sum));
    }
}

 


 


    主方法所在类


    主方法中用来连接HDFS、要读取的文件及处理后的文件在HDFS中的路径。同时需要指明我们所要进行Map和Reduce过程的类


public class MRRunJob {
    public static void main(String[] args) {
        Configuration conf = new Configuration();
        //NameNode入口
        conf.set("fs.defaultFS","hdfs://192.168.2.4:8020");
        Job job = null;
        try {
            job = Job.getInstance(conf,"mywc");
        } catch (IOException e) {
            e.printStackTrace();
        }


        //主类
        job.setJarByClass(MRRunJob.class);
        //Mapper类
        job.setMapperClass(WordCountMapper.class);
        //Reducer类
        job.setReducerClass(WordCountReducer.class);
        //Map输出的value类型
        job.setOutputKeyClass(Text.class);
        //Map输出的value类型
        job.setOutputValueClass(IntWritable.class);


        try {
            //读取文件位置
            job.setWorkingDirectory(new Path("/"));
            System.out.println(job.getWorkingDirectory());
            FileInputFormat.addInputPath(job,new Path("/usr/input/data/wc/"));
            //处理完成后数据存储的位置(注意:如果输出文件夹存在则会报错)
            FileOutputFormat.setOutputPath(job,new Path("/usr/output/data/wc/"));
            job.waitForCompletion(true);
        } catch (Exception e) {
            e.printStackTrace();
        }


    }
}

 


注意:上面的类一定是要在HDFS中的路径是否则程序会报错提示找不到文件位置。切记!!


 


实例二:数据清洗ETL实例(对时间进行格式化处理)


Mapper


public class ETLMapper extends Mapper<LongWritable,Text,Text,NullWritable> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //使用,号对文本内容进行分隔
        String[] strArray = value.toString().split(",");
        String strContent = "";


        for(int i=0;i<strArray.length;i++){
            //把下标为第三个数据转换为yyyy-MM-dd HH:mm:ss的时间格式
            if(i == 3){
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                //把原数据的秒转为毫秒
                String str = sdf.format(Long.parseLong(strArray[i]+"000"));
                strContent = strContent + str + ",";
            } else {
                strContent = strContent + strArray[i] + ",";
            }
        }
        context.write(new Text(strContent),NullWritable.get());
    }
}

 


Reducer


public class ETLReducer extends Reducer<Text,NullWritable,NullWritable,Text> {
    @Override
    protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
        //把Mapper传过来的内容写入磁盘
        context.write(NullWritable.get(),key);
    }
}

 


主方法


public class MRRunJob {
    public static void main(String[] args) {
        Configuration conf = new Configuration();
        conf.set("fs.defaultFS","hdfs://192.168.2.4:8020");
        //初始化fs
        FileSystem fs = null;
        try {
            fs = FileSystem.get(conf);
        } catch (IOException e) {
            e.printStackTrace();
        }
        Job job = null;
        try {
            job = Job.getInstance(conf,"mywc");
        } catch (IOException e) {
            e.printStackTrace();
        }
        //主方法
        job.setJarByClass(MRRunJob.class);
        //Mapper方法
        job.setMapperClass(ETLMapper.class);
        //Reducer方法
        job.setReducerClass(ETLReducer.class);
        //Map输出的key类型
        job.setOutputKeyClass(Text.class);
        //Map输出的value类型
        job.setOutputValueClass(NullWritable.class);


        try {
            //读取文件位置
            Path inputPath = new Path("/usr/input/data/etl/");
            //如果这个文件不存在则新增
            if(!fs.exists(inputPath)){
                fs.mkdirs(inputPath);
            }
            //需要把本地的文件上传到HDFS
            Path src = new Path("D:\\IdeaProjects\\MapReduceTest\\ETLDemo\\etl01.txt");
            fs.copyFromLocalFile(src,inputPath);


            FileInputFormat.addInputPath(job,inputPath);


            //处理完成后文件输出位置
            Path outputPath = new Path("/usr/output/data/etl01/");
            //如果这个输出的目录存在则删除
            if(fs.exists(outputPath)){
                fs.delete(outputPath,true);
            }
            FileOutputFormat.setOutputPath(job,outputPath);
            boolean f = job.waitForCompletion(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 


注意:如果在window环境中需要运行成功的话,需要修改原码 NativeIO.java


把这个源码复制到自己的工程当中,并修改如下内容:


把原来的return注释掉,直接reurn true即可


如果不做这个操作,则在运行时会报错: org.apache.hadoop.io.nativeio.NativeIO$Windows.access0(Ljava/lang/String;I)Z



大数据离线数仓架构图 大数据离线计算框架_hadoop_03