第三节 MapReduce(一)

MapReduce

1. 概述

1.1 MapReduce 是什么

  1. MapReduce 是谷歌改变世界的三篇论文之一,它是一个简化的并行计算编程模型,其最有 意义的地方在于,让一些 没有分布式编程经验的人员,在不会 并行编程 的情况下,将自己的程序运行在分布式系统上。
  2. MapReduce采用的是 “分散任务,汇总结果” 的思想,将大规模的数据集的清洗工作 分发给各个子节点完成,然后整合各个子节点的中间结果,最后汇总计算得到最终结果。

1.2 为什么要用集群的方式处理和计算文件?

  1. MapReduce可以看做两个单词,合作去干一件事。

    • 两个组件: Mapper组件(映射), Reduce组件(规约)。
    • 解释为: Mapper 做数据清洗,数据整理。 Reduce 数据汇总合并。
  2. 一台计算机,HDFS主要功能是管理和存储数据,主要利用磁盘存储能力MapReduce主要使用的是cpu的计算和处理,这就充分的使用了计算机的整体功能。

    • 如下图: 针对于集群中HDFSMapReduce 中的组件名称。
      在这里插入图片描述
  3. 了解了上面的一些知识之后,我们回想以前是咋处理文件的?使用单个机器去计算。为什么要用集群的方式处理和计算一个文件? 主要是因为大数据处理的数据集都是PB,TB级别,单个数据太大,以前的单机形式处理不合适了,所以采用了集群的形式, 集群可以理解为“人多干活快”!

    • MapReduce 利用集群的思想,每个机器处理整个文件中的一部分文件
    • MapReduce 有自己的一套分配机制,用来进行任务分配,这个分配工作由ResourceManager负责NodeManager去执行操作。
    • MapReduce 会对数据进行 “逻辑切块(inputsplit)” 默认128M切片,一个切片对应一个Map数量计算
    • MapReduce 对于数据的处理方式,默认按照行来处理,一行一行读取
    • MapReduce 也有自己的监测机制通过 RPC 心跳来监测子节点变化, 来保证运算能力。

1.3 集群与分布式区别

  1. 集群: 的主要场景是为了分担请求压力,就是增加机器数量解决问题 ,集群中任一节点,都是做一个完整的任务。
    • 同一个业务,部署在多个服务器上 。
  2. 分布式: 的主要应用场景是 单台机器已经无法满足这种性能的要求,必须要融合多个节点,并且节点之间是相关之间有交互的。
    • 将一套系统拆分成不同子系统,部署在不同服务器上。
  • 举一个生活的例子:
    例如, 餐厅中的厨师,配菜之间的关系。

2. MapReduce 编程模型

2.1 编程模型演变

  1. 想一下,如果统计一个大文件中的单词计数问题
    • 传统写法怎么去解决问题? 数据源从HDFS上取数据。
    • 将准备的数据上传到hdfs上。
      在这里插入图片描述
/**
 * 统计单词
 */
public class WordCount {
    public static void main(String[] args) throws Exception {
        //1.创建连接
        FileSystem fs = FileSystem.get(new URI("hdfs://192.168.150.130:9000"), new Configuration(), "root");
        //2.读取文件 
        FSDataInputStream fin = fs.open(new Path("/wordcount.txt"));
        //2.1 包一层高效的bufferReader,转换流使用。
        BufferedReader br = new BufferedReader(new InputStreamReader(fin));

        //3.2 创建集合 hashmap
        HashMap<String,Integer> hashMap = new HashMap();

        //3.开始读取
        String line = null;
        while ((line=br.readLine())!=null){
            String[] words = line.split(" "); //按照空格切
                //3.1 使用hashmap做单词计数
                for (String word : words){
                    if (hashMap.containsKey(word)){
                        hashMap.put(word,hashMap.get(word)+1); //如果之前存在加1
                    }else {
                        hashMap.put(word,1);
                    }
                }
        }

        //遍历输出map中数据
        for (Map.Entry<String,Integer> entry : hashMap.entrySet()){
            System.out.println(entry.getKey() +"----"+entry.getValue());
        }

        //后获取的先关,先获取的后关
            br.close();
            fin.close();

    }
}


输出结果:

yangmi   ...    2
hello   ...    6
liuyifei   ...    1
angelbaby   ...    3
  • 思考一个问题,我想让多个机器去一起做这件事:怎么设计
    在这里插入图片描述

