(Hadoop操作与应用)S4MapReduce分布式计算框架

概要

MapReduce是hadoop的核心组件之一,用于大数据并行处理的计算模型、框架和平台,主要解决海量数据的计算。
借鉴了面向对象编程思想

MapReduce概述

核心思想

分而治之,将大问题分解为小问题,解决小问题。
使用MapReduce操作海量数据是,每个MapReduce程序被初始化为一个工作任务,每个工作任务分为Map和Reduce两个阶段:
	* Map:负责任务分解,分解为若干个“简单的任务”来并行处理,前提是这些任务没有必然的依赖关系。
	* Reduce:负责任务合并,把Map阶段的结果进行全局汇总

在这里插入图片描述

MapReduce编程模型

在这里插入图片描述

通过两个函数Map()和Reduce()两函数,Map()接收键值对返回键值对,Reduce()接收键值对,返回结果键值对

在这里插入图片描述
流程

1. 将原始数据装换为<k1,v1>形式
2. 解析后的<k1,v1>传给Map映射为中间结果形式<k2,v2>
3. <k2,v2>形成<k2,{v2,...}>传给Reduce,把相同k的值通过Reduc()输出形成<k3,v3>

案例–词频统计流程

在这里插入图片描述

MapReduce工作原理

工作过程

在这里插入图片描述

分片、格式化数据源

  1. 分片操作:将源文件划分为大小相等的小数据块(hadoop2.x默认128M),hadoop自动为每一个分片运行自定义的Map()操作。
  2. 格式化操作:将划分好的分片格式化为键值对的数据形式,其中键为偏移量,值为每一行的内容

执行MapTask

每一个Map任务都有内存缓冲区默认100M,当写到80M的时候将数据写入磁盘(溢出),会对数据进行排序,多次溢出多次写入磁盘,最后进行合并

执行Shuffle过程

将MapTask输出的处理结果数据分发给ReduceTask,分发过程中对Key进行分区和排序

执行ReduceTask

输入ReduceTask的数据流形式是<key,{values list}>形式,可以自定义reduce()方法进行逻辑处理,最终以<key value>形式输出

写入文件

MapRduce框架自动将ReduceTask生成的减值对传入OutputFormat的write方法实现写入操作

MapTask工作原理

主要经过Read阶段、Map阶段、Collect阶段、Split阶段、Combiner阶段
在这里插入图片描述

  1. Read阶段:通过用户编写的RecordReader,从输入的InputSplit中解析出一个个key/value
  2. Map阶段:将解析出的key/value交给用户编写的map()函数处理,并产生一系列新的key/value
  3. Collect阶段:将生成的key/value分片,写入一个环形内存缓冲区
  4. Split阶段(溢写):当环形缓冲区满后,将数据写入本地磁盘,写入之前在本地进行排序
  5. Combiner阶段:当所有数据处理完成后,MapTask对所有临时文件进行合并,保证只生成一个文件

ReduceTask工作原理

主要经过Copy阶段、Merge阶段、Sort阶段、Reduce阶段、Write阶段
在这里插入图片描述

  1. Copy阶段:reduce从各个MapTask上进行远程拷贝数据,若数据超过一定大小则写到磁盘上,否则放到内存中
  2. Merge阶段:在远程拷贝数据的同时,ReduceTask会启动两个后台线程,分别对内存和磁盘上的文件进行合并,以防止内存使用过多或磁盘文件过多
  3. Sort阶段:为了将Key相同的数据聚集在一起,Hadoop采用基于排序的策略,由于MapTask对自己的处理结果局部排序,ReduceTask进行一次归并排序即可
  4. Reduce阶段:对排序的键值对调用reduce方法,键相等键值对调用一次reduce()方法,每次调用产生多个键值对,最后将这些键值对写入带HDFS系统中
  5. Write阶段:reduce()函数将计算结果写到HDFS上

Shuffle工作原理

