hadoop04_MapReduce

MapReduce 模型

MapReduce 核心思想是移动任务而非移动数据

MapReduce 是一种编程模型,对数据集执行 MAP 映射,然后对结果进行 Reduce 规约,适用于大规模数据集的并行计算。核心思想可以理解为分治法,数据固定不动,分派计算任务到不同计算节点计算。

模型介绍

MapReduce 适用场景

  • 离线处理,比如数据统计,网站 PV、UV 的统计;
  • 搜索引擎构建索引,例如搜索引擎需要对底层数据建立索引,然后才能高效快速查询;
  • 海量数据查询;
  • 复杂数据分析算法的实现。

不适用的场景

  • OLAP:在线分析场景,要求毫秒或者秒级返回结果;因为 MapReduce 是通过依赖磁盘交互从而减少内存使用,所以无法及时返回计算结果。
  • 流式计算:输入的数据集是流动的,而 MapReduce 是固定的;
  • DAG计算:有向无环图的任务计算,后续任务需要依赖前面任务的执行结果,但是 MapReduce 中每个作业的执行结果都需要落盘,落盘后再读取然后网络传输给后续节点,这产生大量磁盘IO,性能低下。DAG计算更适用于 Spark框架,因为 Spark 是基于内存的,计算结果无需落盘就可以传输给下一个对象。

img

示例

举个例子词频统计,输入为多个文件,MapReduce 过程如下:

  • 对输入进行拆分,如果文件存储在 HDFS 已经自动完成了切分,否则按照每 128MB 大小切分;
  • 对每个数据块启动一个 Map 任务,任务会按照空格对字符串进行切分,统计每个词汇的出现次数;
  • 对每个词汇名做哈希计算(shuffer),同名的词汇名与其对应结果会发送到相同的 Reduce节点上,待所有结果建立完映射,开始执行 Reduce任务;
  • Reduce 任务即统计当前词汇的出现频率,最后合并所有结果输出。

所以我们得出一般场景下 MapReduce 的执行流程:

img

img

  • 数据首先存储到 HDFS 中,HDFS 会自动对数据进行拆分为固定大小的块,对每个块启动一个 Map 任务;
  • MapReduce 执行之前根据配置来进一步细分或者合并 Block 块来增加或者减少 Map 的数量。接着对每个数据块执行 Map 任务,输出的结果(<Key,Value> 格式)保存到内存缓冲区上(上限为100MB),当内存容量超过 80% 时执行溢写操作,tong
  • 溢写包括分组排序,分组指按照 Reduce 数目对数据进行分组哈希取模排序,生成多个小文件;
  • 为了方便传输,合并多个小文件为大的分组有序的文件,等待 Reduce 任务拉取;
  • Reduce节点通过 fetch 线程从各个 Map节点拉取指定结果,拉取的数据先保存到缓冲区中。当大小超过阈值后执行溢写操作,溢写会生成多个小文件。然后合并多个小文件结果为一个大文件。
  • 生成大文件时也需要进行分组排序,分组是指将 Key 相同的分到同一组,然后排序;
  • 接着对分组有序的文件执行 Reduce 处理,每个任务最终会输出一个计算结果;合并所有计算结果,保存到 HDFS 中。

综上,MapReduce 整个过程中涉及到大量磁盘IO操作、大量网络IO操作、少量的内存操作:

  • 少量内存操作:执行完 Map 任务结果输出到内存缓冲区;fetch线程拉取 Map结果后先保存到内存缓冲区;
  • 大量磁盘IO操作:Map 阶段内存缓冲区超过阈值时执行溢写,分组排序落盘;Reduce 执行结果落盘。
  • 大量网络IO操作:Map结果需要通过网络才能传输至Reduce节点,这个过程大量数据需要大量网络传输。

源码分析

Job 任务提交

// Job.java
public void submit() 
        throws IOException, InterruptedException, ClassNotFoundException {
// 任务状态检查
ensureState(JobState.DEFINE);
// 解决 Hadoop 新旧版本冲突
setUseNewAPI();
// 建立连接,远程Yarn客户端或者本地客户端
connect();

final JobSubmitter submitter = 
    getJobSubmitter(cluster.getFileSystem(), cluster.getClient());
status = ugi.doAs(new PrivilegedExceptionAction<JobStatus>() {
    public JobStatus run() throws IOException, InterruptedException, 
    ClassNotFoundException {
    
    // 核心提交任务流程
    return submitter.submitJobInternal(Job.this, cluster);
    }
});
state = JobState.RUNNING;
LOG.info("The url to track the job: " + getTrackingURL());
}