2.2 编程模型简单概述

  1. 从上图来看,基本上属于MapReduce转变的前身设计思路,从MapReduce命名上来看有两部分组成,Map意思映射,Reduce为规约。
  2. 可以按照下面的方式理解MapReduce
    • 输入(input)一个大文件,通过切片(split)之后,将数据分成多个切片。
    • 每个文件切片由单独的节点进行处理,这就是Map方法。
    • 将各个节点计算的结果进行汇总,并得到最终结果,这就是Reduce方法。
    • 任务job = Map+Reduce,Map输出,就是Reduce的输入。且所有输入输出都是键值对<Key,Value>的形式。

3. MapReduce 组件介绍

3.1 导入MR依赖jar

  1. 导入Mapreduce的相关依赖jar包,因为需要编写MapReduce程序。
    在这里插入图片描述
  2. wordcount.txt 上传到hdfs上。
    在这里插入图片描述

3.2 Mapper 组件编程介绍

  1. 我们先了解Mapper的使用,看看Mapper的做了些什么事?
    • extends Mapper<????> 类
    • Driver类 程序的驱动类。
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;


//1.编写Map方法需要 extends Mapper<keyin,valuein,keyout,valueout>,为什么形参是这些,一会在讲解?
public class WordCountMapper extends Mapper<LongWritable, Text,LongWritable,Text> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        context.write(key,value); //context是输出的意思,直接输出key value值。
    }
}

  • 注意,Mapper的输出目录不能提前存在,否则报错。二次生成需要提前删除上一次,目录结果。
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.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.FileInputStream;
import java.io.IOException;

public class WordCountDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        //1.创建一个job任务入口
        Job job = Job.getInstance(new Configuration());//不需要配置文件就直接new Configuration();
        //2.设置jar包的启动路径
        job.setJarByClass(WordCountDriver.class);

        //3.设置key和value的输出参数类型,注意导入包hadoop.io.*下。
        job.setMapOutputKeyClass(LongWritable.class);
        job.setMapOutputValueClass(Text.class);

        //4.指定处理相应目录下的数据hdfs,和接受处理结果得目录(注意目录不要提前生成,有则删除)
        FileInputFormat.setInputPaths(job,new Path("hdfs://192.168.150.130:9000/wordcount.txt"));
       FileOutputFormat.setOutputPath(job,new Path("hdfs://192.168.150.130:9000/resault"));  


        //5.提交job任务,系统运行。
        job.waitForCompletion(true);
    }
}

3.3 运行程序两种方式

3.3.1 Jar包运行
  1. 将运行程序打成jar包,上传到服务器上运行。File--->Project structure
    在这里插入图片描述
    在这里插入图片描述

  2. 将jar上传到Linux服务器上,使用命令hadoop jar +jar包名称运行。

    • 注意:在运行hadoop jar时需要先,启动yarn平台。 然后使用jps查看启动进程。

在这里插入图片描述

在这里插入图片描述

3.3.2 IDEA运行
  1. 也需要依赖hadoop.dllwinutils文件 所以学习hdfs时已经配置好的就可以直接使用(参考hdfs插件配置), 但是不管使用哪种方式都是需要Yarn平台,所以如果使用IDEA运行就需要导入Yarn的依赖jar包,导入在工程中。

    • 注意, 因为MapReduce运行的其中一种方式需要Yarn,不然会报错org/apache/hadoop/yarn/util/Apps
      在这里插入图片描述
  2. 直接运行main即可。 结果什么意思? 还记得 偏移量 么?
    在这里插入图片描述

3.4 Mapper组件输出

  1. 现在我想对WordCount.txt中的单词进行统计,该怎么在mapper中做处理 ?
    • 提示: Text类型转String类型 Text.toString();
      在这里插入图片描述
package com.java.mapper;
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;

//keyin valuein保持不变接受类型就是 偏移量和每行内容,但是自定义 keyout valueout 是属于用户自定义类型。
/*
    既然想要每个单词的输出格式
            hello 1
            angelbaby 1
            。。。
            keyout 就是Text valueout 就是IntWritable类型
 */
public class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String s = value.toString();  //Text 相当于String Text.toString()转String类型。
        String[] words = s.split(" ");
        for (String word : words){
            context.write(new Text(word),new IntWritable(1)); // valueout 输出的1是自己定义的。
        }
    }
}

  • 注意Driver,当Mapper输入和输出类型不一致时,需要指定job.setMapperClass()
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;

