Hadoop实践(四)---MR作业配置

1、向作业传递定制的参数

Hadoop自身使用一个配置对象类存储所有作业的配置属性,也可以使用这个对象将参数传递到Mapper和Reducer

  1. MR的driver类通过属性来配置JobConf对象,这些属性包括输入格式,输出格式,Mapper类等。如果要引入自定义的属性,需要在这个配置对象中给属性一个唯一的名称并设置它的值。
  2. 这个配置对象会被传递到所有的TaskTracker(AM),然后作业中的所有任务就能够看到配置对象中的属性
  3. Mapper和Reducer就可以通过读取该配置对象并获得它的属性值

Configuration类(JonConf的父类)有许多通用的setter方法,属性采用键值对的形式,键必须是string,而值可以是常用类型的任意一个。

常用的setter方法:

  public void set(String name,String value)
  public void setBoolean(String name, boolean value)
  public void setInt(String name, int value)
  public void setLong(String name, long value)
  public void setStrings(String name, String... values)

常用的getter方法:

 public String get(String name)
 public String get(String name, String defaultValue)
 public boolean getBoolean(String name, boolean defaultValue)
 public float getFloat(String name, float defaultValue)
 public int getInt(String name, int defaultValue)
 public long getLong(String name, long defaultValue)
 public String getBoolean(String name, String... defaultValue)

Tips:在Hadoop内部,所有的属性都存为字符串

  1. driver类首选设置配置对象的属性,让它们在所有的任务中可见
  2. Mapper和Reducer可以访问Configure()方法中的配置对象
  3. 任务初始化时会调用configure(),它已经被重写为可以提取和存储设置的属性
  4. map和reduce方法访问这些属性的副本
package MR;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.*;

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

/**
 * Created by Promacanthus on 2017/6/29.
 */
public class tample {
    public int run(String[] args)throws Exception{
        Configuration conf = new Configuration();
        JobConf job = new JobConf(conf);
        job.setInt("myjob.property",Integer.parseInt(args[2]));
        JobClient.runJob(job);
        return 0;
    }
    public static class MapClass extends MapReduceBase implements Mapper<Text,Text,Text,Text>{
        int myproperty;
        public void configure(JobConf job){
            myproperty = job.getInt("myjob.property",0);
        }
        public void map(Text key, Text value, OutputCollector output, Reporter reporter) throws IOException {

        }
    }
    public static class Reduc extends MapReduceBase implements Reducer<Text,Text,Text,Text>{
        int myproperty;
        public void configure(JobConf job){
            myproperty = job.getInt("myjob.property",0);
        }
        public void reduce(Text key, Iterator values, OutputCollector output, Reporter reporter) throws IOException {

        }
    }
}

当允许用户设定特定属性时,在driver中最好针对用户的输入进行验证

2、查看任务特定信息

除了获取自定义属性和全局配置外,可以使用配置对象上的getter方法获得当前任务和作业状态的一些信息

例如,通过map.input.file属性来得到当前mao任务的文件路径

在配置对象中可获得的任务特定状态信息:

属性 类型 描述
maperd.job.id String 作业ID
mapred.jar String 作业目录中jar的位置
job.local.dir String 作业的本地空间
mapred.tip.id String 任务ID
mapred.task.id String 任务重试ID
mapred.task.is.map boolean 标志量,表示是否为一个map任务
mapred.task.partition int 作业内部的任务ID
map.input.file String Mapper读取的文件路径
map.input.start long 当前Mapper输入分片的文件偏移量
map.input.length long 当前Mapper输入分片中的字节数
mapred.work.output.dir String 任务的工作(即临时)输出目录

3、划分为多个输出文件

默认情况下,所有的MR作业都输出一组文件。然而在有些场景下,输出多组文件或把一个数据集分为多个数据集更为方便

3-1、MultipleOutputFormat

MultipleOutputFormat提供了一个简单的方法,将相似的记录结组为不同的数据集,在写每条输出记录之前,这个outputFormat类调用一个内部方法来确定要写入的文件名

更具体的说,扩展MultipleOutputFormat的某个子类,并实现generateFileNameKeyValue()方法,扩展的子类将决定输出的格式。

输出格式
MultipleTextOutputFormat 输出文本文件
MultipleSequenceFileOutputFormat 输出序列文件

不论扩展了哪一个子类,都需要重写下面的方法以返回每个输出键值对的文件名:

  protected String generateFileNameForKeyValue(K key,V value, String name)

默认实现返回参数name,即文件名

示例代码如下:

package HDFS;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.*;
import org.apache.hadoop.mapred.lib.MultipleTextOutputFormat;
import org.apache.hadoop.util.Tool;

import java.io.IOException;

/**
 * Created by Promacanthus on 2017/6/29.
 */
public class MultiFile extends Configured implements Tool {
    public static class MapClass extends MapReduceBase implements Mapper<LongWritable, Text, NullWritable, Text> {

        public void map(LongWritable key, Text value, OutputCollector output, Reporter reporter) throws IOException {
            output.collect(NullWritable.get(), value);
        }
    }

    public static class PartitionByCountryMTOF extends MultipleTextOutputFormat<NullWritable, Text> {
        protected String generateFielNameForKeyValue(NullWritable key, Text value, String filename) {
            String[] arr = value.toString().split(",", -1);
            String country = arr[4].substring(1, 3);
            return country + "/" + filename;
        }
    }

