[大数据]Hadoop(3)MapReduce(4)

3.3 OutputFormat 数据输出

OutputFormat:将key-value的格式的数据写回到文件

OutputFormat源码:

public abstract class OutputFormat<K, V> {
    public OutputFormat() {
    }
	//对于给定的的任务,获取一个RecordWriter,接受KV值并处理的方法
    public abstract RecordWriter<K, V> getRecordWriter(TaskAttemptContext var1) throws IOException, InterruptedException;
	//检查OutputFormat的输出参数
    public abstract void checkOutputSpecs(JobContext var1) throws IOException, InterruptedException;
	//获取一个Output的提交器,保证output被正确的提交
    public abstract OutputCommitter getOutputCommitter(TaskAttemptContext var1) throws IOException, InterruptedException;
}

3.3.1 OutputFormat接口实现类

OutputFormat是MapReduce输出的基类,所有实现MapReduce输出都实现了OutputFormat的接口。

1)文本输出 TextOutputFormat
默认输出格式是TextOutputFormat,它把每条记录写为文本行。它的键和值可以是任意类型,因为TextOutputFormat调用toString()方法把他们转换成字符串。

2)SequenceFileOutputFormat
将SequenceFileOutputFormat输出作为后续MapReduce任务的输入,这便是一种好的输出合适,因为格式紧凑,容易被压缩。

3)自定义OutputFormat
根据用户需求,自定义实现输出。

3.3.2 自定义OutputFormat

1)使用场景:为了实现控制最终文件的输出路径和输出格式,可以自定义OutputFormat。如要在MapReduce程序中根据数据的不同输出两类结果到不同目录,这类灵活的输出需求可以通过自定义OutputFormat来实现。

2)自定义OutputFormat步骤:
自定义一个类继承FileOutputFormat,然后改写RecordWriter,具体改写输出数据的方法write。

3)案例需求

http://www.baidu.com
http://www.google.com
http://cn.bing.com
http://www.atguigu.com
http://www.sohu.com
http://www.sina.com
http://www.sin2a.com
http://www.sin2desa.com
http://www.sindsafa.com

将以上面输入分成两个文件输出,有atguigu的放在一个文件,其余的放在一个文件。

