Bootstrap

大数据技术之Hadoop学习(四)——MapReduce

目录

一、MapReduce概述

1、MapReduce核心思想

2、MapReduce编程模型

二、MapReduce工作原理

1、分片、格式化数据源

2、执行 MapTask

3、执行Shuffle过程

4、执行RecudeTack

5、写入文件

三、案例

1、词频统计

(1)InputFormat组件

(2)Mapper组件

(3)Reducer组件

(4)Combiner组件

(5)MapReduce的运行模式

2、倒排索引

(1)介绍

(2)Map阶段的实现

(3)Combine阶段实现

(4)Reduce阶段实现

(5)Driver程序主类实现

(6)结果展示

3、数据去重

(1)Map阶段实现

(2)Reduce阶段实现

(3)Driver程序主类实现

(4)结果展现

4、TopN

(1)案例介绍

(2)Map阶段实现

(3)Reduce阶段实现

(4)Driver程序主类实现

(5)结果展示


案例的代码已经放入百度网盘,有需要可自行提取。

http://链接: https://pan.baidu.com/s/1Vcqn7-A5YWOMqhBLpr3I0A?pwd=759w 提取码: 759w

一、MapReduce概述

1、MapReduce核心思想

        MapReduce的核心思想“分而治之”,即将一个任务分解成多个子任务,这些子任务之间没有必然的相互依赖,都可以单独执行,最后再将这些子任务的结果,进行汇总合并。

2、MapReduce编程模型

        MapReduce作为一种编程模型,专门处理大规模数据的并行运算,该模型借鉴了函数式程序设计的思想,程序实现过程是通过map()函数和reduce()函数实现的。使用MapReduce处理计算任务的时候,每个任务都会分成两个阶段,Map阶段和Reduce阶段。

(1)Map阶段:对于原始数据的预处理。

(2)Reduce阶段:将Map阶段的处理结果进行汇总,最后得到最终结果。

        流程说明:第一步,将原始的数据转换成键值对<k1,v1>的形式;第二步,转换后的键值对<k1,v1>导入到map()函数,map()函数根据映射规则,将键值对<k1,v1>映射为一系列中间结果形式的键值对<k2,v2>;第三步,将中间形式的键值对<k2,v2>形成<k2,{v2......}>形式传给reduce()函数处理,把具有相同结果的key的value合并在一起,产生新的键值对<k3,v3>,此时键值对<k3,v3>就是最终的结果。

二、MapReduce工作原理

1、分片、格式化数据源

        输入Map阶段的数据源,需要经过分片和格式操作。

(1)分片操作:将源文件划分为大小相等的小数据块,然后hadoop会为每一个分片构建一个Map任务,由该任务运行自定义的map()函数,从而处理分片里的每一条记录。

(2)格式化操作:将划分好的分片格式化为键值对<key,value>形式的数据,其中key代表偏移量,value代表一行内容。

2、执行 MapTask

(1)Read阶段:Map Task通过用户编写的RecordReader,从输入的InputSplit中解析一个个键值对<k,v>。

(2)Map阶段:将解析出的<k,v>交给用户编写的map函数处理,产生新的键值对<k,v>。

(3)Collect阶段:在用户编写的map函数中,数据处理完后,一般会调用outputCollector.collect()输出结果,在该函数内部生成键值对<k,v>分片,并写如环形内存缓冲区。

(4)Spill阶段:如果环形缓冲区满后,MapReduce会将数据写入到本地磁盘中,生成一个临时文件。这里需要注意,数据写入本地磁盘前,需要对数据进行一次排序,必要时需要对数据进行合并、压缩等操作。

(5)Combine阶段:当所有数据处理完后,Map Task会对所有的临时文件进行一次合并,以却确保最终只会生成一个数据文件。

3、执行Shuffle过程

        Shuffle会将Map Task输出的处理结果数据分发给RecudeTask,并在分发的过程中,对数据按key进行分区和排序。

4、执行RecudeTack

