【Hadoop源码研究】之Configuration

来源:互联网 发布:李世民 知乎 胡人 编辑:程序博客网 时间:2024/04/27 18:38
作者:周邦涛(Timen)
Email:zhoubangtao@gmail.com
转载请注明出处:  http://blog.csdn.net/zhoubangtao/article/details/25977561

1. 介绍

        Congfiguration类位于hadoop-common工程下的org.apache.hadoop.conf包下,它是Hadoop系统的配置文件类,如core-default.xml, hdfs-site.xml都是有本类进行读取和配置的,它保存了整个Hadoop运行环境的上下文。研究本类对于了解Hadoop读取配置文件的方式,运行时动态改变配置很有帮助!

        特别注意:本文基于Hadoop 2.4版本进行研究

2. 详解

2.1 Configuartion概述

        Configuration类采用懒加载模式(Lazy-Load)加载配置文件,内部由一个Properties对象properties存储所有的配置信息,并由一个Set对象finalParameters存储所有的final属性名,当调用Congfiguration.getXXX()时,会检测properties成员变量是否为空,如果为空则发起加载动作,否则直接获取属性的值。此外Configuration类实现了Iterator接口,也就是说你可以直接通过foreach进行遍历。

        Configuration配置文件中的property标签包含一下几种子标签:

  • name:属性名
  • value:属性值
  • final:是否可覆盖
  • source:属性的来源(记录属性来自那个Resource,这里存的就是Resource的name属性)
  • description:属性的描述信息,这个值不在Configuration中存储,仅用于用户阅读,除此之外其他属性都会存储下来

