hadoop学习笔记(八)MapReduce应用程序执行过程及java程序编写

MapReduce应用程序执行过

在这里插入图片描述

  1. 执行的MapReduce的程序会被部署到集群中去,Master负责作业调度,worker负责执行执行Map和Reduce任务
  2. 从集群中选出执行Map任务的空闲机器,进行分片处理,然后进行map
  3. map任务读取输入数据,得到输出数据<key,value>
  4. 得到的结果写入本地map机器的缓存,满了之后写入磁盘,并被划分为R个分区,Master会记录R个分区的位置,通知R个Reduce任务的Worker来领取属于自己处理的那部分分区
  5. Reduce任务的Worker领取了属于自己处理的分区,而且是当领取所有属于自己的Map机器的分区数据之后,Reduce任务的Worker对所有键值对进行排序,将具有相同的Key值的聚在一起,然后开始执行Reduce任务
  6. 对每一个唯一的Key执行Reduce任务,结果输出到HDFS中

Eclipse运行MapReduce任务

  1. 安装Hadoop-Eclipse-Plugin插件

这个插件对hadoop版本有要求,我使用的是https://github.com/winghc/hadoop2x-eclipse-plugin已经编译好的hadoop-eclipse-plugin-2.6.0.jar,在github界面有相应的教程编译不同版本的方法。

使用较新的Eclipse,我使用的是4.15版本的,需要将插件放在Eclipse安装目录下的eclipse/dropins目录下,才会生效。

  1. 设置Map/Reduce Locations

设置连接,建立与hadoop集群的连接
在这里插入图片描述
Map/Reduce(V2) Master 的 Port 用默认的即可,Location Name随便写,DFS Master对照配置文件写:

在这里插入图片描述

  1. 创建Map/Reduce项目

在这里插入图片描述
在使用 Eclipse 运行 MapReduce 程序时,会读取 Hadoop-Eclipse-Plugin 的 Advanced parameters 作为 Hadoop 运行参数,如果我们未进行修改,则默认的参数其实就是单机(非分布式)参数,因此程序运行时是读取本地目录而不是 HDFS 目录,就会提示 Input 路径不存在。
在这里插入图片描述
所以需要将hadoop修改的配置文件放在src下,hadoop的配置文件(如伪分布式需要 core-site.xml 和 hdfs-site.xml)。

为了看到提示消息,需要将hadoop下的log4j.properties也放入src下。最后变成:
在这里插入图片描述

MapReduce统计词汇

Map任务:

// 通过继承Mapper<KEYIN,VALUEIN,KEYOUT,VALUEOUT>并且重写map(KEYIN key, VALUEIN value, Context context)来实现每一个Map任务的逻辑
    // 文本的每一行分成splite
    // map任务的输入为<行号,内容>,也就是<Object ,Text >,实际用不到行号
    // map任务的输出为<单词内容,1>,也就是<Text,IntWritable>因为Map只要统计每一次单词出现的个数作为输出,剩下交给shuffle来完成归并为<单词内容,1的list>
    // Text,IntWritable相当于java中String和Integer,只不过在MapReduce计算过程中的中间结果都是用的MapReduce自己定义的数据类型
    // 每一次的Map任务可以看成是一个继承Mapper对象运行了一次map函数,所以要通过设置MapReduce作业的Class来达到目的job.setMapperClass(WordCount.TokenizerMapper.class)
    public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> {
        private static final IntWritable one = new IntWritable(1);	// 每一次map单词出现的个数,也就是1,表示这个单词出现过,作为VALUEOUT
        private Text word = new Text();		// 每一次运行map结果存放的单词,作为KEYOUT
        public TokenizerMapper() {
        }
        public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
            // JDK自带分词器API,将输入的每一行作为字符串创建分词器对象
        	StringTokenizer itr = new StringTokenizer(value.toString());
        	// 判断这个字符串中是否有下一个单词
            while(itr.hasMoreTokens()) {
            	// 每一次循环,设置输出key的内容
                this.word.set(itr.nextToken());
                // 每一次循环,将map函数的内容直接输出为<Text,IntWritable>作为中间结果,保存起来,Context是内部类,直接使用
                context.write(this.word, one);
            }
        }
    }

Reduce任务:

// Reduce逻辑就是把这些1求和
// 通过继承Reducer<KEYIN,VALUEIN,KEYOUT,VALUEOUT>并且重写reduce(KEYIN key, VALUEIN value, Context context)来实现每一个Reduce任务的逻辑
// shuffle的Reduce任务得到了<单词,<1,1,1,1>>的<单词内容,1的list>
// Reduce任务输出Text不变,但是IntWritable累加,需要在每一次的逻辑中设置输出值
// 每一次的Reduce任务可以看成是一个继承Reducer对象运行了一次reduce函数,所以要通过设置MapReduce作业的Class来达到目的job.setReducerClass(WordCount.IntSumReducer.class);
public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
    private IntWritable result = new IntWritable(); // Reduce任务的输出值,作为VALUEOUT

    public IntSumReducer() {
    }
 
    public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        int sum = 0;
        IntWritable val;
        // Iterator是shuffle之后value-list表中的迭代器
        for(Iterator shuffle_valuelist = values.iterator(); shuffle_valuelist.hasNext(); sum += val.get()) {
            val = (IntWritable)shuffle_valuelist.next(); // 迭代器指向下一个value-list的内容
        }
        // 每一次循环,设置Reduce任务的VALUEOUT
        this.result.set(sum);
        // 每一次循环,将<单词内容,出现和>输出保存起来,直到Reduce任务执行一次,得到结果。
        context.write(key, this.result);
    }
}

main函数:

public static void main(String[] args) throws Exception {
    	// 给一次MapReduce作业创建作业对象
        Job job = Job.getInstance();
        
        // 给作业传入Map处理逻辑
        job.setMapperClass(WordCount.TokenizerMapper.class);
        // 给作业传入Reduce处理逻辑
        job.setReducerClass(WordCount.IntSumReducer.class);
        // 给作业设置key的输出类型
        job.setOutputKeyClass(Text.class);
        // 给作业设置value的输出类型
        job.setOutputValueClass(IntWritable.class);
        
        // 整个作业的在HDFS上的输入源位置,可以是具体路径,具体路径代表该路径下的所有文件,也可以是具体文件
        FileInputFormat.addInputPath(job, new Path("input"));
        // 整个作业的输出位置
        FileOutputFormat.setOutputPath(job, new Path("output"));
        // 提交这次作业到集群上
        job.waitForCompletion(true);
    }

因为Eclipse运行MapReduce程序时,会读取 Hadoop-Eclipse-Plugin的Advanced parameters 作为 Hadoop 运行参数,没有修改,就会运行本地位置,所以需要添加相应的配置文件到src下:
在这里插入图片描述
复制相应的配置文件来,如伪分布式需要 core-site.xml 和 hdfs-site.xml,如果想要在控制台上打印出相应的运行日志,需要复制hadoop下的log4j.properties,不用log4j.properties不会影响正常运行。

一个wordcount程序如下:

import java.io.IOException;
import java.util.Iterator;
import java.util.StringTokenizer;
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.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
 
public class WordCount {
    public WordCount() {
    }

    public static void main(String[] args) throws Exception {
    	// 给一次MapReduce作业创建作业对象
        Job job = Job.getInstance();
        
        // 给作业传入Map处理逻辑
        job.setMapperClass(WordCount.TokenizerMapper.class);
        // 给作业传入Reduce处理逻辑
        job.setReducerClass(WordCount.IntSumReducer.class);
        // 给作业设置key的输出类型
        job.setOutputKeyClass(Text.class);
        // 给作业设置value的输出类型
        job.setOutputValueClass(IntWritable.class);
        
        // 整个作业的在HDFS上的输入源位置,可以是具体路径,具体路径代表该路径下的所有文件,也可以是具体文件
        FileInputFormat.addInputPath(job, new Path("input"));
        // 整个作业的输出位置
        FileOutputFormat.setOutputPath(job, new Path("output1"));
        // 提交这次作业到集群上
        job.waitForCompletion(true);
    }
 
    // Reduce逻辑就是把这些1求和
    // 通过继承Reducer<KEYIN,VALUEIN,KEYOUT,VALUEOUT>并且重写reduce(KEYIN key, VALUEIN value, Context context)来实现每一个Reduce任务的逻辑
    // shuffle的Reduce任务得到了<单词,<1,1,1,1>>的<单词内容,1的list>
    // Reduce任务输出Text不变,但是IntWritable累加,需要在每一次的逻辑中设置输出值
    // 每一次的Reduce任务可以看成是一个继承Reducer对象运行了一次reduce函数,所以要通过设置MapReduce作业的Class来达到目的job.setReducerClass(WordCount.IntSumReducer.class);
    public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
        private IntWritable result = new IntWritable(); // Reduce任务的输出值,作为VALUEOUT

        public IntSumReducer() {
        }
 
        public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            int sum = 0;
            IntWritable val;
            // Iterator是shuffle之后value-list表中的迭代器
            for(Iterator shuffle_valuelist = values.iterator(); shuffle_valuelist.hasNext(); sum += val.get()) {
                val = (IntWritable)shuffle_valuelist.next(); // 迭代器指向下一个value-list的内容
            }
            // 每一次循环,设置Reduce任务的VALUEOUT
            this.result.set(sum);
            // 每一次循环,将<单词内容,出现和>输出保存起来,直到Reduce任务执行一次,得到结果。
            context.write(key, this.result);
        }
    }
 
    // 通过继承Mapper<KEYIN,VALUEIN,KEYOUT,VALUEOUT>并且重写map(Object key, Text value, Context context)来实现每一个Map任务的逻辑
    // 文本的每一行分成splite
    // map任务的输入为<行号,内容>,也就是<Object ,Text >,实际用不到行号
    // map任务的输出为<单词内容,1>,也就是<Text,IntWritable>因为Map只要统计每一次单词出现的个数作为输出,剩下交给shuffle来完成归并为<单词内容,1的list>
    // Text,IntWritable相当于java中String和Integer,只不过在MapReduce计算过程中的中间结果都是用的MapReduce自己定义的数据类型
    // 每一次的Map任务可以看成是一个继承Mapper对象运行了一次map函数,所以要通过设置MapReduce作业的Class来达到目的job.setMapperClass(WordCount.TokenizerMapper.class)
    public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> {
        private static final IntWritable one = new IntWritable(1);	// 每一次map单词出现的个数,也就是1,表示这个单词出现过,作为VALUEOUT
        private Text word = new Text();		// 每一次运行map结果存放的单词,作为KEYOUT
        public TokenizerMapper() {
        }
        public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
            // JDK自带分词器API,将输入的每一行作为字符串创建分词器对象
        	StringTokenizer itr = new StringTokenizer(value.toString());
        	// 判断这个字符串中是否有下一个单词
            while(itr.hasMoreTokens()) {
            	// 每一次循环,设置输出key的内容
                this.word.set(itr.nextToken());
                // 每一次循环,将map函数的内容直接输出为<Text,IntWritable>作为中间结果,保存起来,Context是内部类,直接使用
                context.write(this.word, one);
            }
        }
    }
}

在hdfs中生成了相应的output目录,用来存放结果
在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值