(1)Copy阶段:Recude会从各个MapTask上远程复制一份数据,并针对某一数据,如果其大小超过一定值,则写到磁盘中,否则存放到内存中。

(2)Merge阶段:在远程复制数据的同时,RecudeTack会启动两个后台线程,分别对内存和磁盘上的文件进行合并,防止内存使用过多或磁盘文件过多。

(3)Sort阶段:用户编写reduce()方法输入数据是按 key进行聚集的一组数据。为了 I key 相同的数据聚在一起.Hadoop 采用了基于排序的策略。由于各个 MapTask 已经实现对自己的处理结果进行了局部排序,因此,ReduceTask 只需对所有数据进行一次归井排序即可。

(4)Reduce阶段:对排序后的键值对调用reduce()方法,键相等的键值对调用一次reduce()方法,每次调用会产生零个或多个键值对,最后把这些键值对写入到HDFS中。

(5)Write阶段:reduce()函数将计算结果写入到HDFS中。

5、写入文件

        MapReduce框架会自动把RecudeTack生成的<key,value>传入OutputFormat的write方法,实现文件的写入操作。

三、案例

1、词频统计

        这里通过词频统计这个案例来简单的了解一下MapReduce的相关组件。

(1)InputFormat组件

        该组件主要用于描述输入数据的格式,它提供以下两个功能。

a、数据切分:按照策略将输入的数据切分成诺干个分片,以便确定MapTask的个数以及对应的分片。

b、为Mapper提供输入数据源:给定某个分片,将其解析成一个个键值对<k,v>。

        Hadoop自带一个InputFormat接口,该接口定义代码如下。

public abstract class InputFormat <K, V> {
        public abstract List<InputFormat>getSplits(JobContext context) throws IOException, InterruptedException;
        public abstract RecordReader <K, V> createRecordReader (InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException;
    }

         InputFormat接口定义了getSplits()和createRecordReader()两个方法,getSplits()方法负责将文件切分成为多个分片,createRecordReader()方法负责创建RecordReader对象,用来从分片中获取数据。

(2)Mapper组件

        MapReduce程序会根据输入的文件产生多个map任务,Mapper类实现Map任务的一个抽象类,该类提供一个map()方法,默认情况下,该方法是没有任何处理,这时我们可以自定义map()方法,继承Mapper类并重写map()方法。

        接下来我们以词频统计为例,自定义map()方法,代码如下。

package cn.itcast.mr.wordcount;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
    @Override
    protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException {
        //接收传入进来的一行文本,并转换成String类型
        String line = value.toString();

        //将这行内容按分隔符空格切割成单词,保存在String数组中
        String[] words = line.split(" ");

        //遍历数组,产生<K2,V2>键值对,形式为<单词,1>
        for (String word : words
             ) {
            //使用context,把map阶段处理的数据发送给reduce阶段作为输入数据
            context.write(new Text(word), new IntWritable(1));
        }
    }
}

(3)Reducer组件

        Map过程输出的键值对,将由Reducer组件进行合并处理。这里以词频统计为例,自定义reduce()方法。

package cn.itcast.mr.wordcount;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
        //定义一个计数器
        int count = 0;

        //遍历一组迭代器,把每一个数量1累加起来构成了单词出现的总次数
        for (IntWritable iw : values
             ) {
            count +=iw.get();
        }

        //向上下文context写入<k3,v3>
        context.write(key, new IntWritable(count));
    }
}

(4)Combiner组件

        该组件的作用是对Map阶段的输出重复数据先做一次合并计算,然后把新的键值对作为Reduce阶段的输入。如果想要自定义Combiner类,需要继承Reducer类,并重写reduce()方法,具体代码如下。

package cn.itcast.mr.wordcount;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class WordCountCombiner extends Reducer<Text, IntWritable, Text, IntWritable> {
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        //局部汇总
        //定义一个计数器
        int count = 0;

        //遍历一组迭代器,把每一个数量1累加起来构成了单词出现的总次数
        for (IntWritable v : values
             ) {
            count += v.get();
        }

        //向上下文context写入<k3,v3>
        context.write(key, new IntWritable(count));
    }
}

