hadoop中Configuration类剖析

Configuration是hadoop中五大组件的公用类,所以放在了core下,org.apache.hadoop.conf.Configruration。这个类是作业的配置信息类,任何作用的配置信息必须通过Configuration传递,因为通过Configuration可以实现在多个mapper和多个reducer任务之间共享信息。

 类图

                                                     

说明:Configuration实现了Iterable和Writable两个接口,其中实现Iterable是为了迭代,迭代出Configuration对象加载到内存中的所有name-value键值对。实现Writable是为了实现hadoop框架要求的序列化,可以将内存中的name-value序列化到硬盘,关于这两个接口的具体实现的话,我想不用再多说了,应该想的明白。
 
下面来详细的分析下Configuration的工作原理,包含配置文件的加载,获取配置信息和加载配置信息的原理,以及在使用过程中应该注意的事项。
研究任何一个类首先从构造函数开始,就算是使用的单例,静态工厂得到对象也同样离不开Constructor。
Configuration有三个构造函数
public Configuration() {
    this(true);
  }
/** A new configuration where the behavior of reading from the default 
   * resources can be turned off.
   * 
   * If the parameter {@code loadDefaults} is false, the new instance
   * will not load resources from the default files. 
   * @param loadDefaults specifies whether to load from the default files
   */
  public Configuration(boolean loadDefaults) {
    this.loadDefaults = loadDefaults;
    updatingResource = new HashMap<String, String>();
    synchronized(Configuration.class) {
      REGISTRY.put(this, null);
    }
  }
/** 
   * A new configuration with the same settings cloned from another.
   * 
   * @param other the configuration from which to clone settings.
   */
  @SuppressWarnings("unchecked")
  public Configuration(Configuration other) {
    this.resources = (ArrayList) other.resources.clone();
    synchronized (other) {
      if (other.properties != null) {
        this.properties = (Properties) other.properties.clone();
      }
      if (other.overlay != null) {
        this.overlay = (Properties) other.overlay.clone();
      }
      this.updatingResource = new HashMap<String, String>(
          other.updatingResource);
    }
    this.finalParameters = new HashSet<String>(other.finalParameters);
    synchronized (Configuration.class) {
      REGISTRY.put(this, null);
    }
  }
 
1,Configuration()
2,Configuration(boolean loadDefaults)
3, Configuration(Configuraiont other)
    前两个Constructor使用的是典型的重叠构造器模式,也就是默认的无参Constructor会生成一个加载了默认配置文件得Configuration对象,其中Configuration(boolean loadDefaults)中的参数就是为了控制构造出来的对象是加载了默认配置文件还是没有的标识。但是如果要我来设计我不会搞得这么麻烦,直接使用两个静态工厂方法来标识不同性质的对象——getConfigruationWithDefault()和getConfiguration,这样的话开发人员在使用是就可以望文生义,不是很好的方式么?不扯这个了。当loadDefaults为false时,Configuration对象就不会将通过addDefaultResource(String resource)加载的配置文件载入内存。但是会将通过addResource(...)加载的配置文件载入内存。具体是怎么实现的呢?
    在Configuration这个Constructor中的this.loadDefaults = loadDefaults就是设置是否加载默认配置文件的flag,我们顺蔓摸瓜,构造了Configuration对象后,接下来会调用getType(String name,Type default)方法得到某个name对应的value值。以getInt为例,看看getInt()的代码
 
getInt(String name,int defalutVale)
public int getInt(String name, int defaultValue) {
    String valueString = get(name);
    if (valueString == null)
      return defaultValue;
    try {
      String hexString = getHexDigits(valueString);
      if (hexString != null) {
        return Integer.parseInt(hexString, 16);
      }
      return Integer.parseInt(valueString);
    } catch (NumberFormatException e) {
      return defaultValue;
    }
  }
方法的第一行代码String valueString = get(name);是关键,所以再来看看get(String name)这个方法
 
