MapReduce基础编程

137 阅读5分钟

1.MapReduce Partition分区

2.MapReduce Combiner规约

3.MapReduce基于两种方法的案例

1.MapReduce Partition分区

MapReduce输出结果文件个数探究

在map过程中,会并发的执行多个task(小任务),在reduce过程中,结果只会有一个task处理

map的输出文件命名格式为part-m-nnnnn,而reduce的输出文件为part-r-nnnnn

在MapReduce运行的过程中,map任务将其中间结果输出写入到本地磁盘,reduce任务实现对多个map输出结果的处理,输出结果到HDFS中,并会将保存到本地磁盘的中间结果文件删除

Snipaste_2023-10-11_15-27-35.png

以上是单个reduce任务的输出结果

当有多个reduce任务时,map和reduce任务中会有一个shuffle的过程,用于并行地执行多个不同的部分

mapreduce中,通过Job提供地方法可以修改reducetask的个数

对应的方法

public void setNumReduceTasks(int tasks) 

该方法的源码

public void setNumReduceTasks(int tasks) throws IllegalStateException {
    // 用于判断Job是否可以使用该方法
    this.ensureState(Job.JobState.DEFINE);
    // 通过配置项对象设置reducetask的个数
    this.conf.setNumReduceTasks(tasks);
}

Snipaste_2023-10-11_16-13-15.png

没有设置ReduceTask个数的结果和设置了的结果

未设置,默认reducetask只有一个,map任务shufffle操作只有一次

Snipaste_2023-10-11_16-16-05.png

设置,map任务的shuffle操作为指定的次数,这里设置的个数为3

Snipaste_2023-10-11_16-16-50.png

Snipaste_2023-10-11_16-17-09.png

同时,reduce任务的输出文件也分成了三部分

Snipaste_2023-10-11_16-19-53.png

Partition分区

当MapReduce中有多个reducetask执行的时候,此时maptask的输出就会面临一个问题:

究竟将自己的输出数据交给哪一个reducetask来处理?这就是所谓的数据分区(partition)问题

当改变reducetask个数的时候,作为maptask就会涉及到分区的问题,即:MapTask输出的结果如何分配给各个ReduceTask来处理

Snipaste_2023-10-11_16-34-48.png

MapReduce默认分区规则为HashPartitioner,分区的结果和map输出的key有关

@Public
@Stable
public class HashPartitioner<K2, V2> implements Partitioner<K2, V2> {
    public HashPartitioner() {
    }

    public void configure(JobConf job) {
    }

    public int getPartition(K2 key, V2 value, int numReduceTasks) {
        return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
    }
}

由该方法可知,分区与map输出的key有关,与对应的reducetask数量有关

Partitioner注意事项

reducetask个数的改变导致了数据分区的产生,而不是有数据分区导致了reducetask个数改变

数据分区的核心是分区规则。即如何分配数据给各个reducetask

默认的规则可以保证只要map阶段输出的key一样,数据就一定可以分区到同一个reducetask,但是不能保证数据平均分区

reducetask个数的改变还会导致输出结果文件不再是一个整体,而是输出到多个文件中

2.MapReduce Combiner规约

MapReduce是一种具有两个执行阶段的分布式计算程序,Map阶段和Reduce阶段之间会涉及到跨网络数据传递

每一个MapTask都可能会产生大量的本地输出,这就导致跨网络传输数据量变大,网络IO性能低

比如WordCount单词统计案例,假如文件中有1000个单词,其中999个为hello,这将产生999个<hello,1>的键值对在网络中传递,性能及其低下

Hadoop允许用户针对map任务的输出指定一个combiner

Combiner本质就是Reducer,combiner和reducer的区别在于运行的位置:

combiner是在每一个maptask所在的节点本地运行,是局部聚合

reducer是对所有maptask的输出结果计算,是全局聚合

combiner通俗来讲,是将map的输出进行优化,以WordCount为例,文件中只有两个词,hello为999个,world为1个

在不使用combiner时,map的输出为<hello,1>,对应的reduce的输入为<hello,[1,1,1,1,1,....]>

此时从本地磁盘读取数据,数据量比较的大,不利于传输

使用combiner,combiner本质是reducer(使用reducer的代码),map任务输出调用combiner,对应的combiner输出为<hello,999>,传输到reduce任务时,网络带宽会降低航舵,利于数据的传输

注意事项

Combiner能够应用的前提是不能影响最终的业务逻辑,而且,Combiner的输出kv应该跟reducer的输入kv类型要对应起来

在进行求解平均数,排序的过程中,不能使用combiner

以求平均数为例假定有多个map任务输出为<1950,23><1950,2>,<1950,40>和<1950,30><1950,3>

不使用combiner,reduce任务操作后的结果为mean(23,2,40,30,3)=19.5

使用combiner,reduce任务操作后的结果为mean(23,2,40,30,3)=mean(mean(23,2,40),mean(30,3)=19.1

两者有偏差,因此要慎用combiner

Combiner组件不是禁用,而是慎用。用的好提高程序性能,用不好,改变程序结果且不易发现

Snipaste_2023-10-11_17-05-01.png

3.MapReduce基于两种方法的案例

统计男女生数量

Job测试类

package com.prettyspider.mapreduce.countnumberofsex;

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;

/**
 * @author prettyspider
 * @ClassName WordCount
 * @description: TODO
 * @date 2023/10/10 2:17
 * @Version V1.0
 */

public class CountNumberOfSex {
    public static void main(String[] args) throws Exception{
        if (args.length != 2) {
            System.out.println("Usage:WordCount <input path> <output path>");
            System.exit(0);
        }

        Job job = new Job();
        job.setJarByClass(CountNumberOfSex.class);
        job.setJobName("CountNumberOfSex");

        FileInputFormat.addInputPath(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        // 设置ReduceTask
        job.setNumReduceTasks(3);


        job.setMapperClass(CountNumberOfSexMapper.class);

        // 设置combiner
        job.setCombinerClass(CountNumberOfSexReducer.class);

        job.setReducerClass(CountNumberOfSexReducer.class);

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

        System.exit(job.waitForCompletion(true)?0:1);
    }
}

Mapper类

package com.prettyspider.mapreduce.countnumberofsex;

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;
import java.util.StringTokenizer;

/**
 * @author prettyspider
 * @ClassName WordcountMapper
 * @description: TODO
 * @date 2023/10/9 13:40
 * @Version V1.0
 */

public class CountNumberOfSexMapper extends Mapper<LongWritable,Text,Text, IntWritable> {
    private static final IntWritable num = new IntWritable(1);
    @Override
    protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException {
        StringTokenizer lines = new StringTokenizer(value.toString());
        while (lines.hasMoreTokens()) {
            String regex = "-";
            String sex = lines.nextToken().split(regex)[1];
            // 获取键
            String keyName = sex;
            //写入内容中
            context.write(new Text(keyName),num);
        }
    }
}

Reducer类

package com.prettyspider.mapreduce.countnumberofsex;

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

import java.io.IOException;

/**
 * @author prettyspider
 * @ClassName WordcountReducer
 * @description: TODO
 * @date 2023/10/9 13:56
 * @Version V1.0
 */

public class CountNumberOfSexReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
    private static final IntWritable count = new IntWritable();
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
        int sum = 0;
        for (IntWritable value : values) {
            int num = value.get();
            sum+=num;
        }
        count.set(sum);
        context.write(key,count);
    }
}

测试数据提取码:4mib