MapReduce初步学习总结(二)

  1. MR的输入处理类介绍
    InputFormat负责处理mr的输入部分,其中有两个抽象方法getSplits和createRecordReader。用来验证输入文件是否规范,并切分成片,把split调到mapper中处理。MR在执行前是把文件分片执行的,也就是说,你一个分片对应一个map任务,map进程。每一个split,都会分成多个kv对,每个kv对都会调用一次该map进程的map函数。所以,当你的分片过多,map进程就会过多,导致卡顿。所以,我们不能输入大量小文件,因为对于小文件是不会分片的,一个文件对应一个split,这样也就表示mr并不适合处理小文件。FileINputFormat代码中,对于如何分片是由Math.max(minSize, Math.min(maxSize, blockSize))所决定的,默认就是一个块大小。FileInputFormat是以文件为输入的处理类的基类,它实现了getsplit方法,把文件分片,至于另外一个方法,由他的不同子类来进行不同实现。输入文件的来源多种多样,这就需要设置不同的输入处理类来分别处理,下面介绍部分输入处理类。

  2. TextInputFomat

    这个是默认的输入处理类,他处理普通文本文件,以一行的偏移量为key,一行的文本为value。默认以\n或回车符来分割行。这个类很基本,就不用代码演示了。

        

    NLineInputFormat

根据用户设定的行数来分片,默认1

    public static void main(String[] args) throws Exception {

        Configuration conf=new Configuration();

        Job job=Job.getInstance(conf);

        //

        job.setInputFormatClass(NLineInputFormat.class);

        //nl 设置

        NLineInputFormat.setNumLinesPerSplit(job, Integer.parseInt(args[0]));

        job.setJarByClass(NlineInputFormatTest.class);

        job.setOutputKeyClass(Text.class);

        job.setOutputValueClass(LongWritable.class);

        job.setMapperClass(NLMapper.class);

        job.setReducerClass(NLReduce.class);

        FileInputFormat.setInputPaths(job, new Path("/ceshishuju"));

        FileOutputFormat.setOutputPath(job, new Path("/out"));

        job.waitForCompletion(true);

}

测试了一下如果输入数据是两个文件,比如a.txtb.txt分别有三行数据,则有四个分片。

这样会使分片数增加,而使map任务增加,如果输入数据有特定规律,比如某几行是一套数据,可以使用。

 

 

 

   KeyValueTextInputFormat

如果行中有分隔符,那么分隔符前面的作为key,后面的 作为value;如果行中没有分隔符,那么整行作为keyvalue为空

public static void main(String[] args) throws Exception {

        Configuration conf=new Configuration();

        Job job=Job.getInstance(conf);

        //

        job.setInputFormatClass(KeyValueTextInputFormat.class);

        //kv 设置

        conf.setStrings(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, "\t");

        job.setJarByClass(KeyValueInputFormatTest.class);

        job.setOutputKeyClass(Text.class);

        job.setOutputValueClass(LongWritable.class);

        job.setMapperClass(KVMapper.class);

        job.setReducerClass(KVReduce.class);

        FileInputFormat.setInputPaths(job, new Path("/ceshishuju"));

        FileOutputFormat.setOutputPath(job, new Path("/out"));

        job.waitForCompletion(true);

}

注意,用了该input后,你的k1类型或许会有变化,注意更改。默认分隔符为“\t

 

SequenceFileInputFormat

           SequenceFile处理、压缩处理

 

  DBInputFormat

输入源是数据库的时候

你的v1需要自定义一个序列化类,来存储从数据库读到的资源,要实现Writable, DBWritable接口

public class LogWriteable implements Writable,DBWritable{

    private String name;

    private String sex;

   

    @Override

    public void write(PreparedStatement statement) throws SQLException {

        statement.setString(1,name);

        statement.setString(2, sex);

       

    }

 

    @Override

    public void readFields(ResultSet resultSet) throws SQLException {

        name=resultSet.getString(1);

        sex=resultSet.getString(2);

    }

 

    @Override

    public void write(DataOutput out) throws IOException {

        out.writeUTF(name);

        out.writeUTF(sex);

    }

 

    @Override

    public void readFields(DataInput in) throws IOException {

        this.name=in.readUTF();

        this.sex=in.readUTF();

       

    }

 

    @Override