public class Driver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //1.创建一个job任务,并关联启动类
        Job job = Job.getInstance(new Configuration());
        job.setJarByClass(Driver.class); //指定linux运行的

        //1.1 不然会报类型不匹配。默认保持输入输出一致。
        job.setMapperClass(WordCountMapper.class); //如果map输入和输出不一致就需要使用这个。

        //2.设置mapper输出格式
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);


        //3.设置路径
        FileInputFormat.setInputPaths(job,new Path("hdfs://192.168.150.130:9000/wordcount.txt"));
        FileOutputFormat.setOutputPath(job,new Path("hdfs://192.168.150.130:9000/resault"));

        //4启动程序
        job.waitForCompletion(true);
    }
}

3.5 Reduce组件编程介绍

  1. 了解完Mapper之后,我们来看看Reduce组件做了什么事情 ?
    • extends Reducer<????> 类
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

/*  第一组泛型 KEYIN Valuein 是 mapper输出的类型
    第二组泛型 就是自定义输出类型。
     我们先看看reducer给我们干了什么事情?
            第二组先默认输出 Text类型
 */
public class WordCountReduce extends Reducer<Text, IntWritable,Text,Text> {

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {

        //3,创建一个字符串接收拼串结果。
        String resault = "";

        //1.先把value值遍历出来看看
        for (IntWritable value :values){
            //2.将结果拼成一个串。
            resault = resault + value.get()+",";
        }

        //4.输出结果,key是Mapper输出的keyout类型
        context.write(key,new Text(resault));
    }
}

  • Driver类要添加Reducer类指定,和输出格式。
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;

public class Driver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //1.创建一个job任务,并关联启动类
        Job job = Job.getInstance(new Configuration());
        job.setJarByClass(Driver.class); //指定linux运行的

        //1.1 不然会报类型不匹配。默认保持输入输出一致。
        job.setMapperClass(WordCountMapper.class); //如果map输入和输出不一致就需要使用这个。
        job.setReducerClass(WordCountReduce.class); //指定reducer类型

        //2.设置mapper输出格式
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        //2.1 设置reducer输出格式
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);

        //3.设置路径
        FileInputFormat.setInputPaths(job,new Path("hdfs://192.168.150.130:9000/wordcount.txt"));
        FileOutputFormat.setOutputPath(job,new Path("hdfs://192.168.150.130:9000/resault"));

        //4启动程序
        job.waitForCompletion(true);
    }
}

在这里插入图片描述

4. MapReduce编程案例

4.1 WordCount 单词统计

  1. 根据上面代码经验,尝试着,自己写出单词统计。
    • 提示:IntWritable.get() 转int类型。
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;

public class WcMapper extends Mapper<LongWritable, Text,Text, IntWritable> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

        String[] words = value.toString().split(" ");
        for (String word : words){
            context.write(new Text(word),new IntWritable(1));
        }
    }
}


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

import java.io.IOException;

public class WcReduce extends Reducer<Text, IntWritable, Text,IntWritable> {

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {

        int count = 0;  //计数器
        for (IntWritable value:values){
            count = count +value.get();// IntWriable.get() 转换int
        }
        context.write(key,new IntWritable(count));
    }
}
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;

public class WcDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //1.启动job主线程
        Job job = Job.getInstance(new Configuration());
        job.setJarByClass(WcDriver.class);//在linux下启动的jar

        //2.指定mapper和reducer的类
        job.setMapperClass(WcMapper.class);
        job.setReducerClass(WcReduce.class);

        //3.设置mapper和reducer输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        //4.设置hdfs路径和输出地址
        //该路径下文件目录都会被处理,也可以指定到文件。
        FileInputFormat.setInputPaths(job,new Path("hdfs://192.168.150.130:9000/mr"));
        FileOutputFormat.setOutputPath(job,new Path("hdfs://192.168.150.130:9000/resault"));

        //5.提交job任务 ,并启动。
        job.waitForCompletion(true);
    }
}

在这里插入图片描述

4.2 数据去重

  1. 假如,电商统计数据,电话号有重复的不能多领取奖品,有这样的数据很多重复,筛除重复怎么做 ?
    • 主要,是使用reduce合并去重复。 提示:NullWritable.get() 相当于 null 类型
      在这里插入图片描述
import java.io.IOException;

