非常用心的好上手coding的MapReduce编程模型

一、前言

可以看这几个视频,挺棒的,可以快速上手MR开发。
先看这个链接的视频,非常短,会大题有一个了解:
1、深入浅出讲解MapReduece
再看这两个,以经典的wordcount为例,讲解原理与编程:
2、wordcount程序原理以及代码实现
3、mapreduce编程模型和具体实现框架之间的概念关系
有时间还可以看看3所在这个视频集合里的其他案例。

如果嫌视频慢的话,那就看我下面的代码以及文字吧。


我先以wordcount为例,来学习。据前辈说,好多的业务逻辑本质上都是wordcount的逻辑。
可以大概看看下面的第二部分,直接看代码,我在代码里面加了很多注释,对整个逻辑写的非常详细啦。

二、wordcount原理及分析

1、如何统计单词数?

在这里插入图片描述

2、MapReduce数据处理逻辑

在这里插入图片描述
要实现一个业务逻辑,关键在于:看我map这边产生写什么key,value。相同key的会通过reduce把对应的那些value聚合起来。

3、MapReduce架构(可略过)

在这里插入图片描述
我早年博客,可以加深理解:
https://blog.csdn.net/u013317445/article/details/51769986

在写代码之前,一定要把wordcount的业务逻辑搞清楚。知道mapper的输入输出是啥,知道reduce的输入输出是啥。知道map()实现啥,reduce()实现啥

三、wordcount code

code主要分为三部分:
Mapper部分:核心是重写里面的map()方法,把map阶段的业务逻辑写到map()方法里面。
Reducer部分:核心是重写里面的reduce()方法,把reduce阶段的业务逻辑写到reduce()方法里面。
Driver部分:在此封装我们的MR程序相关运行参数。整个程序需要一个Drvier来进行提交。

WordcountMapper.java

package wc;/**
 * Created by cc on 2019/11/26.
 */

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;

/**
 * @program: WordC
 * @description: map
 * @author: chen
 * @create: 2019-11-26 16:41
 *
 * Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> 输出输入类型:
 * KEYIN:默认下,是mr框架所读到的一行文本的起始偏移量,Long。
 *          但在hadoop里面有更加精简的接口,不直接用Long,而用LongWritable
 * VALUEIN:默认下,是mr框架所读到的一行文本内容,String
 *          同上,用Text(org.apache.hadoop.io里面的)
 * KEYOUT:是用户自定义逻辑处理完成后输出数据中的key,在此处是单词,String。同上,用Text
 * VALUEOUT:用户自定义逻辑完成之后输出数据中的value,在此处是单词词频,Integer
 *          同上,用IntWritable
 **/
public class WordcountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {

    /**
     * 把map阶段的业务逻辑写到map()方法里面
     * map task会对每一行输入数据调用一次我们自定义的map()方法。原来是每一行数据调一次map!!!
     * 之后,每调一次map()会有一个输出(一行数据处理完了有一个输出)。map task会把这些输出收集收集到一起。然后根据单词的不同给不同的区域,再发给不同的reduce task
     * 实际上,有多个map tasks,每个去处理一部分文本
     */
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //super.map(key, value, context);
        /**
         * map task会对每一行输入数据调用一次map()
         * 那么map()内要对这一行读入的文本作何处理呢?
         * 1、将文本内容转为字符串
         * 2、按空格分割字符串,获得每一个单词
         * 3、每一个单词,构建一个<word,1>,输出<word,1>,写到context里
         **/
        String line= value.toString();
        String[] words= line.split(" ");
        for(String word:words){
            //context.write(word,1); 错误,Mapper的输出类型是Text,IntWritable
            context.write(new Text(word), new IntWritable(1));

        }
    }
}

WordcountReducer.java

package wc;/**
 * Created by cc on 2019/11/27.
 */

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

import java.io.IOException;


/**
 * @program: WordC
 * @description: reduce
 * @author: chen
 * @create: 2019-11-27 10:33
 *
 * Reducer<KEYIN, VALUEIN ,KEYOUT, VALUEOUT >输入输出类型:
 * KEYIN, VALUEIN对应Mapper输出类型Text,IntWritable
 * KEYOUT, VALUEOUT是自定义Reducer的输出数据类型
 * KEYOUT是单词,类型Text
 * VALUEOUT是词频,类型IntWritable
 **/
public class WordcountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
    /**
     * 把reduce阶段的业务逻辑写到reduce()方法里面
     * reduce task拿到一组一组的数据,对每一组数据调一次reduce():
     * 如对<sun,1><sun,1><sun,1><sun,1><sun,1><sun,1>处理一次,调一次reduce();
     * 再下一个对<moon,1><moon,1><moon,1><moon,1>处理一次,调一次reduce(),......。
     *
     * reduce task数量可以指定,默认是1个
     *
     * 具体每一次reduce():
     * 对数据块,key为同一个,对应的value们串成了一个迭代器
     * 对value叠加,出count
     * 输出<key,count>到context,整合出了次数
     * */
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        //super.reduce(key, values, context);
        int count=0;

        for(IntWritable value: values){
            count += value.get();//.get()转为int
        }