在这里插入图片描述
Shuffle是MapReduce的核心,用来确保每个reduce的数据都是按键排序的,他的性能高低取决了整个MapReduce程序的性能高低,map和reduce阶段都涉及shuffle机制。组外合并。combine是组内合并

Map阶段的Shuffle

  1. 当MapTask处理结果放入缓冲区,当快要溢出时,将在本地磁盘创建一个溢出文件,将缓冲区的数据写入这个文件
  2. 写入磁盘之前,线程根据ReduceTask的数量将数据分区,一个Reduce任务对应一个分区的数据,为了避免reduce任务分配不均
  3. 分完数据后,对每个分区的数据进行排序,如果设置了Combiner将排序后的结果进行combiner操作。为了减少执行数据写入磁盘
  4. map执行结束后由许多溢出文件,这时需要不断进行排序和combine操作为了:减少每次写入磁盘的数据量,减少下一复制阶段网络传输的数据量。最后合并完成了一个已分区且已排序的文件
  5. 将分区中的数据拷贝给对应的reduce任务

Reduce阶段

  1. Reduce会接收不同map任务的数据,每个map的数据都是有序的。如果数据量小写入到缓存,如果数据超过一定阈值则将数据合并后写道磁盘中
  2. 随着溢写文件的增多,后台线程会将他们合并成一个更大的有序文件,为了给后面的合并节约时间
  3. 合并过程中会产生很多中间文件(写入磁盘了),但MapReduce会让写入磁盘的数据尽可能的少,并且最后一次合并的结果并没有写入磁盘,而是直接输入到reduce函数。

MapReduce编程组件

  1. InputFormat组件:用于描述输入数据格式,连个功能:数据切分,为Mapper提供输入数据
  2. Mapper组件:Mapper类是实现Map任务的一个抽象基类,该基类提供了一个map()方法
  3. Reduce组件:Map输出的键值对,将由Reduce组件进行合并处理,租后的某种形式的输出结果
  4. Partitioner组件:让Map对Key进行分区,从而可以根据不同的key分发到不同的Reduce中去处理,目的就是将key均匀的分布在ReduceTask上
  5. Combiner组件:对Map阶段输出的重复数据先祖欧一次合并,然后把新的键值对作为Reduce阶段的输入
  6. OutputFromat组件:用于描述MapReduce程序输出格式和规范的抽象类

InputFormat组件

用于描述输入数据格式,连个功能:数据切分,为Mapper提供输入数据

  • 数据切分
  • 为Mapper提供数据输入
public abstract class InputFormat<K, V> {
    public InputFormat() {
    }

    public abstract List<InputSplit> getSplits(JobContext var1) throws IOException, InterruptedException;

    public abstract RecordReader<K, V> createRecordReader(InputSplit var1, TaskAttemptContext var2) throws IOException, InterruptedException;
}

Mapper组件

  • Mapper类是实现Map任务的一个抽象基类,该基类提供了一个map()方法
  • 要自定义map()方法,续继承Mapper方法并重写map()方法
  • 以词频统计为例,自定义map()方法
在这里插入代码片

Reducer组件

  • map过程输出的减值对,将由Reducer组件进行合并处理
  • Reducer类中会调用run()方法,,该方法中定义了setup(),reduce(),cleanup()方法,执行顺序为setup(),reduce(),cleanup()。
  • 默认情况下,setup(),cleanup()内部不作任何处理,所以,reduce()方法是处理数据的核心方法

Partitioner组件

  • 让Map对Key进行分区,从而可以根据不同的key分发到不同的Reduce中去处理,目的就是将key均匀的分布在ReduceTask上
  • 想要自定义Partitioner组件,需要继承Partitioner类并重写getPartition方法。

Combiner组件

  • 对Map阶段的输出重复数据做一次合并计算(组内合并),把心的键值对作为reduce阶段的输入
  • 自定义Combiner,需要继承Reduce类,重写reduce()方法
  • 注意:Combiner组件不予许改变业务逻辑

OutputFromat组件

用于描述MapReduce程序输出格式和规范的抽象类