public class DistinctMapper extends Mapper<LongWritable, Text,Text, NullWritable> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

        //NullWritable.get()转null类型 ; NullWritable相当于java中null。
        context.write(value,NullWritable.get()); //相当于排序输出
    }
}
public class DistinctReduce extends Reducer<Text, NullWritable,Text,NullWritable> {

    @Override
    protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {

        context.write(key,NullWritable.get()); //相当于使用reduce去重复。valueout 输出null即可。
    }
}

public class DistinctDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //1.启动job主线程
        Job job = Job.getInstance(new Configuration());
        job.setJarByClass(DistinctDriver.class);//在linux下启动的jar

        //2.指定mapper和reducer的类
        job.setMapperClass(DistinctMapper.class);
        job.setReducerClass(DistinctReduce.class);

        //3.设置mapper和reducer输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(NullWritable.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NullWritable.class);

        //4.设置hdfs路径和输出地址
        //该路径下文件目录都会被处理,也可以指定到文件。
        FileInputFormat.setInputPaths(job,new Path("hdfs://192.168.150.130:9000/dis"));
        FileOutputFormat.setOutputPath(job,new Path("hdfs://192.168.150.130:9000/dis/resault"));

        //5.提交job任务 ,并启动。
        job.waitForCompletion(true);
    }
}

在这里插入图片描述

4.3 求各部门平均工资(一)

  1. 求各部门的平均工资,该数据源来自于数据库导出格式。

在这里插入图片描述
在这里插入图片描述

public class AvgSalaryMapper extends Mapper<LongWritable, Text,Text, IntWritable> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

        String[] arrs = value.toString().split(" ");
        String department = arrs[2];
        int salary = Integer.parseInt(arrs[1]);

        context.write(new Text(department),new IntWritable(salary));
    }
}
public class AvgSalaryReduce extends Reducer<Text, IntWritable,Text, DoubleWritable> {

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {

        //统计和
        int count = 0;
        //计数器,
        int num = 0;
        for (IntWritable value : values){ //传过来的值是 k 和 v形式。
            count = count + value.get();
            num++; //计数

        }
        double avg = count/num; //类型转换? 这么写行不行?有没有问题。
        //System.out.println(key+"是"+count+" "+num);

        context.write(key,new DoubleWritable(avg)); //key值合并之后输出一次。
    }
}
public class AvgSalaryDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //1.启动job主线程
        Job job = Job.getInstance(new Configuration());
        job.setJarByClass(AvgSalaryDriver.class);//在linux下启动的jar

        //2.指定mapper和reducer的类
        job.setMapperClass(AvgSalaryMapper.class);
        job.setReducerClass(AvgSalaryReduce.class);

        //3.设置mapper和reducer输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(DoubleWritable.class);

        //4.设置hdfs路径和输出地址
        //该路径下文件目录都会被处理,也可以指定到文件。
        FileInputFormat.setInputPaths(job,new Path("hdfs://192.168.150.130:9000/salary"));
        FileOutputFormat.setOutputPath(job,new Path("hdfs://192.168.150.130:9000/salary/resault"));

        //5.提交job任务 ,并启动。
        job.waitForCompletion(true);
    }
}

思考一个问题:这结果正确么 ?

在这里插入图片描述

4.4 求员工中工资最高的人(二)

  1. 数据接 4.3 中数据,相当于求最大值的人名和薪资,输出结果:只有人名和最高薪资。
  2. 怎么处理数据的传递。
public class MaxMapper extends Mapper<LongWritable, Text, IntWritable,Text> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] words = value.toString().split(" ");
        /**思路分析: map作用就是切分,reduce作用是聚合。
         *  所以要想输出最值只有一条,那就需要在reduce聚合时key相同,value算出不同值。
         *  需要进行拼串   new StringBudile()
         */
        //拼串 形式 徐三,2450
        StringBuilder name_Salary = new StringBuilder().append(words[0]).append(",").append(words[1]);
        String string = name_Salary.toString(); //转String类型
        context.write(new IntWritable(0),new Text(string));//key值随便传,在reduce合并不输出即可。
    }
}

  • 因为key值都是0,相同不需要考虑,Reduce不输出key值即可。
public class MaxReduce extends Reducer<IntWritable, Text,Text,IntWritable> {
    @Override
    protected void reduce(IntWritable key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        int max =0; //最大值
        String resName=""; //最大值人名
        //1.遍历拿到value的拼串
        for (Text value :values){
            String[] words = value.toString().split(",");
             String name = words[0];
            int salary = Integer.parseInt(words[1]);

            if (max<salary){
                max=salary;
                resName=name;
            }
        }

        context.write(new Text(resName),new IntWritable(max));
    }
}

