day02_mapreduce编写,运行原理,案例

来源:doit教育

1 MR设计思想

image-20210623110527008

  1. 任务如何合理的分配?一般来说为了方便管理,DN机器内存大部分是一致的,所以划分任务的时候按照大小划分,DN收到任务后,并行地运算数据。
  2. 数据运算结束后,需要将数据发送到存储数据的机器上,可能需要多个机器来存储数据,为了保证数据的分布式存储(每台机器上对同一个数据要完整的存储例如:a只存储在DN1机器,不是在DN1和ND2上),引入了分区器。
  3. 分区器对map阶段处理的数据得hash值%2 ,根据得到0或者1的结果,把相应的数据存储到DN机器上。这个存储的过程就是reduce过程

2 入门程序

模拟mr在本地测试实现

package com._51doit.mr.day03;

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

/**
 * Description:
 * 统计文本中单词出现的次数
 *    逻辑 代码   测试环境下编写代码
 *  本地测试环境: 在本地模拟分布式运行, 加载本地数据
 */
public class MR1_Demo {

    // 1 map阶段
    /**
     * 继承Mapper类
     *  **** 类上有泛型:在Mr内部处理数据是以KV的形式处理的
     *  1 输入:从文件中读取数据 , 一行一行的读
     *  2 四个泛型
     *  KIN   行的偏移量  Long
     *  VIN   行数据     String
     *  KOUT  单词       String
     *  VOUT   1个       Integer
     *  3  输出的数据 会 传递给  下一个阶段(reduce阶段)  网络传输数据 所以  KVOUT要序列化
     *  JDK的序列化 : 完整的类结构 , 类名 属性类型 值 ... 1,zss,23  10 --> 90byte
     *  JDK 的序列化臃肿, 不适合大数据的数据传输场景
     *  hdp中有自己的序列化方式
     *  Long--> LongWritable
     *  String--> Text
     *  Integer--> IntWritable
     *
     */
    static class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable>{
       // 重写一个map方法
        // map方法一行执行一次   ,循环执行 , 当前任务有多少行 ,指定多少次
        @Override
        protected void map(LongWritable key, Text line, Context context) throws IOException, InterruptedException {
            // 获取一行数据  处理
            String lineStr = line.toString();
            String[] words = lineStr.split("\\s+");
            for (String word : words) {
                Text kout = new Text(word);
                IntWritable vout = new IntWritable(1);
                // context上下文对象 , 可以衔接map和reduce两个阶段
                // 输出 单词 1
                context.write(kout,vout);//a 1    a 1
            }

        }
    }
    // 2 reduce阶段
    /**
     * 继承Reducer类
     */
    static class WordCountReducer extends Reducer<Text, IntWritable ,Text , IntWritable>{
        // reduce方法 一个单词执行一次 a 1  a 1  a 1
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            int cnt = 0 ;
            for (IntWritable value : values) {
                cnt++ ;
            }
            context.write(key , new IntWritable(cnt));
        }
    }

    // 3 运行程序
    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        // 1 创建一个Job
        Job job = Job.getInstance(conf, "wordcount");
        // 2 设置mapper类
        job.setMapperClass(WordCountMapper.class);
        // 3 设置reduce类
        job.setReducerClass(WordCountReducer.class);
        // 4 设置map端和reduce端输出类型

        // 指定map端的输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        //指定最终结果的数据类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);


        // 设置reduce的个数
        job.setNumReduceTasks(2);
        //5 输入路径
        FileInputFormat.setInputPaths(job ,new Path("d://wc/"));
        // 6 输出路径
        FileOutputFormat.setOutputPath(job,new Path("d://wc_res"));
        //7 job提交  等待程序执行完毕
        boolean b = job.waitForCompletion(true);
    }

}

2.1内部实现细节

MR内部处理数据简易流程
  1. 本地中首先根据数据和文件数量和大小划分任务切片 split ,分别封装成不同的maptask
  2. 调度mapktask到不同的机器运行
  3. 反射生成类,调用Reader方法得到kin vin,也就是偏移量和每行的数据
  4. 循环执行map程序,执行完毕,得到kout vout,本例中(a,1)(a,1)(b,1)(b,1)(c,1)(d,1)(d,1)
  5. 分区器根据kout的哈希值取模得到区号,进行区内排序(a,1,1)(a,1,1) (c,1,1) ||| (b,1,0)(d,1,0)(d,1,0)
  6. 溢出,将排序好的数据以分区为单位,写到本地磁盘
  7. 启动reduceTask,不同的机器拉取属于自己分区的数据(这里如果map和reduce不是同一台机器,kout和vout就一定要实现序列化才可以拉取到)根据得到的kout分组,然后对对应的迭代器操作(本例中就是统计数量)
  8. 将数据合并,排序,分组
  9. 调用reduce方法,聚合输出。本案例中就是:(a,2) (b,1) (c,1) (d,2) 最终输出到磁盘。

2 线段案例

需求:求线段的交集例如: [1,2] [2,3] [1,4] —>>>[1,2] [2,3] [1,2,3,4] 计算出每个数字出现了几次

