Hadoop(五)——mapreduce(中)

回顾:

上一篇我们讲述了mapreduce处理hdfs中数据的过程,就是分为map阶段和reduce阶段,map阶段就是把hdfs中的数据给“包装”好,这个包装工作会有很多的map线程去做,并且以key——value的形式,通过context传递给reduce阶段。相同key的值底层会把他做成一个带迭代器的数组。reduce阶段也称规约阶段,目的就是把上面分散的数据给集合起来,所以reduce可以拿到这个key,和value的数组迭代器,就可以对数据进行整合,最后传递出去。

问题的引出:

我们之前在map阶段对数据的包装都是基本数据,那么如果我们的value是一个对象,是不是还是像之前的模式一样操作就可以?答案肯定是否定的,如果想要使用自定义数据类型来封装数据,就需要使用到序列化和反序列化。这是因为在MapReduce中要求被传输的数据能够被序列化

MapReduce中的序列化和反序列化:

案例:统计一个人 花费的总流量/出现过的地方

hdfs中的数据如下所示。
手机号/地区/名字/流量使用数

思路: 我们可以先用一个类来封装这下面的数据,然后在map阶段把封装好的数据作为value,用户名作为key传递给reduce阶段。
在这里插入图片描述

1.首先我们需要声明一个类来封装这些数据。
需要注意的是,这里我们就做了一个序列化和反序列化的操作,就按照代码里的来写就可以,很简单。
mapreduce序列化底层使用的是avro,但是它还是做了一层封装,所以只需要继承writable并且override一下就可以。

public class Flow implements Writable {

    private String phone = "";
    private String addr = "";
    private String name = "";
    private int flow;

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getFlow() {
        return flow;
    }

    public void setFlow(int flow) {
        this.flow = flow;
    }

    // 序列化
    // 只需要将有必要的属性(需要根据开发文档的要求)来一一写出即可
    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(phone);
        out.writeUTF(addr);
        out.writeUTF(name);
        out.writeInt(flow);
    }

    // 反序列化
    @Override
    public void readFields(DataInput in) throws IOException {
        this.phone = in.readUTF();
        this.addr = in.readUTF();
        this.name = in.readUTF();
        this.flow = in.readInt();
    }
}

2.map阶段的代码:
这里就是很常规的把文件里的数据给封装到对象中作为value,用户名作为key传递过去给reduce阶段

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

public class SerialFlowMapper
        extends Mapper<LongWritable, Text, Text, Flow> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        // 13877779999 bj zs 2145
        String[] arr = value.toString().split(" ");
        // 封装对象
        Flow f = new Flow();
        f.setPhone(arr[0]);
        f.setAddr(arr[1]);
        f.setName(arr[2]);
        f.setFlow(Integer.parseInt(arr[3]));
        context.write(new Text(f.getName()), f);
    }
}

3.reduce阶段:
从数据的特点我们可以看出,一个手机号只会对应一个人,而一个人可能对应多个手机号。

统计每一个人花费的总流量:以name作为key传递过来,所以每个数组中就包含这个name的所有手机使用信息。

统计每一个人出现过的地方: 一个人可能出现过多个地方,但是这里我们还是用name作为key,传递过来的数组里包含这个用户的所有信息。


import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
// 统计每一个人花费的总流量 - Text, IntWritable
public class SerialFlowReducer
        extends Reducer<Text, Flow, Text, IntWritable> {
    @Override
    protected void reduce(Text key, Iterable<Flow> values, Context context) throws IOException, InterruptedException {
        int sum = 0;
        for (Flow val : values) {
            sum += val.getFlow();
        }
        context.write(key, new IntWritable(sum));
    }
}

// 统计每一个人出现过的地方
class SerialFlowReducer2
        extends Reducer<Text, Flow, Text, Text> {
    @Override
    protected void reduce(Text key, Iterable<Flow> values, Context context) throws IOException, InterruptedException {
        StringBuilder sb = new StringBuilder();
        for (Flow val : values) {
            sb.append(val.getAddr()).append("\t");
        }
        context.write(key, new Text(sb.toString()));
    }
}