public class MaxDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //1.启动job主线程
        Job job = Job.getInstance(new Configuration());
        job.setJarByClass(MaxDriver.class);//在linux下启动的jar

        //2.指定mapper和reducer的类
        job.setMapperClass(MaxMapper.class);
        job.setReducerClass(MaxReduce.class);

        //3.设置mapper和reducer输出类型
        job.setMapOutputKeyClass(IntWritable.class);
        job.setMapOutputValueClass(Text.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        //4.设置hdfs路径和输出地址
        //该路径下文件目录都会被处理,也可以指定到文件。
        FileInputFormat.setInputPaths(job,new Path("hdfs://192.168.150.130:9000/max"));
        FileOutputFormat.setOutputPath(job,new Path("hdfs://192.168.150.130:9000/max/resault1"));

        //5.提交job任务 ,并启动。
        job.waitForCompletion(true);
    }
}

在这里插入图片描述

4.5 对象序列化:手机上网流量

  1. 针对上网数据,计算手机上网产生的,上行流量和下行流量和。
  2. 面向对象编程注意:涉及到对象序列化问题。一定要保证序列化和反序列化一致
  3. NullPointerException 如果不实现Writable接口。

在这里插入图片描述

  • 封装 Flow 对象,还记得在hdfs中的指定编码去读文件么!?
public class Flow implements Writable {
    private String address; 
    private String phone_Number;
    private long upFlow;
    private long downFlow;
    private long sumFlow; //总流量相当于 上行加下行流量

    //提供构造方法
    public Flow(){}

    //含参数构造方法
    public Flow(String phone_Number,String address,long upFlow, long downFlow) {
        this.phone_Number =phone_Number;
        this.address = address;
        this.upFlow = upFlow;
        this.downFlow = downFlow;
        this.sumFlow =upFlow+downFlow; //眼前一亮,计算总流量。
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPhone_Number() {
        return phone_Number;
    }

    public void setPhone_Number(String phone_Number) {
        this.phone_Number = phone_Number;
    }

    public long getUpFlow() {
        return upFlow;
    }

    public void setUpFlow(long upFlow) {
        this.upFlow = upFlow;
    }

    public long getDownFlow() {
        return downFlow;
    }

    public void setDownFlow(long downFlow) {
        this.downFlow = downFlow;
    }

    public long getSumFlow() {
        return sumFlow;
    }

    public void setSumFlow(long sumFlow) {
        this.sumFlow = sumFlow;
    }

    @Override
    public String toString() {
        return "Flow{" +
                "address='" + address + '\'' +
                ", phone_Number='" + phone_Number + '\'' +
                ", upFlow=" + upFlow +
                ", downFlow=" + downFlow +
                ", sumFlow=" + sumFlow +
                '}';
    }

    /*1.重写两个方法write序列化方法。
                readFillds 反序列化方法。
               2.注意序列化顺序和反序列化顺序保持一致。
             */
    @Override
    public void write(DataOutput dataOutput) throws IOException {
        dataOutput.writeUTF(phone_Number);
        dataOutput.writeUTF(address);
        dataOutput.writeLong(upFlow);
        dataOutput.writeLong(downFlow);
        dataOutput.writeLong(sumFlow);
    }

    @Override
    public void readFields(DataInput dataInput) throws IOException {
        this.phone_Number=dataInput.readUTF();
        this.address=dataInput.readUTF();
        this.upFlow = dataInput.readLong();
        this.downFlow = dataInput.readLong();
        this.sumFlow = dataInput.readLong();
    }

}


public class FlowMapper extends Mapper<LongWritable, Text,Text,Flow> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //15639120688	http://v.baidu.com/movie 3936 12058 shanghai
        //1,按照规则切分数据
        String[] words = value.toString().split("\t");//按照制表符切
        String phone = words[0];

        //1.1  在按照空格切分数据
        String[] words2 = words[1].split(" ");//按照空格切
        long upFlow = Long.parseLong(words2[1]);
        long downFlow = Long.parseLong(words2[2]);
        String address = words2[3];

        //2.封装对象
        Flow flow = new Flow(phone,address,upFlow, downFlow);
        context.write(new Text(phone),flow);
    }
}