MapReduce运行模式

本地模式运行

在当前的开发环境模拟MapReduce执行环境,处理的数据及输出结果在本地操作系统

集群运行模式

MapReduce程序打成一个jar包,提交到Yarn集群上去运行任务。Yarn负责资源管理和任务调度,因此处理的数据和输出结果都在HDFS文件系统中。

MapReduce性能优化策略

五个方面:数据输入、Map阶段、Reduce阶段、Sheffle阶段、其他调优属性

  1. 数据输入:执行前,将小文件合并,使用CombineTextInputFormat作为输入

  2. Map阶段

     1. 减少溢写:调整io.sort.mb(缓冲区大小,默认100m),调整sort.split.perccent(溢写比例,默认80%)
     2. 减少合并:调整io.sort.factor(控制一次合并多少文件)
     3. 在map之后,不影响业务逻辑的前提下,先进性combine处理,减少I/O
    
属性名称类型默认值说明
mapreduce.io.sort.mbint100内存缓冲区大小,默认100M
mapreduce.sort.split.perccentfloat0.80溢出写入磁盘的百分比
mapreduce.task.io.sort.factorint10排序文件时,一次合并的流数,实际开发时可以设置为80
mapreduce.task.min.num.spills.for.combineint3运行combiner时,所需的最少溢出文件数
  1. Reduce阶段

     1. 合理设置map和reduce数。
     2. 设置map、reduce共存
     3. 规避使用reduce
     4. 合理设置reduce端的buffer
    

都在mapred-default.xml中设置,属性如下

属性名称类型默认值说明
mapreduce.job.reduce.slowstare.completemapsfloat0.05当MapTask在执行到5%,就开始为reduce申请资源。开始执行reduce操作,reduce可以开始拷贝map结果数据做reduce shuffle操作
mapred.job.reduce.input.buffefloat0.0在reduce过程,内存中保存map输出的空间占整个堆空间的比例。可以增加这个值来减小范文磁盘次数
  1. Sheffle阶段:可以给shuffle多一下内存空间,任务节点上的内存大小应尽量大

参数设置位于mapred-site.xml

属性名称类型默认值说明
mapred.map.child.java.opts-Xmx200m当用户设置该值会以最大1G jvm heap size 启动MapTask。可能导致内存溢出,建议设置为-Xmx1024m
mapred.reduce.child.java.opts-Xmx200m当用户设置该值会以最大1G jvm heap size 启动ReduceTask。可能导致内存溢出,建议设置为-Xmx1024m
  1. 其他调优属性:mapred-defult.xml中设置
属性名称类型默认值说明
mapreduce.map.memory.mbint1024一个MapTask可用的资源上香,如果超过上限会被杀死
mapreduce.reduce.memory.mbint1024一个ReduceTask可用的资源上香,如果超过上限会被
mapreduce.map.cpu.vcoresint1每个MapTask可食用最多cpu core数目
mapreduce.reduce.cpu.vcoresint1每个ReduceTask可食用最多cpu core数目
mapreduce.reduce.shuffle.parallelcopiesint5每个reduce去map中那数据的并行数
mapreduce.map.maxattemptsint4每个MapTask最大重试次数,超过后则认为该MapTask运行失败

案例—倒排索引

  1. 需求:现假设有三个源文件file1.txt、file2.txt和file3.txt,需要使用倒排索引的方式对这三个源文件内容实现倒排索引,并将最后的倒排索引文件输出。
    在这里插入图片描述
  2. 首先,使用默认的TextInputFormat类对每个输入文件进行处理,得到文本中每行的偏移量及其内容。Map过程首先分析输入的<key,value>键值对,经过处理可以得到倒排索引中需要的三个信息:单词、文档名称和词频。在这里插入图片描述
  3. 经过Map阶段数据转换后,同一个文档中相同的单词会出现多个的情况,而单纯依靠后续Reduce阶段无法同时完成词频统计和生成文档列表,所以必须增加一个Combine阶段,先完成每一个文档的词频统计。在这里插入图片描述
  4. 经过上两个阶段的处理后,Reduce阶段只需将所有文件中相同key值的value值进行统计,并组合成倒排索引文件所需的格式即可。在这里插入图片描述

