大数据求索(3):实战MapReduce

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wen_fei/article/details/82928855

MapReduce 概述

主要用于离线、海量数据运算

WordCount编写

下面这张经典图很好地说明了如何编写一个WordCount,也清楚说明了MapReduce的流程

mr-1

对于输入的一个文本(可以存放在HDFS上,可以非常非常大),先对文件进行拆分,假设这里一行一份,对于每一行,按空格进行切分,然后给每个单词赋初值为1,这里同一个map里有相同的单词,也是不会覆盖的,会保留两个(word, 1),不同的map之间是没有依赖关系的,是独立的、并行的。shuffing阶段是为了将相同的聚到一起。map的输出会作为reduce的输入,注意,这里会对map的结果做排序,然后reduce阶段进行求和,最终得到统计的词频。

MapReduce编程模型

框架会把输入看做由键值对组成的集合,key和value都需要被框架所序列化,所以都要实现Writable接口,同时默认会进行排序,所以key还需要实现WritableComparable接口。

将作业拆分成map阶段和reduce阶段,执行map tasks和reduce tasks,MR的执行流程如下

(input) <k1, v1> -> map -> <k2, v2> -> combine -> <k2, v2> -> reduce -> <lk3, v3>(output)

代码

有了上面的分析,可以直接进行编写了,代码如下:

package org.wds.mr;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

/**
 * 使用MapReduce开发wordcount应用程序
 */
public class WordCount {
    /**
     * map: 读取输入的文件
     */
    public static class MyMapper extends Mapper<LongWritable, Text, Text, LongWritable> {

        LongWritable one = new LongWritable(1);

        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            // 接收到的每一行数据
            String line = value.toString();

            // 按照分隔符拆分
            String[] words = line.split(" ");

            for (String word : words) {
                // 通过上下文把map的处理结果输出
                context.write(new Text(word), one);
            }
        }
    }


    /**
     * Reduce:归并操作
     */
    public static class MyReducer extends Reducer<Text, LongWritable, Text, LongWritable> {
        @Override
        protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {

            long sum = 0;

            for (LongWritable value : values) {
                // 求key出现的次数总和
                sum += value.get();
            }
            // 最终统计结果的输出
            context.write(key, new LongWritable(sum));

        }
    }

    /**
     * 定义Driver : 封装了MapReduce作业的所有信息
     * @param args
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        // 设置
        Configuration conf = new Configuration();

        // 创建job
        Job job = Job.getInstance(conf, "wordcount");

        // 设置job的处理类
        job.setJarByClass(WordCount.class);

        // 设置作业处理的输入路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));

        // 设置Map相关参数
        job.setMapperClass(MyMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);

        // 设置reduce相关参数
        job.setReducerClass(MyReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);

        // 设置作业处理的输出路径
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

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

这里输入输出是自己指定,输出是到hdfs,然后使用maven把项目打包,上传到服务器,使用hadoop jar xx input output就可以运行了。

这里还有些问题,就是多次运行同一个job会出现路径已存在的错误,解决办法便是每次运行前检查路径是否存在,存在则删除,操作hdfs很容易实现,在main函数里里加入如下代码:

	   // 清理已存在的输出目录
        Path outpath = new Path(args[1]);
        FileSystem fileSystem = FileSystem.get(conf);
        if (fileSystem.exists(outpath)) {
            fileSystem.delete(outpath, true);
            System.out.println("output file exists, but it has deleted");
        }

MapReduce核心概念

  • Split

    交由MR作业来处理的数据库,是MR中最小的计算单元,类比于HDFS的blocksize,blocksize是HDFS最小的存储单元,默认是128M。

    默认情况下,两者是一一对应的,当然也可以修改(不建议)。

  • InputFormat

    将输入数据进行分片,将一个文件拆分为多个split,底层调用的是InputSplit[] getSplits(JobConf job, int numSplits)

    比较常用的是TextInputFormat,用于处理文本

  • OutputFormat

    输出

  • Combiner

    如下一个经典的图可以很好地解释

    mr-2

    对map的结果先进行合并,合并之后总共有4条数据,没合并之前有9条,如果数据很大,这样就能够大大减少网络传输的消耗,相当于map在本地做了一个reduce。

    使用Combiner也非常简单,直接在设置里面加入如下代码

     // 通过job设置combiner处理类,其实逻辑上和reduce一模一样
     // combiner使用场景是有限制的,比如求和、排序,但是求平均是错误的
            job.setCombinerClass(MyReducer.class);
    
  • Partitioner

Partitioner决定了MapTask输出的数据交由哪个ReduceTask进行处理,默认情况下,是由分发的key的hash值对Reduce Task个数进行取模。

猜你喜欢

转载自blog.csdn.net/wen_fei/article/details/82928855