get(String name)
private synchronized Properties getProps() {
    if (properties == null) {
      properties = new Properties();
      loadResources(properties, resources, quietmode);
      if (overlay!= null) {
        properties.putAll(overlay);
        for (Map.Entry<Object,Object> item: overlay.entrySet()) {
          updatingResource.put((String) item.getKey(), UNKNOWN_RESOURCE);
        }
      }
    }
    return properties;
  }
这里就有话可讲了,由getInt --> get --> getProps的路径是任何一次getType方法调用要走的路径,但是在getProps()这个地方就要分道扬镳了,第一次用getType方法在判断了properties == null后会执行loadResources(properties,resources,quietmode)方法。但是在properties不为null的情况下就不会执行后续代码。下面走进loadResources(properties,resources,quietmode)方法一探究竟
 
loadResources(properties,resources,quietmode)
private void loadResources(Properties properties,
                             ArrayList resources,
                             boolean quiet) {
    if(loadDefaults) {
      for (String resource : defaultResources) {
        loadResource(properties, resource, quiet);
      }
    
      //support the hadoop-site.xml as a deprecated case
      if(getResource("hadoop-site.xml")!=null) {
        loadResource(properties, "hadoop-site.xml", quiet);
      }
    }
    
    for (Object resource : resources) {
      loadResource(properties, resource, quiet);
    }
  }

看到了loadDefaults了没有?是不是很开心,在Constructor涉及到的控制默认配置文件加载的loadDefaults终于现身了。defaultResource在loadDefaults为true是才会加载。但是resources中存放的配置文件无论怎么样都会被加载,这里出现了两个存放配置文件的容器defaultResources和resource

/**
   * List of configuration resources.
   */
  private ArrayList<Object> resources = new ArrayList<Object>();
/**
   * List of default Resources. Resources are loaded in the order of the list 
   * entries
   */
  private static final CopyOnWriteArrayList<String> defaultResources =
    new CopyOnWriteArrayList<String>();
一个是cofiguration resources的list,一个default Resources的list,那么如何区分是否是default resources呢?别着急,看下面的分析。在Configuration类中有多个加载配置文件的方法 ,addDefaultResource(String name),addResource(String resoruce)及重载方法,addResourceObject(Object resource)。由于addResource(...)系类的方法最终是通过调用addResourceObject来实现的,所以这个就要看addDefaultResource(String name)和addResourceObject(Object resource)的区别了
 
addDefaultResource(String resource)
public static synchronized void addDefaultResource(String name) {
    if(!defaultResources.contains(name)) {
      defaultResources.add(name);
      for(Configuration conf : REGISTRY.keySet()) {
        if(conf.loadDefaults) {
          conf.reloadConfiguration();
        }
      }
    }
  }

addResourceObject(Object object)

private synchronized void addResourceObject(Object resource) {
    resources.add(resource);                      // add to resources
    reloadConfiguration();
  }
看清楚没有?没看清楚多看一下。addDefaultResource(String name)内部通过defaultResources.add(name)将配置文件的name加入了容器defaultResources容器中,addResourceObject(Object resource)通过resources.add(resource)将配置文件加入了resources容器中。所以这就说明了,所以的默认配置文件是通过addDefaultResource(String name)加载的,也就存放在defaultResources这个容器中的,存放在resources中的配置文件就不能当做是默认的配置文件了。
    仔细观察这两个方法的实现,发现reloadConfiguration(),这里面有文章可以做,还是看源码说话吧
 
reloadConfiguration()
/**
   * Reload configuration from previously added resources.
   *
   * This method will clear all the configuration read from the added 
   * resources, and final parameters. This will make the resources to 
   * be read again before accessing the values. Values that are added
   * via set methods will overlay values read from the resources.
   */
  public synchronized void reloadConfiguration() {
    properties = null;                            // trigger reload
    finalParameters.clear();                      // clear site-limits
  }