实现

  1. InvertedIndexMapper
public class InvertedIndexMapper extends Mapper<LongWritable, Text, Text, Text> {
    private static Text keyInfo = new Text();
    private static final Text valueInfo = new Text("1");

    @Override
    protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, Text>.Context context) throws IOException, InterruptedException {
        // 拆分数据
        String line = value.toString();
        String[] fields = line.split(" ");
        // 获取文本行数据所在文件名
        FileSplit fileSplit = (FileSplit) context.getInputSplit();
        String fileName = fileSplit.getPath().getName();

        // 将K2和V2写入上下文
        for(String field: fields){
            keyInfo.set(field+":" + fileName);
            context.write(keyInfo, valueInfo);
        }
    }
}
  1. InvertedIndexCombiner
public class InvertedIndexCombiner extends Reducer<Text, Text,Text,Text> {
    private static Text info  = new Text();
    @Override
    protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text,Text,Text>.Context context) throws IOException, InterruptedException {
        int sum = 0;
        for (Text value:values){
            sum += Integer.parseInt(value.toString());
        }
        int splitIndex = key.toString().indexOf(":");
        info.set(key.toString().substring(splitIndex + 1) + ":"+sum);
        // 获取单词
        key.set(key.toString().substring(0, splitIndex));
        // 将key和value写入上下文
        context.write(key,info);
    }
}

  1. InvertedIndexReducer
public class InvertedIndexReducer extends Reducer<Text,Text,Text,Text> {
    private static Text result = new Text();
    @Override
    protected void reduce(Text key, Iterable<Text> values, Reducer<Text,Text,Text,Text>.Context context) throws IOException, InterruptedException {
        // 遍历几个,拼接集合内容,生成文档列表
        String fileList = new String();
        for (Text value: values
             ) {
            fileList += value.toString()+";";
        }
        // 将k3和v3写入上下文
        result.set(fileList);
        context.write(key,result);
    }
}

  1. InvertedIndexDriver
public class InvertedIndexDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        // 获取job任务对象
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        // 设置job对象
        job.setJarByClass(InvertedIndexDriver.class);
        job.setMapperClass(InvertedIndexMapper.class);
        job.setCombinerClass(InvertedIndexCombiner.class);
        job.setReducerClass(InvertedIndexReducer.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);

        FileInputFormat.setInputPaths(job,new Path("G:\\TwoNextCode\\Hadoop\\input\\"));
        FileOutputFormat.setOutputPath(job,new Path("G:\\TwoNextCode\\Hadoop\\output\\"));

        // 启动job任务
        boolean res = job.waitForCompletion(true);
        System.exit(res?0:1);

    }
}

在这里插入图片描述在这里插入图片描述

案例–数据去重

  1. 需求
    文件file1.txt本身包含重复数据,并且与file2.txt同样出现重复数据,现要求使用Hadoop大数据相关技术对以上两个文件进行去重操作,并最终将结果汇总到一个文件中。
    (1) 编写MapReduce程序,在Map阶段采用Hadoop默认作业输入方式后,将key设置为需要去重的数据,而输出的value可以任意设置为空。
    (2) 在Reduce阶段,不需要考虑每一个key有多少个value,可以直接将输入的key复制为输出的key,而输出的value可以任意设置为空,这样就会使用MapReduce默认机制对key(也就是文件中的每行内容)自动去重。
  • file1.txt
2018-3-1 a
2018-3-2 b
2018-3-3 c
2018-3-4 d
2018-3-5 a
2018-3-6 b
2018-3-7 c
2018-3-3 c
  • file2.txt
2018-3-1 b
2018-3-2 a
2018-3-3 b
2018-3-4 d
2018-3-5 a
2018-3-6 c
2018-3-7 d
2018-3-3 c