    public int run(String[] args) throws Exception {
        Configuration conf = getConf();
        JobConf job = new JobConf();

        Path in = new Path(args[0]);
        Path out = new Path(args[1]);
        FileInputFormat.setInputPathFilter(job,in);
        FileOutputFormat.setOutputPath(job,out);

        job.setJobName("MultiFile");
        job.setMapperClass(MapClass.class);

        job.setInputFormat(TextInputFormat.class);
        job.setOutputFormat(PartitionByCountryMTOF.class);
        job.setOutputKeyClass(NullWritable.class);
        job.setOutputValueClass(Text.class);

        job.setNumReduceTasks(0);

        JobClient.runJob(job);

        return 0;
    }
}

    public static void main(String[] args) throws Exception {
        int res = ToolRunner.run(new Configuration(),new MultiFile(),args);
        System.exit(res);
    }

在输入出目录中可以看到每个国家都有一个单独的目录

3-2、MultipleOutputs

MultipleOutputs不要求给每条记录请求文件名,而是创建多个OutputCollector,每个OutputCollector可以有自己的OutputFormat和键值对类型,MR程序决定如何向每个OutputCollector输出数据

示例代码如下:

package HDFS;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.*;
import org.apache.hadoop.mapred.lib.MultipleOutputs;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

import java.io.IOException;

/**
 * Created by Promacanthus on 2017/6/29.
 */
public class NewMultipleFile extends Configured implements Tool {
    public static class MapClass extends MapReduceBase implements Mapper {
        private MultipleOutputs mos;
        private OutputCollector collector;

        public void configure(JobConf conf) {
            mos = new MultipleOutputs(conf);
        }

        public void map(LongWritable key, Text value, OutputCollector output, Reporter reporter) throws IOException {
            String[] arr = value.toString().split(",", -1);
            String chrono = arr[0] + "," + arr[1] + "." + arr[2];
            String geo = arr[0] + "," + arr[4] + "." + arr[5];

            collector = mos.getCollector("chrono", reporter);
            collector.collect(NullWritable.get(), new Text(chrono));
            collector = mos.getCollector("geo", reporter);
            collector.collect(NullWritable.get(), new Text());
        }

        public void close() throws IOException {
            mos.close();
        }
    }

    public int run(String[] args) throws Exception {
        Configuration conf = getConf();
        JobConf job = new JobConf(conf,MultiFile.class);

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

        job.setJobName("MultiFile");
        job.setMapperClass(MultiFile.MapClass.class);

        job.setInputFormat(TextInputFormat.class);
        job.setOutputFormat(MultiFile.PartitionByCountryMTOF.class);
        job.setOutputKeyClass(NullWritable.class);
        job.setOutputValueClass(Text.class);

        job.setNumReduceTasks(0);

        MultipleOutputs.addNamedOutput(job,"chrono",TextOutputFormat.class,NullWritable.class,Text.class);
        MultipleOutputs.addNamedOutput(job,"gep",TextOutputFormat.class,NullWritable.class,Text.class);

        JobClient.runJob(job);

        return 0;
    }

    public static void main(String[] args) throws Exception {
        int res = ToolRunner.run(new Configuration(),new MultiFile(),args);
        System.exit(res);
    }
}

若要使用MultipleOutputs,MR程序的driver类必须设置它想要使用的输出收集器

在输出目录中有一组以chrono为前缀的文件,另一组以geo为前缀的文件

4、以数据库作为输入输出

  • 建立一个MR程序通过直接查询数据库来去的输入数据,而不是从HDFS中读取文件,但这样性能不好
  • 或者将数据集从数据库复制到HDFS,使用标准的数据库管理工具dump或者HDFS的shell命令put

DBOutputFormat是访问数据库的关键类,在driver类中将输入输出格式设置为这个类,指定配置,便能够连接到该数据库了。具体配置通过DBConfiguration中的静态方法configureDB():

 public static void configureDB(JobConf job, String driverClass, String dbUrl, String userName, String passwd)

然后指定将要写入的表,以及哪些字段,使用DBOutputFormat中的今天方法setOutput():

 public static void setOutput(JobConf job, String tableName, String... fieldNames)

在driver类中添加如下代码:

 job.setOutputFormat(DBOutputFormat.class);
        DBConfiguration.configureDB(job,"com.mysql.jdbc.Driver","jdbc:mysql://db.host.com/mydb","username","password");

使用DBOutputFormat将强制输出的键实现DBWritable接口

Tips:从Hadoop内部读写数据库仅适用于凭借Hadoop标准来说相对较小的数据集,除非数据库和Hadoop并行,否则数据库将成为性能瓶颈

5、保持输出的顺序

MR框架保证每个reducer的输入都按键来排序,在许多情况下,reducer只是对键值对中值的部分做简单的计算,输出仍然保持顺序排序。MR框架并不能保证reducer输出的顺序,它只是已经排序好的输入以及reducer锁执行的操作类型的副产品

如何让reducer的输出时有序的呢,即part-r-00000中所有记录小于part-r-00001,以此类推?关键在于Partitioner操作

  1. Partitioner的任务是确定地为每个键分配一个reducer。相同键的所有记录都结成组并在reducer阶段被集中处理
  2. Partitioner的一个重要设计需求是在reducer之间达到负载平衡(没有一个reducer会比其他reducer被分配到更多的键)
  3. 由于没有以前对键的分配信息,Partitioner默认使用散列函数来均匀的分配,分配完全随机,不存在任何顺序
  4. 如果事先知道键是大致均匀分布的,可以使用Partitioner给每个reducer分配一个键的范围

TotalOrderPartitioner是一个可以保证在输入分区之间,而不仅仅是分区内部排序的Partitioner,这个类利用一个排好序的分区键组读取一个序列文件,并进一步将不同区域的键分配到reducer上

你可能感兴趣的:(Hadoop)