public class FlowReduce extends Reducer<Text,Flow,Text,Flow> {
    @Override
    protected void reduce(Text key, Iterable<Flow> values, Context context) throws IOException, InterruptedException {
        //根据数据分析,有重复手机号段,就需要对结果进行累加
        long upFlow =0;
        long downFlow =0;
        String adress="";
        String phone_Num="";

        for (Flow f:values){
            upFlow = upFlow + f.getUpFlow();//上行流量,累加求和。
            downFlow = downFlow + f.getDownFlow();//下行流量,累加求和。
            phone_Num=f.getPhone_Number();
            adress=f.getAddress();
        }

        //通过构造方法赋值
        Flow flow = new Flow(phone_Num,adress,upFlow,downFlow);
        context.write(new Text(key),flow);
    }
}

public class FlowDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //1.启动job主线程
        Job job = Job.getInstance(new Configuration());
        job.setJarByClass(FlowDriver.class);//在linux下启动的jar

        //2.指定mapper和reducer的类
        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReduce.class);

        //3.设置mapper和reducer输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Flow.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Flow.class);

        //4.设置hdfs路径和输出地址
        //该路径下文件目录都会被处理,也可以指定到文件。
        FileInputFormat.setInputPaths(job,new Path("hdfs://192.168.150.130:9000/flow"));
        FileOutputFormat.setOutputPath(job,new Path("hdfs://192.168.150.130:9000/flow/resault"));

        //5.提交job任务 ,并启动。
        job.waitForCompletion(true);
    }
}

在这里插入图片描述

5. 分区

5.1 分区概述

  1. 在进行MapReduce时,有时候需要把 最终的输出数据 分到不同的文件中,例如:如果要按照部门划分,就需要将相同部门的人放到一个文件中。谁来操作这个最终的输出? 答案是: Reducer,
  2. 如果想要得到多个文件,就需要设置相同数量的Reducer任务在运行,Reducer的数据来自Mapper任务,Mapper要划分数据,将不同的数据分配给不同的Reducer运行,这个划分的过程称为分区(Partition)负责实现划分数据的类成为Partitioner。
    • Hadoop默认是一个Reducer。需要在Driver类中设置Reducer的数量。
job.setNumReduceTasks(3); //例如,三个分区。即生成3个不同的文件。
  1. 通过 4.3 求各部门的平均工资案例,来看下分区。 得到如下结果:?为什么?

    在这里插入图片描述

  2. Partitioner类干了啥事情?

    • 相当于key.hashcode()%reducer个数取余,不能保证结果均匀的分配到文件中。
    • 这样会造成数据倾斜现象。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.2 自定义分区

  1. 改造案例 4.5 手机上网流量按照地区分输出文件。
    • 如果按照分区输出的话,需要分5个输出文件。这就需要自定义分区。
    • 自定义分区要extends Partition<key,value> 第一个泛型时Mapper输出key 第二个是Mapper输出Value类型
    • 重写方法中getPartition(Text text, Flow flow, int i) 的第一个参数类型,是reduce输出的key相同。第二个参数类型是reduce输出的value相同。第三个参数,是job拿到的第几个分区。
      在这里插入图片描述
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;

/*自定义分区类
 *      extends Partition<?,?> 主要是map进行工作。
        * 第一个泛型的,是map的输出类型的key。
        * 第二个泛型的,是map的输出类型的value。

        @Override 重写getPatition()方法
        *
 */
public class MyPartitioner extends Partitioner<Text,Flow> {

    /*
        getPartition 的第一个参数类型,是reduce输出的key相同。
	 *               第二个参数类型是reduce输出的value相同。
	 * int numPartitions 是从job中拿到的几个分区。

     */

    @Override
    public int getPartition(Text text, Flow flow, int i) {
        if (flow.getAddress().equals("chengdu")){
            return 0;//去第0个分区。
        }else if (flow.getAddress().equals("guangzhou")){
            return 1;
        }else if (flow.getAddress().equals("shenzhen")){
            return 2;
        }else if(flow.getAddress().equals("beijing")){
            return 3;
        }else {
            return 4; //最后一个上海
        }

    }
}

  • Driver类中添加 设置如下:
   //自定义分区设置 增加输出和设置分区类
        job.setNumReduceTasks(5);
        job.setPartitionerClass(MyPartitioner.class);

在这里插入图片描述

6. Hadoop的数据类型对照