恩,properties=null,fianlParmeters.clear(),这就将内存中存在的name-value都清空了。所以在使用getType方法后又得重新将配置文件载入内存,所以建议在作业运行的过程中不要使用addDefaultResource(String resource)addResourceObject(Object object),因为这会导致重新加载配置文件到内存。有必要解释下finalParameters这个filed,该feilds也是一个Set容器,主要是用来存储被final修饰的name-value,被fianl修饰后的name-value无法被后续的配置文件覆盖,但是在程序中可以通过set(String name,String value),这里让人不明白,不允许管理员通过配置文件修改的name-value但是可以被用户修改,实则是很奇怪。
    关于第三个构造函数,根据参数和具体实现很容易知道是生成一个和传入的configuration对象一样的configuration对象,不说了这里
    现在关于构造Configuration对象时如何控制是否加载的配置文件原理已经清楚了,同时也弄清楚了getType的原理。下面是调用getType方法的时序图
                                                              
 
  构造器,getType原理应该已经清楚了,现在来看下setType方法,setType(String name ,Type value)方法内部都调用了set(String name,String value)方法,这一点和getType(String name,Type defaultValue)与get(String)的关系是相同的。那么现在来思考一个问题:上面说了,在使用addDefaultResources(...)和addResourceObject(...)方法都会清空内存中的name-value键值对,放在配置文件中的name-value可以重新加载到内存中,也就说这些name-value键值对并不会丢失。但是通过setType()设置的值别没有写到配置文件中,他们是存在内存当中。
public void set(String name, String value) {
    getOverlay().setProperty(name, value);
    getProps().setProperty(name, value);
    this.updatingResource.put(name, UNKNOWN_RESOURCE);
  }
getProps返回的是存放有所有的name-value键值对的Properties对象,使用set(String name,String value)方法设置的name-value仅仅是放在了properties对象的内存空间总并没有写入到文件,这样addDefaultResources(...)和addResourceObject(...)时properties被设置为null后,好不容易通过set(String name,String value)加载进来的name-value岂不是丢弃了?
    注意在set(String name,String value)中还有一个地方是关键getOverlay().setProperty(name, value),其中getOverlay()方法返回的overlay,该对象的引用类型是Properties。矛盾来了,set(String name,String value)方法将name-value加到了两个Properties对象中,这又是干什么?呀,现在可以肯定的是通过set(String name,String value)方法设置的name-value键值对在字段overlay对象和字段properties都有,在回头来看看getProps()方法
private synchronized Properties getProps() {
    if (properties == null) {
      properties = new Properties();
      loadResources(properties, resources, quietmode);
      if (overlay!= null) {
        properties.putAll(overlay);
        for (Map.Entry<Object,Object> item: overlay.entrySet()) {
          updatingResource.put((String) item.getKey(), UNKNOWN_RESOURCE);
        }
      }
    }
    return properties;
  }
在properties为null的条件下,除了会去加载配置文件中value-name,还会探测一下overlay对象是不是为空,不为空就将overlay对象中的name-value加载到properties中,恩,这一点和reloadConfiguration()不矛盾,因为reloadConfiguration()是将properties对象置为 null,并没有将overlay置为空。可以这样说overlay的作用是将用户设置的name-value保存起来作为properties在内存部分的备份,这样properties中由系统和管理员配置的name-value由配置文件备份,而后期用户载入的name-value则有overlay备份到内存中,properties在configuration对象存活期间不会有信息丢失。
    setType和getType方法都可以触发loadResources()方法将name-value加入到properties对象的内存中,但是一旦properties已经存放了配置文件中的name-value键值对,再次调用setType或者是getType方法就不会触发loadResources()的加载动作,除非调用了addDefaultResources(...)和addResourceObject(...)。
 
Summarize:
    1 在作业运行过程中不要使用addDefaultResources(...)和addResourceObject(...)加载资源,因为这会导致properties对象重构一遍,建议此时使用setType(...)
    2 Configuration在整个MapReduce中使用得很频繁,JobTraker,TaskTraker进程在启动的时候都会使用到Configuration对象,HDFS中同样也会使用Configuration对象,所以我认为理解Configuration的基本工作原理很重要。
    3 Configuration可以用来在MapReduce任务之间共享信息,当然这样共享的信息是在作业中配置,一旦作业中的map或者reduce任务启动了,configuration对象就完全独立。所以共享信息是在作业中设置的。
 
原文地址:https://www.cnblogs.com/wolfblogs/p/4156403.html