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

目录

一、MapReduce概述

1、MapReduce核心思想

2、MapReduce编程模型

二、MapReduce工作原理

1、分片、格式化数据源

2、执行 MapTask

3、执行Shuffle过程

4、执行RecudeTack

5、写入文件

扫描二维码关注公众号,回复: 15429305 查看本文章

三、案例

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大数据技术原理与应用》

猜你喜欢

转载自blog.csdn.net/weixin_63507910/article/details/128540149