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方法,把文件分片,至于另外一个方法,由他的不同子类来进行不同实现。输入文件的来源多种多样,这就需要设置不同的输入处理类来分别处理,下面介绍部分输入处理类。
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.txt和b.txt分别有三行数据,则有四个分片。
这样会使分片数增加,而使map任务增加,如果输入数据有特定规律,比如某几行是一套数据,可以使用。
KeyValueTextInputFormat
如果行中有分隔符,那么分隔符前面的作为key,后面的 作为value;如果行中没有分隔符,那么整行作为key,value为空
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;
}
}
}(注意,这里的key和value是k2和v2)
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决定的。
在map和reduce阶段进行排序时,比较的是k2。v2是不参与排序比较的。如果要想让v2也进行排序,需要把k2和v2组装成新的类,作为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)