(5)MapReduce的运行模式

        MapReduce的运行模式分为两种,本地运行模式和集群运行模式两种。

a、本地运行模式:在当前开发环境模拟MapReduce的运行环境,处理数据的输出结果都在本地。

b、集群运行模式:把MapReduce程序打成jar包,上传到Yarn集群运行,处理的数据和结果都在HDFS上。

        这里我们主要讲本地运行模式,要实现本地的运行,我们还需要一个Driver类,代码如下。

package cn.itcast.mr.wordcount;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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;

import java.io.IOException;

public class WordCountDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        //通过 Job 来封装本次 MR 的相关信息
        Configuration conf = new Configuration();
        //配置MR运行模式,使用 local 表示本地模式,可以省略
        conf.set("mapreduce.framework.name","local");
        //获取 Job 运行实例
        Job wcjob = Job.getInstance(conf);
        //指定 MR Job jar运行主类
        wcjob.setJarByClass(WordCountDriver.class);
        //指定本次 MR 所有的 Mapper Combiner Reducer类
        wcjob.setMapperClass(WordCountMapper.class);
        wcjob.setCombinerClass(WordCountCombiner.class); //不指定Combiner的话也不影响结果
        wcjob.setReducerClass(WordCountReducer.class);
        //设置业务逻辑 Mapper 类的输出 key 和 value 的数据类型
        wcjob.setMapOutputKeyClass(Text.class);
        wcjob.setMapOutputValueClass(IntWritable.class);

        //设置业务逻辑 Reducer 类的输出 key 和 value 的数据类型
        wcjob.setOutputKeyClass(Text.class);
        wcjob.setOutputValueClass(IntWritable.class);

        //使用本地模式指定要处理的数据所在的位置
        FileInputFormat.setInputPaths(wcjob,"/home/huanganchi/Hadoop/实训项目/HadoopDemo/textHadoop/WordCount/input");
        //使用本地模式指定处理完成后的结果所保持的位置
        FileOutputFormat.setOutputPath(wcjob,new Path("/home/huanganchi/Hadoop/实训项目/HadoopDemo/textHadoop/WordCount/output"));

        //提交程序并且监控打印程序执行情况
        boolean res = wcjob.waitForCompletion(true);
        System.exit(res ? 0:1);
    }
}

         当我们运行完后,会在本地生成一个结果文件。

2、倒排索引

(1)介绍

        倒排索引是文档检索系统的中常用数据格式结构,被广泛应用于全文搜索引擎。可以简单的理解为根据内容来查找文档,而不是根据文档来查找内容。

(2)Map阶段的实现

package cn.itcast.mr.invertedIndex;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.FileSplit;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

public class InvertedIndexMapper extends Mapper<LongWritable, Text, Text, Text> {
    //存储单词和文档名称
    private static Text keyInfo = new Text();

    // 存储词频,初始化为1
    private static final Text valueInfo = new Text("1");

    /*
     * 在该方法中将K1、V1转换为K2、V2
     * key: K1行偏移量
     * value: V1行文本数据
     * context: 上下文对象
     * 输出: <MapReduce:file3 "1">
     */

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();

        // 得到单词数组
        String[] fields = line.split(" ");

        //得到这行数据所在的文件切片
        FileSplit fileSplit = (FileSplit) context.getInputSplit();

        //根据文件切片得到文件名
        String filename = fileSplit.getPath().getName();

        for (String field : fields
             ) {
            // key值由单词和文件名组成,如“MapReduce:file1”
            keyInfo.set(field + ":" + filename);
            context.write(keyInfo, valueInfo);
        }
    }
}

(3)Combine阶段实现