实现

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

  1. DedupReducer.java
public class DedupReduce 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());
    }
}
  1. DedupDrive.java
public class DedupDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        // 获取job任务对象
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        // 设置job对象
        job.setJarByClass(DedupDriver.class);
        job.setMapperClass(DedupMapper.class);
        job.setReducerClass(DedupReduce.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NullWritable.class);

        FileInputFormat.setInputPaths(job,new Path("G:\\TwoNextCode\\Hadoop\\input\\"));
        FileOutputFormat.setOutputPath(job,new Path("G:\\TwoNextCode\\Hadoop\\output\\"));

        // 启动job任务
        boolean res = job.waitForCompletion(true);
        System.exit(res?0:1);

    }
}

在这里插入图片描述
在这里插入图片描述

案例–TopN

  • TopN分析法是指从研究对象中按照某一个指标进行倒序或正序排列,取其中所需的N个数据,并对这N个数据进行重点分析的方法。
  • 现假设有数据文件num.txt,现要求使用MapReduce技术提取上述文本中最大的5个数据,并最终将结果汇总到一个文件中。
  1. 先设置MapReduce分区为1,即ReduceTask个数一定只有一个。我们需要提取TopN,即全局的前N条数据,不管中间有几个Map、Reduce,最终只能有一个用来汇总数据。
  2. 在Map阶段,使用TreeMap数据结构保存TopN的数据,TreeMap默认会根据其键的自然顺序进行排序,也可根据创建映射时提供的 Comparator进行排序,其firstKey()方法用于返回当前集合最小值的键。
  3. 在Reduce阶段,将Map阶段输出数据进行汇总,选出其中的TopN数据,即可满足需求。这里需要注意的是,TreeMap默认采取正序排列,需求是提取5个最大的数据,因此要重写Comparator类的排序方法进行倒序排序。

实现

  1. 编写TopNMapper.java
public class TopNMapper extends Mapper<LongWritable, Text, NullWritable, IntWritable> {
    private static TreeMap<Integer, String> repToRecordMap = new TreeMap<Integer, String>();
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        // 拆分行文本数据,得到每个数字
        String line = value.toString();
        String[] nums = line.split(" ");

        // 将得到数字存到TreeMap集合,获取每一行最大的5个数
        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();
            }
        }
    }
}
  1. 编写TopNReduce.java
public class TopNReduce extends Reducer<NullWritable, IntWritable,NullWritable,IntWritable> {
        private static TreeMap<Integer, String> repToRecordMap = new TreeMap<Integer, String>(new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                return b - a;
            }
        });
    @Override
    protected void reduce(NullWritable key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        // 遍历集合,将所有数字存入TreeMap集合,自定义TreeMap集合排序规则
        for (IntWritable value: values
             ) {
            repToRecordMap.put(value.get(), "");
            // 取出TreeMap最大的5个数
            if(repToRecordMap.size()>5){
                repToRecordMap.remove(repToRecordMap.firstKey());
            }
        }
        // 将最终结果写入
        for ( Integer i :repToRecordMap.keySet()
             ) {

            context.write(NullWritable.get(),new IntWritable(i));

        }
    }
}
  1. 编写TopNDriver
public class TopNDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        // 获取job任务对象
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        // 设置job对象
        job.setJarByClass(TopNDriver.class);
        job.setMapperClass(TopNMapper.class);
        job.setReducerClass(TopNReduce.class);
        
        job.setOutputKeyClass(NullWritable.class);
        job.setOutputValueClass(IntWritable.class);
        
        FileInputFormat.setInputPaths(job,new Path("G:\\TwoNextCode\\Hadoop\\input1\\"));
        FileOutputFormat.setOutputPath(job,new Path("G:\\TwoNextCode\\Hadoop\\output1\\"));

        // 启动job任务
        boolean res = job.waitForCompletion(true);
        System.exit(res?0:1);
    }
}

结果

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值