大数据学习笔记-2020-09-16--MR中自定义outputFormat于MR中的join操作

MR中自定义outputFormat

outputFormat接口的实现类

OoutputFormat时MR输出时的基类,所有mr输出都实现了OF接口,一下时几种常见的OF实现类:

  • TextOutputFormat 文本输出:
    • 为默认输出格式,将每条记录写为文本行,键和值可以是任意类型,因为会调用它们的toSTring方法
  • sequenceFileOutputFormat :
    • 将SequenceFileOutputFormat的输出作为后续 MapReduce任务的输入,这便是一种好的输出格式,因为它的格式紧凑,很容易被压缩。
  • 自定义OutputFormat:
    • 即接下来要实现的内容,可以根据需求自定义实现输出

自定义OutputFormat

自定义OutputFormat的步骤
  • 自定义一个类继续FileOutputFormat
  • 自定义RecordWriter,改写输出数据的方法write
通过过滤网页日志案例实操自定义OutputFormat
  • 需求:

    过滤输入的log日志,将包含TOUHOU的网站记录输出到TOUHOU.log中,不包含的输出到other.log中

  • 输入数据为:

    net.log

    内容为:

    http://www.baidu.com
    http://www.google.com
    http://cn.bing.com
    http://www.TOUHOU.only.com
    http://www.sohu.com
    http://www.sina.com
    http://www.sin2a.com
    http://www.TOUHOU.com
    http://www.sindsafa.com
    http://www.google.com
    http://cn.bing.com
    http://www.TOUHOU.com
    http://www.TOUHOU.com
    http://www.sina.com
    
  • 预期输出:

    含有TOUHOU字样的条目和其他条目分开输出

  • 代码实现:

    CustomOF(自定义OF

    import org.apache.hadoop.io.NullWritable;
    import org.apache.hadoop.mapreduce.RecordWriter;
    import org.apache.hadoop.mapreduce.TaskAttemptContext;
    import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
    import java.io.IOException;
    
    /**
     * 继承FileOutputFormat
     * 实现getRecordWriter方法,返回自定义的RecordWriter
     */
    public class CustomOF extends FileOutputFormat<String, NullWritable> {
    
        public RecordWriter<String, NullWritable> getRecordWriter(TaskAttemptContext job) throws IOException, InterruptedException {
            return new CustomRecordWriter(job);
        }
    }
    

    CustomRecordWriter:

    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.NullWritable;
    import org.apache.hadoop.mapreduce.RecordWriter;
    import org.apache.hadoop.mapreduce.TaskAttemptContext;
    import java.io.IOException;
    
    /**
     * 自定义写出器,负责将数据写出到磁盘中
     * 继承RecordWriter,实现其中write方法
     * 另外实现其初始化和资源关闭
     */
    public class CustomRecordWriter extends RecordWriter<String, NullWritable> {
        //两个输出流和文件系统
        //通过两个输出流实现输出到不同文件中
        private FSDataOutputStream THOS;
        private FSDataOutputStream otherOS;
        private FileSystem fs;
    
        /**
         * 构造方法,通过传入的context创建输出流
         * 初始化输出流
         */
        public CustomRecordWriter(TaskAttemptContext job) throws IOException {
            fs = FileSystem.get(job.getConfiguration());
            //两个输出路径
            Path TOUHOUPath = new Path(
                    "e:/work/test/output1/TOUHOU.log");
            Path otherPath = new Path(
                    "e:/work/test/output1/other.log");
            //创建输出流
            THOS = fs.create(TOUHOUPath);
            otherOS = fs.create(otherPath);
        }
    
        /**
         * 写出的业务实现
         * 同其他自定义组件那样
         * 只要掌握了业务的内核以及实现的步骤
         * 本身的业务代码很简单,就是一个if判断
         */
        public void write(String key, NullWritable value) throws IOException, InterruptedException {
            //判断网址中是否包含TOUHOU字样,如包含输出到TOUHOU.log中
            if (key.contains("TOUHOU")){
                //TOUHOU
                THOS.write(key.getBytes());
            }else {
                //other
                otherOS.write(key.getBytes());
            }
        }
    
        /**
         * 负责在最后关闭资源
         */
        public void close(TaskAttemptContext context) throws IOException, InterruptedException {
            if(THOS!=null){
                IOUtils.closeStream(THOS);
            }
            if(otherOS!=null){
                IOUtils.closeStream(otherOS);
            }
            if(fs!=null){
                fs.close();
            }
        }
    }
    

    Mapper:

    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 java.io.IOException;
    
    /**
     * mapper类中只要读取数据并输出即可
     * 本次业务没有reduce阶段,所以mr会直接把mapper阶段的输出作为mr的输出数据处理
     */
    public class CustomMapper extends Mapper&lt;LongWritable, Text, String, NullWritable&gt; {
        //输出的key
        private String k_out;
    
        /**
         * 只要将输入数据的value、也即是一行网址作为key输出即可
         */
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            k_out=value.toString();
            //记得给输出加上换行,不然输出数据会只有一行
            context.write(k_out+"\r\n",NullWritable.get());
        }
    }dd
    

    driver(部分代码)

    //将reduceTask数量设置为0就可以跳过reduce阶段
    job.setNumReduceTasks(0);
    //设置使用自定义的outputFormat
    job.setOutputFormatClass(CustomOF.class);
    
  • 运行结果:

    我们可以在mr的输出路径看到成功标识文件:

