MapReduce编程练习1.0

MapReduce编程练习1.0

1.0 初始需求

需求: 有一批数据,手机通过基站上网留下了一下访问数据,数据内容如下,需要统计每一个用户(手机号)所耗费的总上行流量、总下行流量,总流量。

[外链图片转存失败(img-4yiB7x0A-1568619010488)(D:\学习笔记\hadoop\保存图片\MapReduce编程练习\数据格式.jpg)]

问题点:

  1. 按从左至右的顺序,字段的含义:时间戳,手机号,基站地址(表示手机访问的是哪个基站),请求网站的IP,请求网站的域名,请求网站的简介,上行流量个数,下行流量个数,上行总流量,下行总流量,请求的状态码。
  2. 数据不是以空格切分,而是以 \t 来进行切分。
  3. 数据存在缺失情况,比如第二行缺失请求网站的域名和请求网站的简介。
  4. 最后输出的接口不是一个值,而是三个(总上行流量、下行流量,总流量)。

解析:

  1. 数据切分方式不用管,这个之前练习时有方法设置,
  2. 数据缺失这个比较难整,初看,手机号好获取,没有缺失情况,对每一行数据切分后取第二个值就是;总上行流量、总下行流量,总流量这三个顺着看位置有变动,没法确认,但是反向思路,我们从后面数,总上行流量、总下行流量,总流量这三个固定在后面倒数2、3、4位置,这个可以利用一下啊,对数据切分后,取倒数2、3、4位置的即可(使用length - n)。
  3. 输出值的问题,这个我们不可能跑三个程序来处理这批数据,如果后续有其他的需求,不就没得玩了吗?在Hadoop序列化机制中,我们可以定义自己的一个类来封装数据,但是这个类需要实现Hadoop的序列化接口Writable。所以这里我们可以定义一个实现Writable接口的类来解决这个问题,这个类中包含三个参数,就是我们需要获取的总上行流量、下行流量,总流量。

1.1 初始需求解决编程

1. 先编写我们自定义的数据封装类:FlowBean
package com.chinasofti.mapreducepractice.flowsum;

import org.apache.hadoop.io.Writable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/*
 * 自定义的数据封装类,用于封装需求数据
 * Java 的序列化(Serializable)是一个重量级序列化框架,
 * 一个对象被序列化后,会附带很多额外的信息(各种校验信息,header,继承体系…),不便于在网络中高效传输;
 * hadoop 自己开发了一套序列化机制(**Writable**),精简,高效。
 * 不用像 java 对象类一样传输多层的父子关系,需要哪个属性就传输哪个属性值,大大的减少网络传输的开销。
 * 数据封装类需要实现序列化接口,Hadoop有自己的序列化接口Writable,实现Hadoop的Writable接口
 */
public class FlowBean implements Writable {

    // 我们要保存三个数据,所以在类中定义三个参数
    private long upflow = 0;    // 上行流量
    private long downflow = 0;  // 下行流量
    private long sumflow = 0;   // 总流量

    // 创建无参构造
    // 注意:如果没有创建这个无参构造方法,再反序列化的时候会出错
    public FlowBean(){

    }

    // 创建有参构造
    public FlowBean(long upflow, long downflow, long sumflow) {
        this.upflow = upflow;
        this.downflow = downflow;
        this.sumflow = sumflow;
    }

    //因为入参的时候有时会入下行流量和上行流量,没有入总流量,所有需要再建立一个这样的有参构造方法
    public FlowBean(long upflow, long downflow) {
        this.upflow = upflow;
        this.downflow = downflow;
        this.sumflow = upflow+downflow;
    }
    // bean对象常规操作:为属性提供get和set方法
    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;
    }

    // set数据时没有set方法,所以我们在这里手动编写一个set方法
    // 主要实现:给属性复赋值
    public void set(long upflow, long downflow){
        this.upflow = upflow;
        this.downflow = downflow;
        this.sumflow = upflow+downflow;
    }

    //这就序列化方法
    @Override
    public void write(DataOutput dataOutput) throws IOException {
        // 先序列化上行流量,再序列化下行流量,最后序列化总流量
        // 序列化的方法在 DataOutput 类中以write开头的方法
        dataOutput.writeLong(this.upflow);
        dataOutput.writeLong(this.downflow);
        dataOutput.writeLong(this.sumflow);
    }

    //这是反序列化方法
    //反序列时候  注意序列化的顺序
    //先序列化的先出来
    @Override
    public void readFields(DataInput dataInput) throws IOException {
        // 注意反序列化的顺序,先反序列化上行流量,再反序列化下行流量,最后反序列化总流量
        // 反序列化的方法在 DataInput 类中以read开头的方法
        this.upflow = dataInput.readLong();
        this.downflow = dataInput.readLong();
        this.sumflow = dataInput.readLong();
    }

    // 重写toSting方法
    @Override
    public String toString() {
        return upflow+"/t"+downflow+"/t"+sumflow;
    }
}
2. 编写Mapper继承类:FlowSumMapper

这里需要注意,避免每一次调用map方法时都会去创建一次kv对象,所以我们需要提前创建好kv对象,因为v是我们自己创建Bean对象,没有set方法,所以我们需要再为其创建一个set方法。