2.1 Configuartion主要方法和内部类

        1. Resource内部类

         Resource内部类代表一个配置资源(如:core-site.xml),有一个资源名称和资源对象(可以是String,URL,Path,InputStream等)构成,Configuration类有两种Resource:一种为默认配置资源(如:core-default.xml,core-site.xml等,由CopyOnWriteArrayList静态对象defaultResources存储),一种为用户自定义配置资源(由有一个ArrayList对象resources存储)

  /**   * List of default Resources. Resources are loaded in the order of the list    * entries   */  private static final CopyOnWriteArrayList<String> defaultResources = new CopyOnWriteArrayList<String>();  /**   * List of configuration resources.   */  private ArrayList<Resource> resources = new ArrayList<Resource>();

        2. reloadConfiguration方法

        该方法并不会真正触发加载动作,只是把properties置空,源码如下:

  public synchronized void reloadConfiguration() {    properties = null;                            // trigger reload    finalParameters.clear();                      // clear site-limits  }

        3. addResourceObject

        该方法是添加用户配置资源的私有方法,它是通过一系列addResource重载方法向外提供服务,addResource根据资源对象的类型(String,URL,Path,InputStream等)定义了若干种重载方法,源码如下:

  public void addResource(Configuration conf) {    addResourceObject(new Resource(conf.getProps()));  }    private synchronized void addResourceObject(Resource resource) {    resources.add(resource);                      // add to resources    reloadConfiguration(); // 每添加一次资源都会置空一次properties  }
        4. addDefaultResource

        该方法是添加默认配置资源的public static方法,一般在静态代码块调用,代码如下:

  /**   * Add a default resource. Resources are loaded in the order of the resources    * added.   * @param name file name. File should be present in the classpath.   */  public static synchronized void addDefaultResource(String name) {    if(!defaultResources.contains(name)) {      defaultResources.add(name);      for(Configuration conf : REGISTRY.keySet()) {        if(conf.loadDefaults) {          conf.reloadConfiguration();        }      }    }  }
        5. substituteVars私有方法

        该方法是使用系统变量替换某一配置value中的${var}变量,如${user.name}会被替换成系统用户名

        6. getProps方法

        该方法是触发加载的最终方法

  protected synchronized Properties getProps() {    if (properties == null) {//如果properties为null,则加载配置      properties = new Properties();      HashMap<String, String[]> backup =         new HashMap<String, String[]>(updatingResource);      loadResources(properties, resources, quietmode);      if (overlay!= null) {        properties.putAll(overlay);        for (Map.Entry<Object,Object> item: overlay.entrySet()) {          String key = (String)item.getKey();          updatingResource.put(key, backup.get(key));        }      }    }    return properties;  }

        7. get方法

        该方法是Configuration类中最重要的取值方法,这里

  public String get(String name) {    String[] names = handleDeprecation(deprecationContext.get(), name);    String result = null;    for(String n : names) {      result = substituteVars(getProps().getProperty(n));//这里调用了getProps,并且用substituteVars替换了变量    }    return result;  }

        8. loadResource和loadResources私有方法

        这两个方法都是私有方法,也就是说Configuration类一定是懒加载的,只有在get属性的时候才会加载配置,loadResource是加载单个Resource,由loadResources调用实现多个Resource加载

        loadResource方法比较复杂,主要实现资源对象类型检测,并最终转化为xml的root元素进行解析(除资源类型为Properties外,它会直接覆盖目标propertis中然后方法推出,所以这里可能将final的值也给覆盖了,不过不用担心,在对外暴露的众多addResource重载方法中,并没有一个是添加Properties这种类型资源的,这个只是Configuration内部将InputStream这种资源类型转换成了Properties资源类型后使用的),

  private Resource loadResource(Properties properties, Resource wrapper, boolean quiet) {    String name = UNKNOWN_RESOURCE;    try {      Object resource = wrapper.getResource();      name = wrapper.getName();            //构建DocumentBuilder 略      Document doc = null;      Element root = null;      boolean returnCachedProperties = false;//是否转化为Properties资源类型            if (resource instanceof URL) {                  // an URL resource        doc = parse(builder, (URL)resource);      } else if (resource instanceof String) {        // a CLASSPATH resource        URL url = getResource((String)resource);        doc = parse(builder, url);      } else if (resource instanceof Path) {          // a file resource        // Can't use FileSystem API or we get an infinite loop        // since FileSystem uses Configuration API.  Use java.io.File instead.        File file = new File(((Path)resource).toUri().getPath())          .getAbsoluteFile();        if (file.exists()) {          if (!quiet) {            LOG.debug("parsing File " + file);          }          doc = parse(builder, new BufferedInputStream(              new FileInputStream(file)), ((Path)resource).toString());        }      } else if (resource instanceof InputStream) {        doc = parse(builder, (InputStream) resource, null);        returnCachedProperties = true;//将InputStream类型转化为Properties类型      } else if (resource instanceof Properties) {        overlay(properties, (Properties)resource); // 若为Properties类型,则直接覆盖,这里会导致root和doc都为null,后边就会退出      } else if (resource instanceof Element) {        root = (Element)resource;      }      if (root == null) {        if (doc == null) {          if (quiet) {            return null;          }          throw new RuntimeException(resource + " not found");        }        root = doc.getDocumentElement();      }      Properties toAddTo = properties;      if(returnCachedProperties) {        toAddTo = new Properties();//看到了吧,转化为Properties了      }      if (!"configuration".equals(root.getTagName()))        LOG.fatal("bad conf file: top-level element not <configuration>");      NodeList props = root.getChildNodes();      DeprecationContext deprecations = deprecationContext.get();      for (int i = 0; i < props.getLength(); i++) {        Node propNode = props.item(i);        if (!(propNode instanceof Element))          continue;        Element prop = (Element)propNode;        if ("configuration".equals(prop.getTagName())) {          loadResource(toAddTo, new Resource(prop, name), quiet);          continue;        }        if (!"property".equals(prop.getTagName()))          LOG.warn("bad conf file: element not <property>");        NodeList fields = prop.getChildNodes();        String attr = null;        String value = null;        boolean finalParameter = false;        LinkedList<String> source = new LinkedList<String>();        for (int j = 0; j < fields.getLength(); j++) {          Node fieldNode = fields.item(j);          if (!(fieldNode instanceof Element))            continue;          Element field = (Element)fieldNode;          if ("name".equals(field.getTagName()) && field.hasChildNodes())            attr = StringInterner.weakIntern(                ((Text)field.getFirstChild()).getData().trim());          if ("value".equals(field.getTagName()) && field.hasChildNodes())            value = StringInterner.weakIntern(                ((Text)field.getFirstChild()).getData());          if ("final".equals(field.getTagName()) && field.hasChildNodes())            finalParameter = "true".equals(((Text)field.getFirstChild()).getData());          if ("source".equals(field.getTagName()) && field.hasChildNodes())            source.add(StringInterner.weakIntern(                ((Text)field.getFirstChild()).getData()));//把property属性中的source属性加入source中        }        source.add(name);//这里又加了一个资源的名字,看函数开始部分                // Ignore this parameter if it has already been marked as 'final'        if (attr != null) {          if (deprecations.getDeprecatedKeyMap().containsKey(attr)) {            DeprecatedKeyInfo keyInfo =                deprecations.getDeprecatedKeyMap().get(attr);            keyInfo.clearAccessed();            for (String key:keyInfo.newKeys) {              // update new keys with deprecated key's value               loadProperty(toAddTo, name, key, value, finalParameter,                   source.toArray(new String[source.size()]));            }          }          else {            loadProperty(toAddTo, name, attr, value, finalParameter,                 source.toArray(new String[source.size()]));//调用函数加载一个具体的property          }        }      }            if (returnCachedProperties) {        overlay(properties, toAddTo);        return new Resource(toAddTo, name);      }      return null;    } catch (IOException e) {      LOG.fatal("error parsing conf " + name, e);      throw new RuntimeException(e);    } catch (DOMException e) {      LOG.fatal("error parsing conf " + name, e);      throw new RuntimeException(e);    } catch (SAXException e) {      LOG.fatal("error parsing conf " + name, e);      throw new RuntimeException(e);    } catch (ParserConfigurationException e) {      LOG.fatal("error parsing conf " + name , e);      throw new RuntimeException(e);    }  }
        loadResources方法根据资源列表依次加载,并将loadResource返回的Resource发到资源列表的对应位置

  private void loadResources(Properties properties,                             ArrayList<Resource> resources,                             boolean quiet) {    if(loadDefaults) {      for (String resource : defaultResources) {//加载默认资源        loadResource(properties, new Resource(resource), quiet);      }          //support the hadoop-site.xml as a deprecated case      if(getResource("hadoop-site.xml")!=null) {        loadResource(properties, new Resource("hadoop-site.xml"), quiet);      }    }        for (int i = 0; i < resources.size(); i++) {//加载用户自定义资源      Resource ret = loadResource(properties, resources.get(i), quiet);      if (ret != null) {        resources.set(i, ret);//资源列表被改变了,原来的InputStream资源类型变成了Properties类型      }    }  }

        9. loadProperty私有方法

        用来添加一个property到指定的Properties中,并标记该property的来源到updatingResource中,如果是final的,还会将其属性名加到finalParameters中,源码如下:

  private void loadProperty(Properties properties, String name, String attr,      String value, boolean finalParameter, String[] source) {    if (value != null) {      if (!finalParameters.contains(attr)) {        properties.setProperty(attr, value);        updatingResource.put(attr, source);      } else if (!value.equals(properties.getProperty(attr))) {//如果为final的,则不允许覆盖        LOG.warn(name+":an attempt to override final parameter: "+attr            +";  Ignoring.");      }    }    if (finalParameter) {      finalParameters.add(attr);    }  }

        参数说明:

        properties:存放property

        name:属性来源的Resource的name属性

        attr:属性名

        value:属性值

        finalParameter:是否为final

        source:属性的所有来源(包括该属性中配置的source值和属性所在的Resource的name属性)

        10. 其他的get和set方法就不用说了,很简单无非是类型转换而已

2.3 Configuartion家族

        1.位于org.apache.hadoop.conf下的Configuration类加载了默认配置文件core-default.xml和core-site.xml(hadoop-site.xml已经废弃),并没有加载HDFS的配置文件,也就是说,如果你new了Configuration则只能获取core的配置,那么使用HDFS API(如FileSystem)编程时怎么办呢?

         实际上,在Hadoop 2.4中各个组件(Yarn,MR,HDFS等)都继承了Configuration从而加载了自己的配置文件,子类如下:

         HdfsConfiguration:加载hdfs-default.xml和hdfs-site.xml

         YarnConfiguration:加载yarn-default.xml和yarn-site.xml

         JobConf:加载yarn-default.xml,yarn-site.xml,mapred-default.xml和mapred-site.xml

         另外还有FairSchedulerConfiguration和CapacitySchedulerConfiguration

         所以一定要注意了,如果使用HDFS的FileSystem应该像这样:

    Configuration conf = new HdfsConfiguration();//new 一个HdfsConfiguration        FileSystem fs = null;    try {      fs = FileSystem.get(URI.create("hdfs://ns1:8009"), conf);      fs.listFiles(new Path("/"), false);    } catch (IOException e) {      e.printStackTrace();    }

3. 总结

         本文阐述了Configuration的原理和一些主要方法的实现,最后指使用方法。

4. 参考资料

  1. http://hadoop.apache.org/
作者:周邦涛(Timen)
Email:zhoubangtao@gmail.com
转载请注明出处:  http://blog.csdn.net/zhoubangtao/article/details/25977561
0 0