    public String toString() {

        return "name=" + name + "\t"+"sex=" + sex;

    }

 

}

 

接着就是配置

    public static void main(String[] args) throws Exception {

        Configuration conf=new Configuration();

        //配置数据库信息

        DBConfiguration.configureDB(conf, "com.mysql.jdbc.Driver", "jdbc:mysql://localhost/crxy", "root","2251231");

       

        Job job=Job.getInstance(conf);

        //设定输入格式类

        job.setInputFormatClass(DBInputFormat.class);

        //设置查询语句,与你自定义的类挂钩

        DBInputFormat.setInput(job, LogWriteable.class,"select name,sex from person" , "select count(1) from person");

        job.setJarByClass(DBInputFormatTest.class);

        job.setOutputKeyClass(LogWriteable.class);

        job.setOutputValueClass(NullWritable.class);

        job.setMapperClass(DBMapper.class);

        job.setNumReduceTasks(0);

     

        FileOutputFormat.setOutputPath(job, new Path("/out"));

        job.waitForCompletion(true);

}

 

注意:默认的分片是2,配置的话可以设置配置文件。

 

 

   CombineFileInputFormat

用来处理大量小文件的。比如你的输入数据是两个文件,我们知道默认会产生两个分片,也就是两个map任务。或许你觉得这没有什么,但是如果是大量的小文件,那么就会多很多map任务,从而消耗大量资源。所以我们可以使用这个输入格式类来整合成一个split,从而减少map任务数。这是有可能会有这样的疑惑,如果我这么多小文件分散在不同的datanode上,若整合成一个split,一个map任务来运行,岂不是要把很多小文件通过网络传输过去,那这样使用该格式有没有问题。我们知道小文件网络传输速度很快的,相比于会产生大量的map任务来说,这是值得使用的。

public static void main(String[] args) throws Exception {

        Configuration conf=new Configuration();

        Job job=Job.getInstance(conf);

        //

        job.setInputFormatClass(CombineTextInputFormat.class);

         

        job.setJarByClass(CombineTextInputFormatTest.class);

        job.setOutputKeyClass(Text.class);

        job.setOutputValueClass(LongWritable.class);

        job.setMapperClass(CMapper.class);

        job.setReducerClass(CReduce.class);

        FileInputFormat.setInputPaths(job, new Path("/ceshishuju"));

        FileOutputFormat.setOutputPath(job, new Path("/out"));

        job.waitForCompletion(true);

}

注意这里设置的是,combinetextinputformat,而不是CombineFileInputFormat

(这是抽象类),text是子类,所以用它。

MultipleInputs

多个输入源的情况,如下,分别有从数据库输入和从文件输入

中间包括了计数器

 public class MultipleInputFormatTest {

    public static class DBMapper  extends  Mapper<LongWritable, LogWriteable, Text, Text>{

     

        @Override

        protected void map(LongWritable k1, LogWriteable v1,Context context)

                throws IOException, InterruptedException {

             context.write(new Text(v1.getId()+""), new Text(v1.getName()));

             //计数器

             Counter counter=context.getCounter("Tongji", "DB");

             counter.increment(1L);

        }

    }

    public static class TextMapper extends Mapper<LongWritable, Text, Text,Text >{

        @Override

        protected void map(LongWritable k1, Text v1,Context context)

                throws IOException, InterruptedException {

            String line=v1.toString();

            String temp=line.substring(0, line.indexOf("\t"));

            context.write(new Text(temp), new Text(line.substring(line.indexOf("\t")+1)));

            //计数器

            Counter counter=context.getCounter("Tongji", "Text");

             counter.increment(1L);

        }

    }

    public static class MReduce extends Reducer<Text, Text, Text, Text>{

        @Override

        protected void reduce(Text k2, Iterable<Text> v2s, Context context)

                throws IOException, InterruptedException {

            String point="";

            String name="";

            for (Text text : v2s) {

                String temp=text.toString();

                if(temp.contains("\t")){

                    point=temp;

                }else{

                    name=temp;

                }

            }

            context.write(new Text(name), new Text(point));

        }

    }

    public static void main(String[] args) throws Exception {

        Configuration conf=new Configuration();

        //配置数据库信息

        DBConfiguration.configureDB(conf, "com.mysql.jdbc.Driver", "jdbc:mysql://localhost/crxy", "root","2251231");

       

        Job job=Job.getInstance(conf);

         

        //设置查询语句,与你自定义的类挂钩

        DBInputFormat.setInput(job, LogWriteable.class,"select id,name,sex from person" , "select count(1) from person");

        job.setJarByClass(MultipleInputFormatTest.class);

       

        job.setOutputKeyClass(Text.class);

        job.setOutputValueClass(Text.class);

        job.setReducerClass(MReduce.class);

        //这个Path路径是设置输入路劲,DBInputFormat输入路劲本是没有的。但你不写会报错,而且如果你路劲指定了一个存在的路劲,他就不会执行。。。。

        MultipleInputs.addInputPath(job, new Path("hdfs://115.28.138.100:9000"), DBInputFormat.class, DBMapper.class);

        MultipleInputs.addInputPath(job, new Path("hdfs://115.28.138.100:9000/ceshishuju2"), TextInputFormat.class, TextMapper.class);

        FileOutputFormat.setOutputPath(job, new Path("/out"));

        job.waitForCompletion(true);

    }

 

}

  