在这里插入图片描述

在recordwriter中指定的输出路径中可以找到输出文件:

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.sindsafa.com
http://www.google.com
http://cn.bing.com
http://www.sina.com

TOUHOU.log:

http://www.TOUHOU.only.com
http://www.TOUHOU.com
http://www.TOUHOU.com
http://www.TOUHOU.com

MR中的JOIN操作

reduce Join

  • 原理:
    • map端将来自不通过表或文件的k-v对打标签区别不通来源的记录。然后将连接字段作为key,其余部分作为value,最后输出
    • reduce端组已经将数据以连接字段作为key分组完成了,我们只需要在每一个分组当中将那些来源不同的文件记录分开,最后合并即可。
  • 该法的缺点:
    • 很明显会导致shuffle阶段出现大量的数据传输,效率很低

reduce Join案例实操

  • 需求:

    • 订单数据表:

      idpidamount
      1001011
      1002022
      1003033
    • 商品信息表:

      pidpname
      01小米
      02华为
      03格力
    • 最终期望输出的表:

      idpnameamount
      1001小米1
      1004小米4
      1002华为2
      1005华为5
      1003格力3
      1006格力6
  • 分析:

    • 通过将关联条件作为map输出的key将两表满足join条件的额数据并携带数据来源的文件信息,法网同一个reduceTask,在reduce中进行数据的串联。
  • 代码实现

    封装类TableBean(setter getter略

    import org.apache.hadoop.io.Writable;
    import java.io.DataInput;
    import java.io.DataOutput;
    import java.io.IOException;
    /**
     * 利用flag来标识来自哪个文件
     */
    public class TableBean implements Writable {
        private String order_id;
        private String p_id;
        private String p_name;
        private String amount;
        private String flag;
    
         /**
         * 改写toString方法,使其输出我们想要的数据
         */
        @Override
        public String toString() {
            return order_id+"\t"+p_name+"\t"+amount;
        }
    
        public void write(DataOutput out) throws IOException {
            out.writeUTF(order_id);
            out.writeUTF(p_id);
            out.writeUTF(p_name);
            out.writeUTF(amount);
            out.writeUTF(flag);
        }
    
        public void readFields(DataInput in) throws IOException {
            order_id=in.readUTF();
            p_id=in.readUTF();
            p_name=in.readUTF();
            amount=in.readUTF();
            flag=in.readUTF();
        }
    }
    

    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 org.apache.hadoop.mapreduce.lib.input.FileSplit;
    import java.io.IOException;
    
    /**
     * 读取文件后先判断数据来自哪个文件
     * 区分文件对数据进行不通的封装处理
     */
    public class JoinMapper extends Mapper<LongWritable, Text, IntWritable, TableBean> {
        private IntWritable k_out = new IntWritable();
        private TableBean v_out =new TableBean();
        private String name;
    
        /**
         * setup方法在map方法前调用一次
         * 可以用于初始化一些参数
         * 这里用于判断数据来自哪个文件
         */
        @Override
        protected void setup(Context context) throws IOException, InterruptedException {
            FileSplit file = (FileSplit) context.getInputSplit();
            name = file.getPath().getName();
        }
    
        /**
         * map方法中要将数据封装到tableBean对象中
         * 然后写出
         */
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String[] words = value.toString().split("\t");
            if ("order.txt".equals(name)){
                //来自order文件,即订单文件
                v_out.setOrder_id(words[0]);
                v_out.setP_id(words[1]);
                v_out.setP_name("nodata");
                v_out.setAmount(words[3]);
                v_out.setFlag("order");
            }else {
                //来自p_id文件,即商品文件
                v_out.setOrder_id("nodata");
                v_out.setP_id(words[0]);
                v_out.setP_name(words[1]);
                v_out.setAmount("nodata");
                v_out.setFlag("pd");
            }
            //封装输出的键,即连接字段p_id(商品id
            k_out.set(Integer.parseInt(v_out.getP_id()));
            context.write(k_out,v_out);
        }
    }
    

    reducer

    import org.apache.commons.beanutils.BeanUtils;
    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.NullWritable;
    import org.apache.hadoop.mapreduce.Reducer;
    
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.util.ArrayList;
    
    /**
     * 利用map阶段对数据进行的标识完成分类
     * 将pd文件中读取到的数据于order文件中读取到的数据连接
     * 最后的到的结果写出
     */
    public class JoinReducer extends Reducer<IntWritable, TableBean, TableBean, NullWritable> {
        @Override
        protected void reduce(IntWritable key, Iterable<TableBean> values, Context context) throws IOException, InterruptedException {
            //用于存放数据的列表
            ArrayList<TableBean> tableBeans = new ArrayList<TableBean>();
    
            //新建一个类用于存放pd表中的数据
            TableBean pdBean = new TableBean();
            //遍历输入的values,将来自两张表的数据作区分
            for (TableBean bean : values) {
                if ("order".equals(bean.getFlag())){
                    TableBean orderBean = new TableBean();
                    try {
                        BeanUtils.copyProperties(orderBean,bean);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
    
                    //如果来自order文件,加入list中
                    tableBeans.add(orderBean);
                }else {
                    try {
                        //如果来自pd表,将其数据拷贝到提前准备好的对象中
                        BeanUtils.copyProperties(pdBean,bean);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
            //遍历连接表,并输出数据
            for (TableBean tableBean : tableBeans) {
                tableBean.setP_name(pdBean.getP_name());
                context.write(tableBean,NullWritable.get());
            }
        }
    }
    
    

    driver

    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.FileSystem;
    import org.apache.hadoop.fs.Path;
    import org.apache.hadoop.io.IntWritable;
    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 TableDriver {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
            //输入输出路径
            Path inputPath = new Path(
                    "e:/work/test/input/join");
            Path outputPath = new Path(
                    "e:/work/test/output");
    
            FileSystem fs = FileSystem.get(new Configuration());
            if (fs.exists(outputPath)){
                fs.delete(outputPath,true);
            }
            fs.close();
    
            //获取job
            Configuration conf = new Configuration();
            Job job = Job.getInstance(conf);
            
            job.setJarByClass(TableDriver.class);
    
            job.setMapperClass(JoinMapper.class);
            job.setReducerClass(JoinReducer.class);
    
            job.setMapOutputKeyClass(IntWritable.class);
            job.setMapOutputValueClass(TableBean.class);
    
            job.setOutputKeyClass(TableBean.class);
            job.setOutputValueClass(NullWritable.class);
            
    
            FileOutputFormat.setOutputPath(job,outputPath);
            FileInputFormat.setInputPaths(job,inputPath);
    
            boolean result = job.waitForCompletion(true);
            System.out.println(result?1:-1);
    
        }
    }
    

    输出结果为:

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

    结果正确。

map Join

  • 两种实现方法
    • 在mapper的setup阶段将文件读取到缓存中。
    • 在驱动函数中加载缓存
  • 适用场景
    • 适用于一张表非常小,另一张表很大的时候。
  • 优点
    • 在map端缓存多张表,提前处理业务逻辑,这样增加map端业务,减少reduce端数据的压力。

map join案例实操

  • 数据同reduce join的数据,需求同

  • 代码实现:

    mapper

    import org.apache.commons.lang.StringUtils;
    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 java.io.*;
    import java.net.URI;
    import java.util.HashMap;
    import java.util.Map;
    
    public class JoinMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
        private Map<String, String> pdMap = new HashMap<String, String>();
        private Text k_out = new Text();
    
        /**
         * 负责在map开始前将pd表加入内存中
         */
        @Override
        protected void setup(Context context) throws IOException {
            URI[] cacheFiles = context.getCacheFiles();
            for (URI cacheFile : cacheFiles) {
                BufferedReader reader = new BufferedReader(
                        new FileReader(new File(cacheFile)));
    
                String line = "";
                while (StringUtils.isNotBlank(line = reader.readLine())){
                    String[] words = line.split("\t");
                    pdMap.put(words[0],words[1]);
                }
    
                reader.close();
            }
        }
    
        /**
         * 只要简单的切割数据
         * 然后连接封装就好
         */
        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {
            //切割输入
            String[] words = value.toString().split("\t");
            //将数据连接封装
            k_out.set(words[0]+"\t"+pdMap.get(words[1])+"\t"+words[2]);
            //写出
            context.write(k_out,NullWritable.get());
        }
    }
    

    driver

    //注意这里的uri的路径和平日写的路径不同
    job.addCacheFile(new URI("file:///e:/work/test/input/pd.txt"));
    //设置reduce任务数为0
    job.setNumReduceTasks(0);
    

    输出结果为

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

    符合要求

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值