submitJobInternal(Job job, Cluster cluster) 方法主要执行以下步骤:

  • 检查输入文件、输出目录,如果存在输出目录报错;
  • 创建给集群提交任务的文件目录 Stag,提交文件时会在目录下创建以任务名为目录名的子目录,然后初始化任务执行信息、配置;
  • 拷贝任务 jar 包到远程集群目录,如果是本地则无需拷贝;
  • 计算切片信息,生成切片规划文件;
  • Stag 目录中写入 XML 配置文件(job.xml),该文件内容为该任务运行所需的参数配置,然后客户端向远程集群提交任务信息;
  • 提交任务,返回提交结果状态。
JobStatus submitJobInternal(Job job, Cluster cluster) 
throws ClassNotFoundException, InterruptedException, IOException {
    //validate the jobs output specs 
    checkSpecs(job);

    // 创建给集群提交任务的目录
    Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);

    // 生成当前唯一任务ID
    JobID jobId = submitClient.getNewJobID();
    job.setJobID(jobId);
    // 给当前任务生成提交用的目录
    Path submitJobDir = new Path(jobStagingArea, jobId.toString());

    // 拷贝任务Jar包等文件到远程集群
    copyAndConfigureFiles(job, submitJobDir);

    // 计算切片,生成切片规划文件
    int maps = writeSplits(job, submitJobDir);

    // 向任务目录中写入任务XML执行配置文件
    writeConf(conf, submitJobFile);

    // 提交任务
    status = submitClient.submitJob(
          jobId, submitJobDir.toString(), job.getCredentials());
}

分词案例实操

  • 实现分词 Map、Reduce 过程,分别实现 Mapper、Reducer 接口;
  • Map 过程,输入类型(key:Object、Value:IntWritable),输出类型(key:Text、Value:IntWritable);
  • 配置生成任务所需的核心配置,创建作业并运行。
public class WordCount {
    /**
     * Mapper
     * 接收的是文件,会读取文件的每一行,所以这里设置为 LongWritable/Object,Value 为这一行的文件内容
     */
    public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> {
        private final static IntWritable vOut = new IntWritable(1);

        private Text kOut = new Text();

        @Override
        protected void map(Object key, Text value, Mapper<Object, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException {
            StringTokenizer stringTokenizer = new StringTokenizer(value.toString());
            while (stringTokenizer.hasMoreTokens()) {
                String word = stringTokenizer.nextToken();
                kOut.set(word);
                context.write(kOut, vOut);
            }
        }
    }

    /**
     * Reducer
     */
    public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
        private final static IntWritable vOut = 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 val : values) {
                sum += val.get();
            }
            vOut.set(sum);
            context.write(key, vOut);
        }
    }

    public static void main(String[] args) throws Exception {
        // 初始化Job
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf, "word count");

        // 配置 Mapper/Reducer 实现类
        job.setJarByClass(WordCount.class);
        job.setMapperClass(TokenizerMapper.class);
        job.setCombinerClass(IntSumReducer.class);

        // 配置结果输出类
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        // 配置结果输入和输出路径
        FileInputFormat.addInputPath(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        // 运行作业,等待结束
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}
  • 上传本地输入文件到 hdfs
# hdfs创建目录并赋予权限
hadoop fs -mkdir -p /tmp/data/wc_input
hadoop fs -chmod 777 /tmp/data/wc_input

# 本地创建词频文件并上传到 hdfs
echo -e "hello hadoop\nhello hdfs\nhello yarn\nhello mapreduce" > wordcount.txt
hadoop fs -put wordcount.txt /tmp/data/wc_input

img

  • 本地 IDEA 打包为 wc.jar 包并上传 linux;
  • 执行 hadoop 程序:
# 命令格式:hadoop jar xxx.jar 主类名(绝对路径) hdfs输出路径
hadoop jar wc.jar WordCount com.xjx.demo.WordCount /tmp/data/wc_output
  • 查看 hdfs 输出路径结果
hadoop fs -cat /tmp/data/wc_output/part-r-*

img

posted @ 2024-06-28 20:00  Stitches  阅读(37)  评论(0)    收藏  举报