package cn.itcast.mr.invertedIndex;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class InvertedIndexCombiner extends Reducer<Text, Text, Text, Text> {
    private static Text info = new Text();
    // 输入: <MapReduce:file3 {1,1,...}>
    // 输出:<MapReduce file3:2>
    @Override
    protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        int sum = 0;  //统计词频
        //遍历一组迭代器,把每一个数量1累加起来构成了单词出现的总次数
        for (Text value : values) {
            sum += Integer.parseInt(value.toString());
        }
        int splitIndex = key.toString().indexOf(":");
        // 重新设置 value 值由文件名和词频组成
        info.set(key.toString().substring(splitIndex + 1) + ":" + sum);
        // 重新设置 key 值为单词
        key.set(key.toString().substring(0, splitIndex));

        //向上下文context写入<k3,v3>
        context.write(key, info);
    }
}

(4)Reduce阶段实现

package cn.itcast.mr.invertedIndex;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class InvertedIndexReducer extends Reducer<Text, Text, Text, Text> {
    private static Text result = new Text();

    // 输入:<MapReduce, file3:2>
    // 输出:<MapReduce, file1:1;file2:1;file3:2;>
    @Override
    protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        // 生成文档列表
        StringBuffer fileList = new StringBuffer();
        for (Text value : values) {
            fileList.append(value.toString() + ";");
        }
        result.set(fileList.toString());
        context.write(key, result);
    }
}

(5)Driver程序主类实现

package cn.itcast.mr.invertedIndex;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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;

import java.io.IOException;

public class InvertedIndexDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        //通过 Job 来封装本次 MR 的相关信息
        Configuration conf = new Configuration();
        //获取 Job 运行实例
        Job job = Job.getInstance(conf);
        //指定 MR Job jar运行主类
        job.setJarByClass(InvertedIndexDriver.class);
        //指定本次 MR 所有的 Mapper Combiner Reducer类
        job.setMapperClass(InvertedIndexMapper.class);
        job.setCombinerClass(InvertedIndexCombiner.class);
        job.setReducerClass(InvertedIndexReducer.class);
        //设置业务逻辑 Mapper 类的输出 key 和 value 的数据类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);

        //设置业务逻辑 Reducer 类的输出 key 和 value 的数据类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);

        //使用本地模式指定要处理的数据所在的位置
        FileInputFormat.setInputPaths(job,"/home/huanganchi/Hadoop/实训项目/HadoopDemo/textHadoop/InvertedIndex/input");
        //使用本地模式指定处理完成后的结果所保持的位置
        FileOutputFormat.setOutputPath(job,new Path("/home/huanganchi/Hadoop/实训项目/HadoopDemo/textHadoop/InvertedIndex/output"));

        //提交程序并且监控打印程序执行情况
        boolean res = job.waitForCompletion(true);
        System.exit(res ? 0:1);
    }
}

(6)结果展示

3、数据去重

(1)Map阶段实现

package cn.itcast.mr.dedup;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

public class DedupMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
    private static Text field = new Text();
    @Override
    protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, NullWritable>.Context context)
            throws IOException, InterruptedException {
        field = value;
        context.write(field,NullWritable.get());
    }
}

(2)Reduce阶段实现

package cn.itcast.mr.dedup;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class DeupReducer extends Reducer<Text, NullWritable, Text, NullWritable> {
    @Override
    protected void reduce(Text key, Iterable<NullWritable> values, Reducer<Text, NullWritable, Text, NullWritable>.Context context)
            throws IOException, InterruptedException {
        context.write(key, NullWritable.get());
    }
}

(3)Driver程序主类实现

package cn.itcast.mr.dedup;

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.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class DedupDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException{
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        job.setJarByClass(DedupDriver.class);
        job.setMapperClass(DedupMapper.class);
        job.setReducerClass(DeupReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NullWritable.class);
        FileInputFormat.setInputPaths(job, new Path("/home/huanganchi/Hadoop/实训项目/HadoopDemo/textHadoop/Dedup/input"));
        FileOutputFormat.setOutputPath(job, new Path("/home/huanganchi/Hadoop/实训项目/HadoopDemo/textHadoop/Dedup/output"));

        //job.waitForCompletion(true);
        boolean res = job.waitForCompletion(true);
        if (res) {
            FileReader fr = new FileReader("/home/huanganchi/Hadoop/实训项目/HadoopDemo/textHadoop/Dedup/output/part-r-00000");
            BufferedReader reader= new BufferedReader(fr);
            String str;
            while ( (str = reader.readLine()) != null )
                System.out.println(str);

            System.out.println("运行成功");
        }
        System.exit(res ? 0 : 1);
    }
}