思路:本质还是求单词出现的次数。

package com._51doit.mr.day03;

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

/**
 * Author:   Hang.Z
 * Date:     21/06/23
 * Description:
 */
public class MR2_Line {

    static  class  LineMapper extends Mapper<LongWritable, Text, IntWritable , IntWritable>{
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String line = value.toString();
            String[] split = line.split(",");
            String x1 = split[0] ; // 2
            String x2 = split[1] ;// 6
            int x1Int = Integer.parseInt(x1);
            int x2Int = Integer.parseInt(x2);
            IntWritable k = new IntWritable();
            IntWritable v = new IntWritable();
            for(int i = x1Int ; i<=x2Int;i++){
                k.set(i);
                v.set(1);
                context.write(k,v);
            }
        }
    }

    static  class  LineReducer extends Reducer<IntWritable , IntWritable,IntWritable , IntWritable>{
        @Override
        protected void reduce(IntWritable key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            int cnt = 0 ;
            for (IntWritable value : values) {
                cnt++ ;
            }
            IntWritable v = new IntWritable();
            v.set(cnt);
            context.write(key ,v);

        }
    }


    public static void main(String[] args) throws  Exception{
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf, "line");
        job.setMapperClass(LineMapper.class);
        job.setMapOutputKeyClass(IntWritable.class);
        job.setMapOutputValueClass(IntWritable.class);
        job.setReducerClass(LineReducer.class);
        job.setOutputKeyClass(IntWritable.class);
        job.setOutputValueClass(IntWritable.class);
        FileInputFormat.setInputPaths(job,new Path("data/line.txt"));
        FileOutputFormat.setOutputPath(job, new Path("line_res"));
        job.waitForCompletion(true);//10s

    }
}

3 电影案例

3.1 电影均分

自定义Bean
package com._51doit.beans;

import org.apache.hadoop.io.Writable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.Serializable;

/**
 * Author:   Hang.Z
 * Date:     21/06/23
 * Description:
 * {"movie":"2288","rate":"4","timeStamp":"978160616","uid":"17"}
 * 这个类要放在Map端的VALUE的位置输出  序列化
 * 序列化本质
 *    内存对象--->二进制  写
 *    二进制--->内存对象  读
 */
public class MovieWritable implements Writable {

    private String movie ;
    private double rate ;
    private String timeStamp ;
    private String uid ;

    public String getMovie() {
        return movie;
    }

    public void setMovie(String movie) {
        this.movie = movie;
    }

    public double getRate() {
        return rate;
    }

    public void setRate(double rate) {
        this.rate = rate;
    }

    public String getTimeStamp() {
        return timeStamp;
    }

    public void setTimeStamp(String timeStamp) {
        this.timeStamp = timeStamp;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    @Override
    public String toString() {
        return "MovieWritable{" +
                "movie='" + movie + '\'' +
                ", rate=" + rate +
                ", timeStamp='" + timeStamp + '\'' +
                ", uid='" + uid + '\'' +
                '}';
    }
// 序列化
    public void write(DataOutput dataOutput) throws IOException {
        dataOutput.writeUTF(movie);
        dataOutput.writeDouble(rate);
        dataOutput.writeUTF(timeStamp);
        dataOutput.writeUTF(uid);

    }
// 反序列化
    public void readFields(DataInput dataInput) throws IOException {
      movie =  dataInput.readUTF();
      rate = dataInput.readDouble() ;
      timeStamp = dataInput.readUTF() ;
        uid = dataInput.readUTF() ;
    }
}

package com._51doit.mr.day03;

import com._51doit.beans.MovieWritable;
import com.alibaba.fastjson.JSON;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.IntWritable;
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;

/**
 * Author:   Hang.Z
 * Date:     21/06/23
 * Description:
 * 求每部电影的平均分数
 *    K  电影ID
 *    V  自定义的类(JavaBean 封装整条数据)  , 实现序列化
 * JavaBean
 *     1 私有属性
 *     2 公共的Get Set方法
 *     3 toString
 *     4 序列化
 */
public class MR3_Movie_AvgRate {

    static  class MovieAvgRateMapper extends Mapper<LongWritable,Text, Text, MovieWritable>{
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            try {
                String line = value.toString();
                // 解析JSON数据 解析失败 抛异常
                MovieWritable mb = JSON.parseObject(line, MovieWritable.class);
                String movie = mb.getMovie();
                Text k = new Text(movie);
                context.write(k, mb);
            } catch (Exception e) {  // 最大异常
                e.printStackTrace();
            }
        }
    }

    static  class MovieAvgRateReducer extends Reducer<Text, MovieWritable , Text , DoubleWritable>{