Java类型Hadoop类型
booleanBooleanWritable
Integer intIntWritable
Long longLongWritable
Float floatFloatWritable
Double doubleDoubleWritable
StringText
nullNullWritable

7. MapReudce 不擅长的场景

  1. 通过学习MapReduce之后,它虽然有很多优势,如,处理PB级别数据,高容错性,一台机器出问题,不用担心,job可以在其他机器上运行。扩展性高,当资源不满足时可以增加机器数量,但是它也有一些不擅长的场景:
    • 实时计算,它属于流式输出数据,无法像MySQL一样,在毫秒级或者秒级别返回数据。
    • 流式计算,不能动态输入,实时计算数据,而是输入的数据需要是静态的,实时计算产生的日志就不可以,需要其他流式实时框架处理。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 在Hadoop中使用第三方MapReduce程序,需要将程序打包成一个可执行的jar文件,并将其上传到Hadoop集群上。这个jar文件需要上传到所有的Hadoop节点上,包括NameNode和DataNode节点。这样才能确保程序在整个Hadoop集群中都可以运行。一般情况下,可以使用Hadoop自带的hadoop命令行工具来上传jar文件到Hadoop集群中。具体操作步骤可以参考Hadoop官方文档。 ### 回答2: Hadoop使用第三方的MapReduce,需要上传到集群中的所有节点上。 Hadoop是一个分布式计算框架,其核心思想是将大规模数据集分解为多个小数据块,并将这些数据块分散存储在不同的节点上。当使用第三方的MapReduce时,Map任务和Reduce任务需要执行在集群中的不同节点上,以实现并行计算和分布式处理。 在Hadoop中,我们通常会将自己编写的MapReduce程序打包成一个JAR文件,并使用Hadoop提供的命令将该JAR文件上传至Hadoop集群。一旦成功上传到集群上,Hadoop会将该JAR文件在各个节点上进行复制和分发,以确保所有节点上都能够访问到该文件。 当我们通过Hadoop提交任务时,Hadoop会按照设定的配置,在集群中选择一定数量的节点作为任务执行节点。对于Map任务,每个任务节点将会接收到一部分输入数据进行处理;而对于Reduce任务,不同的任务节点将接收到不同的Map任务输出结果,进行进一步的处理。 因此,当使用第三方的MapReduce时,其执行过程需要上传到集群中的所有节点上,以保证任务能够在集群中的各个节点上进行并行计算,从而实现更高效的大规模数据处理。 ### 回答3: Hadoop 使用第三方 MapReduce 时,需要将程序上传到集群中的几个节点上。 在 Hadoop 集群中,通常由一个 Master 节点和多个 Slave 节点组成,Master 节点负责调度任务和管理整个集群的状态,而 Slave 节点则负责执行任务。 当我们利用 Hadoop 运行第三方 MapReduce 程序时,需要将该程序上传到集群中的两个节点上,分别是 Master 节点和一个 Slave 节点。在 Master 节点上,我们需要上传该程序的驱动代码,用于调度和管理 MapReduce 任务的执行。同时,在一个 Slave 节点上,我们需要将 MapReduce 程序的其他相关文件(如 Mapper 和 Reducer)上传,用于实际的数据处理和计算。 上传这些文件到集群节点上,可以通过 Hadoop 提供的命令行工具或者通过 Hadoop 的 Web 界面进行操作。具体的步骤如下: 1. 将 MapReduce 程序的驱动代码上传到 Master 节点。 - 可以通过命令行使用 `hadoop fs` 命令将代码文件上传到 Hadoop 分布式文件系统(HDFS)中。 - 或者通过 Web 界面使用 Hadoop 的文件浏览器将代码文件上传到 HDFS 中。 2. 将 MapReduce 程序的其他相关文件上传到一个 Slave 节点上。 - 同样可以通过命令行使用 `hadoop fs` 命令将文件上传到 HDFS 中。 - 或者通过 Web 界面使用 Hadoop 的文件浏览器将文件上传到 HDFS 中。 上传完成后,Hadoop 将自动将这些文件分发到集群中的对应节点上,以供 MapReduce 任务的执行。 需要注意的是,具体需要上传到几个节点上,取决于集群的配置和并发任务的需求。在一个典型的 Hadoop 集群中,通常会有多个 Slave 节点,我们可以选择将程序上传到其中的一个节点上,然后 Hadoop 会自动将其分发到其他 Slave 节点上,并同时运行多个实例以处理不同的数据块。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吴琼老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值