//        Iterator<IntWritable> it= values.iterator();
//        while(it.hasNext()){
//            count += it.next().get();
//        }

        context.write(key, new IntWritable(count));//输出
    }
}

WordcountDriver.java

刚开始学,Driver这个就当成一个固定模板照着写吧,只需要改一下参数和路径。
主要在上面Mapper里面的map()方法重写,Reduecer里面的reduce()方法重写,把咱们的业务逻辑算法逻辑梳理清,就可以用代码表达啦。

package wc;/**
 * Created by cc on 2019/11/27.
 */

import org.apache.hadoop.conf.Configuration;
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;

import java.io.IOException;


/**
 * @program: WordC
 * @description: 写main()
 * @author: chen
 * @create: 2019-11-27 12:04
 *
 * 相当于yarn集群的客户端
 * 需要在此封装我们的MR程序相关运行参数,这顶jar包
 * 最后提交给yarn
 **/

/**
 * map:本质是拆解,汽车拆成零件
 * reduce:本质是组合,汽车零件+其它各种机械零件组合成变形金刚
 * input(取数据)+split(切分)+map()+ shuffle(归类数据)+reduce(组装)+finalize(交付)
 * */

public class WordcountDriver {

    public static void main(String[] args) throws Exception {
        Configuration conf= new Configuration();
        Job job= Job.getInstance(conf);  //创建一个job

        //指定本程序的jar包所在的本地路径(告诉yarn jar包在哪里)
        //job.setJar("path");//写死了路径
        job.setJarByClass(WordcountDriver.class);//不写死,就在jar包所在路径


        //指定本业务job要使用的mapper/reduce业务类
        job.setMapperClass(WordcountMapper.class);
        job.setReducerClass(WordcountReducer.class);

        //指定mapper输出数据类型,kv的
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        //指定最终输出数据类型,kv的
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
		
		//如果以后我们要自定义输入输出格式的话需要加上:
		//job.setInputFormatClass(MyInputFormat.class);
        //job.setOutputFormatClass(MyOutputFormat.class);

        //指定job的输入原始文件目录
        FileInputFormat.setInputPaths(job, new Path("F:\\ideaWorkspace\\WordC\\input"));//我把路径写死了
        //指定job的输出结果所在目录
        FileOutputFormat.setOutputPath(job, new Path("F:\\ideaWorkspace\\WordC\\output2"));

        //提交:将job中配置的相关参数,以及job所用的java类所在的jar包,提交给yarn去运行
        //job.submit();
        boolean res= job.waitForCompletion(true);
        System.exit(res? 0:1);//退出个0:job跑成功了
    }
}

四、梳理一下MR编程规范

  • 1、用户编写的程序分为三个部分:Mapper、Reducer、Driver(提交运行mr程序的客户端)
  • 2、Mapper的输入数据是KV对的形式(KV类型可自定义)
  • 3、Reducer的输出数据是KV对的形式(KV类型可自定义)
  • 4、Mapper中的业务逻辑写在map()方法中
  • 5、map()方法(maptask进程)对每一个<K,V>调用一次。(处理一次<K,V>调一次map)
  • 6、Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
  • 7、Reducer的业务逻辑写在reduce()方法中
  • 8、Reduce()方法对每一组相同K的<K,V>组调用一次
  • 9、用户的Mapper和Reducer都要继承各自的类
  • 10、整个程序需要一个Drvier来进行提交,提交的是一个描述了各种必要信息的job对象

map task们全部输出完了,reduce task们才开始去map的机器上去拿数据。
map task们输出数据时候,有outputCollector在收集<k1,v>,<k1,v><k1,v><k1,v>…
reduce task拿到一组一组的数据(相同key的为一组)
map task可能和reduce task在不同的机器上。

五、补充一下hadoop mapreduce内置数据类型

在考虑KV的输入输出类型的时候需要到这个知识点。

  • BooleanWritable 标准布尔型数值
  • ByteWritable 单字节数值
  • DoubleWritable 双字节数
  • FloatWritable 浮点数
  • IntWritable 整型数
  • LongWritable 长整型数
  • Text 使用UTF-8格式存储的文本
  • NullWritable 当<key, value>中的key或value为空时使用

这些内置数据类型,都实现了WritableComparable接口,以便用这些类型定义的数据可以被序列化进行网络传输和文件存储,以及进行大小比较。
我现在是能用内置类型就用内置类型,不用自己写了。
我们也可以自定义数据类型,不过要自己写一些接口。具体的话暂时没用到。

六、进一步,写Partitioner,写Combiner

https://www.cnblogs.com/frankdeng/p/9256254.html 可以看一下这个博客后面的部分。写了Partitioner把单词按照ASCII码奇偶分区,写了Combiner对每一个maptask的输出局部汇总。


好啦,就分享到这里吧。the end~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章