        @Override
        protected void reduce(Text key, Iterable<MovieWritable> values, Context context) throws IOException, InterruptedException {
            double sumRate = 0d ;
            int cnt = 0 ;
            for (MovieWritable mb : values) {
                double rate = mb.getRate();
                sumRate+=rate ;
                cnt++ ;
            }
            double avgRate =  sumRate/cnt ;
            DoubleWritable v = new DoubleWritable(avgRate);
            context.write(key , v);
        }
    }

    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf, "avg");
        job.setMapperClass(MovieAvgRateMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(MovieWritable.class);
        job.setReducerClass(MovieAvgRateReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(DoubleWritable.class);
        FileInputFormat.setInputPaths(job,new Path("D:\\mrdata\\movie\\input"));
        FileOutputFormat.setOutputPath(job, new Path("D:\\mrdata\\movie\\avg_out"));
        job.waitForCompletion(true);

    }

}

回顾 :

元数据管理机制 checkpoint

MR核心设计思想 ,使用多台节点并行计算 , 任务的划分 ,处理数据->结果–分区器–>获取全局结果

物理切块:HDFS文件存储的物理块 128M

任务切片:Maptask处理任务的数据大小 128M

尽量做到MRTASK运行的节点是DN节点

案例 : 1) xxMapper extends Mapper<kin , vin , kout,vout> :

map() 一对INKV执行一次 , 默认的文本文件,一行执行一次

​ 2) xxReducer extends Reducer<kin , vin , kout,vout>

reduce() 一组执行一次

context.write(K,V)

MR内部处理数据简单流程

  1. 输入路径/Mapper类/Reducer类/方法/逻辑

  2. 计算任务切片/初始化maptask任务[处理的数据信息, 封装Mapper类的map方法逻辑]

  3. 运行task读取数据 ;读取数据封装成kin vin --> map方法

  4. 在map方法中调用一次处理一行数据 , 一个maptask任务可能会执行多次map方法

  5. map方法输出Kout Vout ----->分区器–[Kout,Vout,int partition] —> 缓存中[区排序]----->>>输出到磁盘

  6. reduce启动 拉取属于自己任务的数据, 合并,排序,分租

  7. 一组数据执行一次reduce方法 进行数据聚合 保存结果 以分区任务为单位

3.2 评论次数最多的前n部电影

  1. 每部电影的评论次数-
package com._51doit.mr.day03;

import com._51doit.beans.MovieWritable;
import com.alibaba.fastjson.JSON;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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;
import java.util.*;


/**
 * Author:   Hang.Z
 * Date:     21/06/24
 * Description:
 * 评论次数最多的前n部电影
 * 高效topN
 */
public class MR4_Movie_TopN {
    static class MR4_Movie_TopN_Mapper extends Mapper<LongWritable, Text, Text, IntWritable> {
        /**
         * 一行执行一次
         *
         * @param key
         * @param value
         * @param context
         * @throws IOException
         * @throws InterruptedException
         */
        Text k = new Text();
        IntWritable v = new IntWritable(1);

        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            try {
                String line = value.toString();
                MovieWritable mb = JSON.parseObject(line, MovieWritable.class);
                String mid = mb.getMovie();
                k.set(mid);
                context.write(k, v);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    static class MR4_Movie_TopN_Reducer extends Reducer<Text, IntWritable, Text, IntWritable> {
        // 内存
        Map<String, Integer> map = new HashMap<String, Integer>();
        IntWritable v = new IntWritable();

        // 一组执行一次
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            int cnt = 0;
            for (IntWritable value : values) {
                cnt++;
            }
            v.set(cnt);
            map.put(key.toString(), cnt);
            // context.write(key , v);
        }

        // 当前的task中的reduce执行完毕以后会执行一次  cleanup方法
        @Override
        protected void cleanup(Context context) throws IOException, InterruptedException {
            // 对当前的task的所有的KV进行排序
            Set<Map.Entry<String, Integer>> entries = map.entrySet();
            ArrayList<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(entries);
            Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
                public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                    return o2.getValue() - o1.getValue();
                }
            });
            // top3
            Text k = new Text();
            for (int i = 0; i < Math.min(3, list.size()); i++) {
                Map.Entry<String, Integer> entry = list.get(i);
                String mid = entry.getKey();
                Integer cnt = entry.getValue();
                k.set(mid);
                v.set(cnt);
                context.write(k, v);
            }

        }
    }

    public static void main(String[] args) throws Exception {

        Configuration conf = new Configuration();
        // 1 创建一个Job
        Job job = Job.getInstance(conf, "topN");
        // 2 设置mapper类
        job.setMapperClass(MR4_Movie_TopN_Mapper.class);
        // 3 设置reduce类
        job.setReducerClass(MR4_Movie_TopN_Reducer.class);
        // 4 设置map端和reduce端输出类型

        // 指定map端的输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        //指定最终结果的数据类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        job.setNumReduceTasks(1);
        // 设置reduce的个数
       // job.setNumReduceTasks(1);
        //5 输入路径
        FileInputFormat.setInputPaths(job, new Path("D:\\mrdata\\movie\\input"));
        // 6 输出路径
        FileOutputFormat.setOutputPath(job, new Path("D:\\mrdata\\movie\\topn2"));
        //7 job提交  等待程序执行完毕
        boolean b = job.waitForCompletion(true);
    }


}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值