4.启动类:

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 SerialFlowDriver {

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

        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        job.setJarByClass(SerialFlowDriver.class);

        job.setMapperClass(SerialFlowMapper.class);
        job.setReducerClass(SerialFlowReducer.class);

        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Flow.class);

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

        FileInputFormat.addInputPath(job,
                new Path("hdfs://hadoop01:9000/txt/flow.txt"));
        FileOutputFormat.setOutputPath(job,
                new Path("hdfs://hadoop01:9000/result/serialflow"));

        job.waitForCompletion(true);
    }

}

分区(Partitioner):

作用:
用于对数据进行分类的

使用方法:
如果要自定义分区,需要定义一个类继承Partitioner类,覆盖其中的getPartition方法来指定分区逻辑

规则:
在MapReduce中要进行分区,默认对分区进行编号,编号从0开始递增
在MapReduce中,ReduceTask的数量默认就只有1个
每一个分区要对应一个ReduceTask,每一个ReduceTask都会产生一个结果文件

案例:计算每个学生每个月的考试总分
文件如下:
在这里插入图片描述
在这里插入图片描述
思路:我们可以先写一个类来做封装,虽然是三个文件,但是其实抽象思维一下,可以把他们看成一个大文件,map线程每次获取一行数据,把这个数据封装到对象中作为value,用学生名字来做key。
这里需要注意的是,在根据key组成数组发送给reduce之前,有一个分区的动作,分区会把已经分组好的数据,根据分区规则筛选一下,把符合规则的发配给同一个reduce
在这里插入图片描述
1.书写封装类:

package cn.tedu.partscore;

import org.apache.hadoop.io.Writable;

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

public class PartScore implements Writable {

    private int month;
    private String name;
    private int score;

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeInt(month);
        out.writeUTF(name);
        out.writeInt(score);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        this.month = in.readInt();
        this.name = in.readUTF();
        this.score = in.readInt();
    }
}

2.书写map:

package cn.tedu.partscore;

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

import java.io.IOException;

public class PartScoreMapper
        extends Mapper<LongWritable, Text, Text, PartScore> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] arr = value.toString().split(" ");
        PartScore s = new PartScore();
        s.setMonth(Integer.parseInt(arr[0]));
        s.setName(arr[1]);
        s.setScore(Integer.parseInt(arr[2]));
        context.write(new Text(s.getName()), s);
    }
}

3.书写分区类:

package cn.tedu.partscore;

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

public class PartScorePartitioner
        extends Partitioner<Text, PartScore> {
    @Override
    public int getPartition(Text key, PartScore value, int numPartitions) {
        int month = value.getMonth();
        //如month==1,就把该对象分配到0号reduce
        if(month==1)
        return 0;
        if(month==2)
        return 1;
        if(month==3)
        return 2;
    }
}

4.书写reduce

package cn.tedu.partscore;

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

import java.io.IOException;

public class PartScoreReducer
        extends Reducer<Text, PartScore, Text, IntWritable> {
    @Override
    protected void reduce(Text key, Iterable<PartScore> values, Context context) throws IOException, InterruptedException {
        int sum = 0;
        for (PartScore val : values) {
            sum += val.getScore();
        }
        context.write(key, new IntWritable(sum));
    }
}

5.启动类:
在写启动类的时候需要注意,如果不声明要几个reduce,默认就是1,后面输出文件也是1。

package cn.tedu.partscore;

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 PartScoreDriver {

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

        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        job.setJarByClass(PartScoreDriver.class);

        job.setMapperClass(PartScoreMapper.class);
        job.setReducerClass(PartScoreReducer.class);

        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(PartScore.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
//声明分区,和处理分区的reduce个数
        job.setPartitionerClass(PartScorePartitioner.class);
        job.setNumReduceTasks(5);

        FileInputFormat.addInputPath(job,
                new Path("hdfs://hadoop01:9000/txt/score1/"));
        FileOutputFormat.setOutputPath(job,
                new Path("hdfs://hadoop01:9000/result/partscore"));

        job.waitForCompletion(true);
    }


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

牛像话

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

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

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

打赏作者

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

抵扣说明:

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

余额充值