自定义OutputFormat类

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class MyOutputFormat extends FileOutputFormat<LongWritable, Text> {
    /**
     * 返回一个处理数据的Record Writer
     * @param taskAttemptContext
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public RecordWriter<LongWritable, Text> getRecordWriter(TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
        return new MyRecordWriter(taskAttemptContext);
    }
}

改写RecordWriter

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;

import java.io.IOException;


/**
 * 将数据按照包不包含atguigu分别输出到两个文件
 */
public class MyRecordWriter extends RecordWriter<LongWritable, Text> {

    FSDataOutputStream atguigu = null;
    FSDataOutputStream other = null;

    public MyRecordWriter(TaskAttemptContext taskAttemptContext) throws IOException {
        Configuration configuration = taskAttemptContext.getConfiguration();
        String outDir = configuration.get("mapreduce.output.fileoutputformat.outputdir");//获取输出目录
        FileSystem fileSystem = FileSystem.get(configuration);
        atguigu = fileSystem.create(new Path(outDir + "/atguigu.log"));
        other = fileSystem.create(new Path(outDir + "/other.log"));
    }

    /**
     * 接受key-value对,并按照值的不同写出到不同文件
     *
     * @param longWritable 读取这一行的偏移量
     * @param text         读取这一行内容
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public void write(LongWritable longWritable, Text text) throws IOException, InterruptedException {
        //获取一行数据
        String line = text.toString() + "\r\n";//待换行的数据
        if (line.contains("atguigu")) {
            //向atguigu文件写数据
            atguigu.write(line.getBytes());
        } else {
            //向other文件写数据
            other.write(line.getBytes());
        }
    }

    /**
     * 关闭资源
     *
     * @param taskAttemptContext
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public void close(TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
        IOUtils.closeStream(atguigu);
        IOUtils.closeStream(other);
    }
}

Dirver类中设置自定义的OutputFormat:

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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 OutputDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Job job = Job.getInstance(new Configuration());

        job.setJarByClass(OutputDriver.class);

        job.setOutputFormatClass(MyOutputFormat.class);//设置自己新建的OutputFormat类

        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        boolean b = job.waitForCompletion(true);
        System.exit(b ? 0 : 1);
    }
}

测试输出:
atguigu.log:

http://www.atguigu.com

other.log:

http://www.baidu.com
http://www.google.com
http://cn.bing.com
http://www.sohu.com
http://www.sina.com
http://www.sin2a.com
http://www.sin2desa.com
http://www.sindsafa.com

3.4 MapReduce工作流程

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

3.5 Join

3.5.1 Reduce join

Map端的主要工作:为来自不同表或文件的key/value对,打标签以区别不同来源的记录。然后用连接字段作为key,其余部分和新加的标志作value,最后进行输出。

Reduce端主要工作:在Reduce端以连接字段作为key的分组已经完成,我们需要在每一个分组当中将那些来源于不同文件的记录(在Mapj阶段已经打标志)分开,最后进行合并就完成了。

Reduce join 案例实操
1)需求
order.txt:

id		pid amount
1001	01	1
1002	02	2
1003	03	3
1004	01	4
1005	02	5
1006	03	6

pd.txt

pid name
01	小米
02	华为
03	格力

将商品信息表中的数据根据商品pid合并到订单数据表中。

id		pname	amount
1001	小米		1
1004	小米		4
1002	华为		2
1005	华为		5
1003	格力		3
1006	格力		6

2)需求分析
通过将关联条件作为Map输出的key,将两表满足的join条件的数据并携带数据所来源的文件信息,法网同一个ReduceTask,在Reduce中进行文件的串联。
在这里插入图片描述
3)代码实现:
1.创建商品和订单合并后的bean类。并按照pid分组,组内按照pname降序排列。
OrderBean.java:

import org.apache.hadoop.io.WritableComparable;

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

public class OrderBean implements WritableComparable<OrderBean> {
    private String id;
    private String pid;
    private int amount;
    private String pname;

    @Override
    public String toString() {
        return id + "\t" + pname + "\t" + amount;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getPid() {
        return pid;
    }

    public void setPid(String pid) {
        this.pid = pid;
    }

    public int getAmount() {
        return amount;
    }

    public void setAmount(int amount) {
        this.amount = amount;
    }

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    /**
     * 序列化
     *
     * @param dataOutput
     * @throws IOException
     */
    @Override
    public void write(DataOutput dataOutput) throws IOException {
        dataOutput.writeUTF(id);
        dataOutput.writeUTF(pid);
        dataOutput.writeInt(amount);
        dataOutput.writeUTF(pname);
    }

    /**
     * 反序列化
     *
     * @param dataInput
     * @throws IOException
     */
    @Override
    public void readFields(DataInput dataInput) throws IOException {
        this.id = dataInput.readUTF();
        this.pid = dataInput.readUTF();
        this.amount = dataInput.readInt();
        this.pname = dataInput.readUTF();
    }

    /**
     * 按照pid分组排序,组内按照pname降序排列
     *
     * @param o
     * @return
     */
    @Override
    public int compareTo(OrderBean o) {
        int i = this.pid.compareTo(o.pid);
        if (i != 0) {
            return i;
        } else {
            return o.getPname().compareTo(this.getPname());
        }
    }
}

2.实现分组比较器 OrderComparator.java

import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;

/**
 * 分组比较器,按照Order的pid分组
 */
public class OrderComparator extends WritableComparator {
    protected OrderComparator() {
        super(OrderBean.class, true);
    }

    /**
     * 按照PID比较a和b
     *
     * @param a
     * @param b
     * @return
     */
    @Override
    public int compare(WritableComparable a, WritableComparable b) {
        OrderBean oa = (OrderBean) a;
        OrderBean ob = (OrderBean) b;
        return oa.getId().compareTo(ob.getId());
    }
}

3.OrderMapper.java 按照数据来源的不同分别封装,发给同一个ReduceTask。

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;

import java.io.IOException;

public class OrderMapper extends Mapper<LongWritable, Text, OrderBean, NullWritable> {
    private OrderBean order = new OrderBean();
    private String filename;

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //获取输入数据的文件名
        FileSplit fs = (FileSplit) context.getInputSplit();
        filename = fs.getPath().getName();

        //切分
        String[] fields = value.toString().split("\t");

        //封装按照数据的来源不同分别分装
        if ("order.txt".equals(filename)) {
            //封装order5
            order.setId(fields[0]);
            order.setPid(fields[1]);
            order.setAmount(Integer.parseInt(fields[2]));
            order.setPname("");//没有用到的地方封装空值
        } else {
            //封装pd
            order.setPid(fields[0]);
            order.setPname(fields[1]);
            order.setAmount(0);
            order.setId("");
        }
        context.write(order, NullWritable.get());
    }
}