3.计数器

MR的计数器设置是让开发人员更好的统计运行中的一些指标,从而及时的方便的发现错误。有些内置计数器,比如你运行mr程序过后控制台打印的map输入记录数,输出记录数,map任务数量等等。自定义计数器代码在上面MulitpltInputs代码里面有。

 

 

 

4.分区

MR中支持分区,分区的作用就是让map出来的数据均匀或者有目的分布到不同的reduce中。一个分区对应一个reduce任务。

HashPartitioner是mapreduce的默认partitioner,他的分区索引关键代码是(key.hashCode() & Integer.MAX_VALUE) % numReduceTasks,是由你设置的reducetask数量决定的。分区代码如下

 //自定义partion

    public static class MyPartion extends Partitioner<Text, Liuliang>{

         @Override

        public int getPartition(Text key, Liuliang value, int numPartitions) {

             String number =key.toString();

             if(number.length()==11){

                 return 1;

             }else{

                 return 0;

             }

        }

}(注意,这里的keyvaluek2v2

public static void main(String[] args) throws Exception {

        Configuration conf=new Configuration();

        Job job=Job.getInstance(conf);

        job.setJarByClass(PartionTest.class);

        job.setOutputKeyClass(Text.class);

        job.setOutputValueClass(Liuliang.class);

        job.setMapperClass(SLMapper.class);

        job.setReducerClass(SLReduce.class);

        //设置pation

        job.setPartitionerClass(MyPartion.class);

         

        //设置reducetask数量

        job.setNumReduceTasks(2);

        FileInputFormat.setInputPaths(job, new Path("/in"));

        FileOutputFormat.setOutputPath(job, new Path("/out"));

        job.waitForCompletion(true);

 

}(注意,如果你分区类返回的是多种索引,但如果你设置的NumReduceTask小于这个值,以NumReduceTask为准,hadoop2.X中会合并,而hadoop1.X会报错)。

 

 

5.排序

MR流程中的排序和分组都是按照key决定的。

mapreduce阶段进行排序时,比较的是k2v2是不参与排序比较的。如果要想让v2也进行排序,需要把k2v2组装成新的类,作为k2,才能参与比较。

分组时也是按照k2进行比较的。要注意一点,如果你的reducetask数量为0,则不会排序。

  public class ShuZi implements WritableComparable<ShuZi>{

    private int first;

    private int second;

    @Override

    public void write(DataOutput out) throws IOException {

        out.writeInt(first);

        out.writeInt(second);

    }

    @Override

    public void readFields(DataInput in) throws IOException {

         first=in.readInt();

         second=in.readInt();

    }

    @Override

    public int compareTo(ShuZi o) {

         if(first==o.getFirst()){

             return second-o.getSecond();

         }

         else{

             return first-o.getFirst();

         }

    }

    public int getFirst() {

        return first;

    }

    public void setFirst(int first) {

        this.first = first;

    }

    public int getSecond() {

        return second;

    }

    public void setSecond(int second) {

        this.second = second;

    }

    @Override

    public String toString() {

        return "ShuZi [first=" + first + ", second=" + second + "]";

    }

   

   

}(注意,自定义类须实现WritableComparable接口。还有要记住,no reduce no sort

你可能感兴趣的:(mapreduce)