package com.chinasofti.mapreducepractice.flowsum;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

/*
 * 这里就是mapreduce程序  mapper阶段业务逻辑实现的类
 * KEYIN:KEYIN就表示每一行的起始偏移量  因此数据类型是Long
 * VALUEIN:VALUEIN就表示读取的这一行内容  因此数据类型是String
 * KEYOUT:KEYOUT表示手机号,因此数据类型是String
 * VALUEOUT:VALUEOUT表示输出的封装好的数据,因此数据类型是 封装好的bean对象
 */

public class FlowSumMapper extends Mapper<LongWritable, Text, Text, FlowBean> {

    // mapper类输出的数据是两个对象,不能每运行一次map方法就创建两个对象,这势必会造成很多垃圾
    // 我们可以先创建这两个对象,每次运行map方法时将值set这两个对象,从而避免创建多个对象的问题
    Text k = new Text();
    FlowBean v = new FlowBean();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        // 获取每一行的内容
        String line = value.toString();
        //将数据按 "/t" 进行切分,这个切分方式是根据数据源来决定的,
        // 一句mmp不知当讲不当讲,\t写成了/t,导致没数据
        String[] fields = line.split("\t");
        //切分后即可获取手机号和上下行流量
        // 手机号就是第二个
        String phoneNum = fields[1];
        // 上行流量是倒数第三个,切分完后数据类型是String,需要转换成Long
        long upflow = Long.parseLong(fields[fields.length - 3]);
        // 下行流量是倒数第二个,切分完后数据类型是String,需要转换成Long
        long downflow = Long.parseLong(fields[fields.length - 2]);

        // 为了避免重复创建对象而产生垃圾,我们先创建好key,value对象,然后通过各自的set方法去赋值
        k.set(phoneNum);
        // 因为数据封装类中没有set方法,所以我们自己去类中编写一个set方法
        v.set(upflow,downflow);

        // 通过context对象将数据传递给reduce
        context.write(k,v);

    }
}
3. 编写reducer继承类:FlowSumReducer
package com.chinasofti.mapreducepractice.flowsum;

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

import java.io.IOException;

/**
 * 这里是MR程序 reducer阶段处理的类
 * KEYIN:就是reducer阶段输入的数据key类型,对应mapper的输出key类型  手机号
 * VALUEIN就是reducer阶段输入的数据value类型,对应mapper的输出value类型  自己封装的bean对象
 * KEYOUT就是reducer阶段输出的数据key类型 在本案例中  手机号
 * VALUEOUTreducer阶段输出的数据value类型 在本案例中  求和后的总流量
        */
public class FlowSumReducer extends Reducer<Text, FlowBean, Text, FlowBean> {

    FlowBean v = new FlowBean();

    @Override
    protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
        // 为了统计求和,获取手机流量的总和,这里设置两个参数用来保存值
        long sumUpFlow = 0;
        long sumDownFlow = 0;
        
        // reduce做的事情很简单,只需要把流量拿出来统计即可
        for (FlowBean flowBean:values) {
            sumUpFlow += flowBean.getUpflow();
            sumDownFlow += flowBean.getDownflow();
        }
        // key还是之前的手机号,所以不用变,
        // value是计算后的值,这里同样为了避免创建多个对象而造成垃圾,先创建好对象后再来set值
        v.set(sumUpFlow,sumDownFlow);

        context.write(key,v);
    }
}
4. 编写driver类:FlowSumDriver
package com.chinasofti.mapreducepractice.flowsum;

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


public class FlowSumDriver {
    public static void main(String[] args) throws Exception {
        //通过Job来封装本次mr的相关信息
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

        //指定本次mr job jar包运行主类
        job.setJarByClass(FlowSumDriver.class);

        //指定本次mr 所用的mapper reducer类分别是什么
        job.setMapperClass(FlowSumMapper.class);
        job.setReducerClass(FlowSumReducer.class);

        //指定本次mr mapper阶段的输出  k  v类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(FlowBean.class);

        //指定本次mr 最终输出的 k v类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);

        // 暂不用设置reduce数量
//        job.setNumReduceTasks(3);
        //如果业务有需求,就可以设置combiner组件
//        job.setCombinerClass(FlowSumReducer.class);

        /*
         * 本地运行语句
         * 注意本地运行时输出目录也不能存在,否则也会报错
         */
        FileInputFormat.setInputPaths(job,"D:\\Practice_File\\hadoop_practice\\MapReduce\\flowsum\\input");
        FileOutputFormat.setOutputPath(job,new Path("D:\\Practice_File\\hadoop_practice\\MapReduce\\flowsum\\output"));
//        job.submit();
        //提交程序  并且监控打印程序执行情况
        boolean b = job.waitForCompletion(true);
        System.exit(b?0:1);
    }
}

[外链图片转存失败(img-AK9cbtNx-1568619010490)(D:\学习笔记\hadoop\保存图片\MapReduce编程练习\程序运行界面.jpg)]

[外链图片转存失败(img-u1RyI89G-1568619010491)(D:\学习笔记\hadoop\保存图片\MapReduce编程练习\初始需求运行结果.jpg)]

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值