(4)结果展现

4、TopN

(1)案例介绍

        TopN分析法是指从研究对象中安装某一个指标进行倒序或正序排列,取其中所需的N个案例,并对这N个数据进行重点分析的方法。

(2)Map阶段实现

package cn.itcast.mr.topN;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;
import java.util.TreeMap;

public class TopNMapper extends Mapper<LongWritable, Text,
        NullWritable, IntWritable> {
    private TreeMap<Integer, String>repToRecordMap =
            new TreeMap<Integer, String>();
    @Override
    public void map (LongWritable key, Text value, Context context) {
        String line = value.toString();
        String[] nums = line.split(" ");
        for (String num : nums
             ) {
            repToRecordMap.put(Integer.parseInt(num), " ");
            if (repToRecordMap.size() > 5) {
                repToRecordMap.remove(repToRecordMap.firstKey());
            }
        }
    }

    @Override
    protected void cleanup(Context context) {
        for (Integer i: repToRecordMap.keySet()
             ) {
            try {
                context.write(NullWritable.get(), new IntWritable(i));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

(3)Reduce阶段实现

package cn.itcast.mr.topN;


import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;
import java.util.Comparator;
import java.util.TreeMap;

public class TopNReducer extends Reducer<NullWritable, IntWritable, NullWritable, IntWritable> {
    private TreeMap<Integer, String>repToRecordMap = new
            TreeMap<Integer, String>(new Comparator<Integer>() {
        public int compare(Integer a, Integer b) {
            return b-a;
        }
    });
    public void reduce(NullWritable key,
                       Iterable<IntWritable>values, Context context)
        throws IOException, InterruptedException {
        for (IntWritable value : values
             ) {
            repToRecordMap.put(value.get(), " ");
            if (repToRecordMap.size() > 5) {
                repToRecordMap.remove(repToRecordMap.lastKey());
            }
        }
        for (Integer i : repToRecordMap.keySet()
             ) {
            context.write(NullWritable.get(), new IntWritable(i));
        }
    }
}

(4)Driver程序主类实现

package cn.itcast.mr.topN;

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.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.BufferedReader;
import java.io.FileReader;

public class TopNDriver {
    public static void main(String[] args) throws Exception{
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        job.setJarByClass(TopNDriver.class);
        job.setMapperClass(TopNMapper.class);
        job.setReducerClass(TopNReducer.class);
        job.setNumReduceTasks(1);
        job.setMapOutputKeyClass(NullWritable.class);
        job.setMapOutputValueClass(IntWritable.class);
        job.setOutputKeyClass(NullWritable.class);
        job.setOutputValueClass(IntWritable.class);
        FileInputFormat.setInputPaths(job, new Path("/home/huanganchi/Hadoop/实训项目/HadoopDemo/textHadoop/TopN/input"));
        FileOutputFormat.setOutputPath(job, new Path("/home/huanganchi/Hadoop/实训项目/HadoopDemo/textHadoop/TopN/output"));
        boolean res = job.waitForCompletion(true);
        if (res) {
            FileReader fr = new FileReader("/home/huanganchi/Hadoop/实训项目/HadoopDemo/textHadoop/TopN/output/part-r-00000");
            BufferedReader reader= new BufferedReader(fr);
            String str;
            while ( (str = reader.readLine()) != null )
                System.out.println(str);

            System.out.println("运行成功");
        }

        System.exit(res ? 0 : 1);
    }
}

(5)结果展示


参考书籍

《Hadoop大数据技术原理与应用》

 

;