4.OrderReducer.java 对每一组,取第一组数据的pname,置换组中其余数据,然后写出。

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

import java.io.IOException;
import java.util.Iterator;

public class OrderReducer extends Reducer<OrderBean, NullWritable, OrderBean, NullWritable> {
    /**
     * 收到的数据,pd在一行的开头,order紧随其后
     *
     * @param key
     * @param values
     * @param context
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
        //获取迭代器
        Iterator<NullWritable> iterator = values.iterator();
        //迭代第一组数据
        iterator.next();
        String pname = key.getPname();

        //迭代剩余的数据,写出并输出
        while (iterator.hasNext()) {
            iterator.next();
            key.setPname(pname);//设置pname
            context.write(key, NullWritable.get());
        }
    }
}

5.OrderDriver.java 驱动

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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 OrderDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Path inputPath = new Path("E:\\笔记\\大数据项目\\input");
        Path outPath = new Path("E:\\笔记\\大数据项目\\output");

        Job job = Job.getInstance(new Configuration());

        job.setJarByClass(OrderDriver.class);

        job.setMapperClass(OrderMapper.class);
        job.setReducerClass(OrderReducer.class);

        job.setGroupingComparatorClass(OrderComparator.class);//设置比较器

        job.setMapOutputKeyClass(OrderBean.class);
        job.setMapOutputValueClass(NullWritable.class);

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

        FileInputFormat.setInputPaths(job, inputPath);
        FileOutputFormat.setOutputPath(job, outPath);

        boolean b = job.waitForCompletion(true);
        System.exit(b ? 0 : 1);
    }
}

输出结果:

1001	小米	1	
1001	小米	1	
1002	华为	2	
1002	华为	2	
1003	格力	3	
1003	格力	3

3.5.2 Map Join

1)使用场景
Map Join使用于一张表十分小,一张表很大的场景。

2)优点
在Reduce端处理过多的表,非常容易产生数据倾斜,于是可以在Map端缓存多张表,提前处理业务逻辑,这样增加Map端业务,减少Reduce端数据的压力,尽可能减少数据倾斜。

MapJoin 案例实操

3)需求:同案例3.5.2的情形

4)案例分析:
MapJoin适用于关联表中有小表的情形。将pd.txt加载到内存的缓存中,建立一个HashMap对应关系,对于大表order.txt,对读入的每一行进行切割,替换(查询Hash表中的键值对),写回。不需要reduce的过程。

5)代码实现
1.驱动类 MJDriver.java

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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;
import java.net.URI;
import java.net.URISyntaxException;

public class MJDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, URISyntaxException {
        Path inputPath = new Path("E:/笔记/大数据项目/input/order.txt");
        Path outputPath = new Path("E:/笔记/大数据项目/output");
        
        Job job = Job.getInstance(new Configuration());

        job.setJarByClass(MJDriver.class);

        job.setMapperClass(MJMapper.class);

        job.setNumReduceTasks(0);//不需要reduce阶段

        //设置分布式缓存,pd文件以缓存都进去 以供Mapper来读取
        //job.addCacheArchive(URI.create("file:///E:/input/pd.txt"));
        job.addCacheFile(new URI("file:///E:/笔记/大数据项目/input/pd.txt"));

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

        FileInputFormat.setInputPaths(job, inputPath);
        FileOutputFormat.setOutputPath(job, outputPath);

        boolean b = job.waitForCompletion(true);
        System.exit(b ? 0 : 1);
    }
}

2.MJMapper.java

import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.shaded.org.apache.commons.lang.StringUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;

public class MJMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
    private Map<String, String> pMap = new HashMap<>();
    private Text k = new Text();

    /**
     * 读取pd到pMap
     *
     * @param context
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void setup(Context context) throws IOException, InterruptedException {
        //开流
        URI[] cacheFiles = context.getCacheFiles();//获取一个数组 文件路径
        FileSystem fileSystem = FileSystem.get(context.getConfiguration());
        FSDataInputStream pd = fileSystem.open(new Path(cacheFiles[0]));

        //将文件按行处理,读取到pMap中
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(pd));//字节流转换为字符流
        String line;
        while (StringUtils.isNotEmpty(line = bufferedReader.readLine())) {
            String[] fields = line.split("\t");
            pMap.put(fields[0], fields[1]);
        }

        //关流
        IOUtils.closeStream(bufferedReader);
    }

    /**
     * 处理order.txt的数据
     *
     * @param key
     * @param value
     * @param context
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] fields = value.toString().split("\t");
        k.set(fields[0] + "\t" + pMap.get(fields[1]) + "\t" + fields[2]);//将pid替换
        context.write(k, NullWritable.get());//写回
    }
}

输出:

1001	小米	1
1002	华为	2
1003	格力	3
1004	小米	4
1005	华为	5	
1006	格力	6

3.6 MapReduce开发总结

3.6.1 输入数据接口:InputFormat

1)默认使用的实现类是:TextInputFormat
2)TextInputFormat的功能逻辑是:一次读一行文本,然后将改行起始的偏移量作为key,行内容作为value返回。
3)CombineTextInputFormat,可以把多个小文件合并成一个切片处理,提高处理效率。

3.6.2 逻辑处理接口:Mapper

用户根据业务需求实现其中三个方法:map() setup() cleanup()

3.6.3 Partitioner分区

1)默认实现HashPartitioner,逻辑是根据key的哈希值和numReduces来返回一个分区号,key.hashCode()&Integer.MAXVALUE%numReduces。
2)如果业务上有特别的需求,可以自定义分区。

3.6.4 Comparable排序

1)当我们用自定义的对象作为key来输出时,就必须要实现WriteableComparable接口,重写其中的compareTo()方法。
2)部分排序:对最终输出的每一个文件进行内部排序。
3)全排序:对所有数据进行排序,通常只有一个Reduce。
4)二次排序:排序的条件有两个。

3.6.5 Combiner合并

Combiner合并可以提高程序的执行效率,减少IO传输。但是使用时必须不能影响原有业务的处理结果。

3.6.6 逻辑处理接口:Reducer

用户根据业务需求实现其中三个方法:reduce(),setup(),cleanup()

3.6.7 输出数据接口:OutputFormat

1)默认实现类时TextOutputFormat,功能逻辑时:将每个KV对,向目标文本文件输出一行。
2)将SequenceFileOutputFormat输出作为后续MapReduce任务的输入,这便是一种好的输出格式,因为它的格式紧凑,很容易被压缩。
2)用户还可以自定OutputFormat。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值