Hadoop源码分析之Configuration

org.apache.hadoop.conf.Configuration类是Hadoop所有功能的基础类,每一种功能执行之前都需要有先得到一个Configuration对象。Hadoop使用了XML文件作为配置文件,来保存运行时的配置信息,然后将配置加载到Configuration对象中,要使用配置信息时直接从Configuration对象中取。

Hadoop配置文件

将下载的Hadoop压缩包解压后,在文件夹中有一个conf文件夹,这里面有一些Hadoop启动时使用的配置文件,比如配置Hadoop为伪分布式后的core-site.xml文件为:

[html]  view plain copy
  1. <?xml version="1.0"?>  
  2. <?xml-stylesheet type="text/xsl" href="configuration.xsl"?>  
  3.   
  4. <!-- Put site-specific property overrides in this file. -->  
  5.   
  6. <configuration>  
  7.     <property>  
  8.         <name>fs.default.name</name>  
  9.         <value>hdfs://localhost:9000</value>  
  10.     </property>  
  11.     <property>  
  12.         <name>hadoop.tmp.dir</name>  
  13.         <value>/home/gmy/hadoop/tmp</value>  
  14.     </property>  
  15. </configuration>  
如上面的xml代码所示,Hadoop的XML配置文件的根节点是configuration,下一层的节点是property,每个property都代表一个配置项,由键值对组成,name节点表示该配置项的键,value节点表示值,除了name和value节点,property节点另一个重要的子节点是final,它表示这个键值对是不可覆盖的,是固定不变的,和Java中的final关键字类似。property节点还有个description子节点,是对该属性的描述,类似于Java的注释,在程序中不使用。

property的保存

在Configuration类中,有个java.util.Properties类型的成员变量properties,它保存了所有读取到的键值对配置项。调用Configuration.get()方法时,是从properties对象中取值,Configuration.get()的相关代码如下:

[java]  view plain copy
  1. public String get(String name) {  
  2.     return substituteVars(getProps().getProperty(name));  
  3.   }  
  4. private synchronized Properties getProps() {  
  5.     if (properties == null) {  
  6.       properties = new Properties();  
  7.       loadResources(properties, resources, quietmode);  
  8.       if (overlay!= null) {  
  9.         properties.putAll(overlay);  
  10.         for (Map.Entry<Object,Object> item: overlay.entrySet()) {  
  11.           updatingResource.put((String) item.getKey(), UNKNOWN_RESOURCE);  
  12.         }  
  13.       }  
  14.     }  
  15.     return properties;  
  16.  }  

其中get()方法中调用的substituteVars()是进行属性扩展,下面会有这个方法的介绍。

属性扩展

再get()方法中调用方法substituteVars()是对配置的属性扩展,那么什么是属性扩展呢?举例说明如下,如果配置项dfs.name.dir值是${hadoop.tmp.dir}/dfs/name,而配置项hadoop.tmp.dir的值是/data, 那么${hadoop.tmp.dir}会使用hadoop.tmp.dir的值/data进行扩展,扩展后dfs.name.dir的值为/data/dfs/name。在Configuration类中,方法substituteVars()就是用来进行属性扩展的,代码如下:

[java]  view plain copy
  1.   //正则表达式对象,包含正则表达式\$\{[^\}\$\ ]+\},u0020是unicode中标识空格的十六进制  
  2.   private static Pattern varPat = Pattern.compile("\\$\\{[^\\}\\$\u0020]+\\}");  
  3.   //最多做20次扩展  
  4.   private static int MAX_SUBST = 20;  
  5.   /** 
  6.    * 进行属性扩展,可以扩展保存在Configuration对象中的键值对,而且还可以使用Java虚拟机的系统属性, 
  7.    * 在该方法中属性扩展优先使用系统属性 
  8.    * @param expr 
  9.    * @return 
  10.    */  
  11.   private String substituteVars(String expr) {  
  12.     if (expr == null) {  
  13.       return null;  
  14.     }  
  15.     Matcher match = varPat.matcher("");  
  16.     String eval = expr;  
  17.     //循环,做多做MAX_SUBST次属性扩展  
  18.     for(int s=0; s<MAX_SUBST; s++) {  
  19.       match.reset(eval);  
  20.       if (!match.find()) {  
  21.         return eval;//什么都没有找到,返回  
  22.       }  
  23.       String var = match.group();  
  24.       //获得属性扩展的键  
  25.       var = var.substring(2, var.length()-1);  
  26.       String val = null;  
  27.       try {  
  28.           //先查看系统属性中是否有var对应的值,保证优先使用系统属性  
  29.         val = System.getProperty(var);  
  30.       } catch(SecurityException se) {  
  31.         LOG.warn("Unexpected SecurityException in Configuration", se);  
  32.       }  
  33.       //如果系统属性中没有,则查看Configuration保存再键值对中是否有var的键值对  
  34.       if (val == null) {  
  35.         val = getRaw(var);  
  36.       }  
  37.       if (val == null) {  
  38.           //没有找到的,则返回  
  39.         return eval; // return literal ${var}: var is unbound  
  40.       }  
  41.       // 进行替换  
  42.       eval = eval.substring(0, match.start())+val+eval.substring(match.end());  
  43.     }  
  44.       
  45.     throw new IllegalStateException("Variable substitution depth too large: "   
  46.                                     + MAX_SUBST + " " + expr);  
  47.   }  
在Configuration类中,使用正则表达式\$\{[^\}\$\ ]+\}来匹配表达式expr中的${}符号,正则表达式中的\$\{是匹配expr中的前面部分${,\}匹配expr中的后面部分},[^\}\$\ ]表示匹配除了^、}和$这三个字符的所有字符,而表达式中的+表示其前面的[^\}\$\ ]至少出现一次。在substituteVars()方法中先得到一个Matcher对象,然后循环MAX_SUBST次对expr中匹配正则表达式的属性进行值替换(如果存在)。可以看到优先匹配系统属性,其次是Configuration.properties中的键值对。

延迟加载

Configuration类采取了一种延迟加载的方式来加载XML配置文件中的键值对。使用语句

[java]  view plain copy
  1. Configuration conf = new Configuration();  
按照Java初始化顺序,先初始化静态域(static 变量和代码块),再初始化非静态成员变量,最后执行构造方法,那么新建一个Configuration对象conf时,先执行Configuration类中的静态域(static 变量和代码块),再初始化非静态成员变量,最后执行Configuration()这个构造方法,所以新建Configuration对象涉及的代码如下:

[java]  view plain copy
  1. /**用来设置加载配置的模式,如果quitemode为true,则再加载解析配置文件的过程中,不输出日志信息,该变量只是一个方便开发人员调试的变量**/  
  2.   private boolean quietmode = true;  
  3.     
  4.   /** 
  5.    * List of configuration resources.<br/> 
  6.    * 保存了所有通过addRescource()方法添加Configuration对象的资源 
  7.    */  
  8.   private ArrayList<Object> resources = new ArrayList<Object>();  
  9.   
  10.   /** 
  11.    * List of configuration parameters marked <b>final</b>.<br/> 
  12.    * 用于保存再配置文件中已经被声明为final的键值对的键  
  13.    */  
  14.   private Set<String> finalParameters = new HashSet<String>();  
  15.   /**是否加载默认资源,这些默认资源保存在defaultResources中**/  
  16.   private boolean loadDefaults = true;  
  17.     
  18.   /** 
  19.    * Configuration objects<br/> 
  20.    * 记录了系统中所有的Configuration对象, 
  21.    */  
  22.   private static final WeakHashMap<Configuration,Object> REGISTRY =   
  23.     new WeakHashMap<Configuration,Object>();  
  24.     
  25.   /** 
  26.    * List of default Resources. Resources are loaded in the order of the list  
  27.    * entries<br/> 
  28.    * 默认资源,通过方法addDefaultResource()可以添加系统默认资源 
  29.    */  
  30.   private static final CopyOnWriteArrayList<String> defaultResources =  
  31.     new CopyOnWriteArrayList<String>();  
  32.     
  33.   /** 
  34.    * The value reported as the setting resource when a key is set 
  35.    * by code rather than a file resource. 
  36.    */  
  37.   static final String UNKNOWN_RESOURCE = "Unknown";  
  38.     
  39.   /** 
  40.    * Stores the mapping of key to the resource which modifies or loads  
  41.    * the key most recently 
  42.    */  
  43.   private HashMap<String, String> updatingResource;  
  44.     
  45.   static{  
  46.     //print deprecation warning if hadoop-site.xml is found in classpath  
  47.     ClassLoader cL = Thread.currentThread().getContextClassLoader();  
  48.     if (cL == null) {  
  49.       cL = Configuration.class.getClassLoader();  
  50.     }  
  51.     if(cL.getResource("hadoop-site.xml")!=null) {  
  52.       LOG.warn("DEPRECATED: hadoop-site.xml found in the classpath. " +  
  53.           "Usage of hadoop-site.xml is deprecated. Instead use core-site.xml, "  
  54.           + "mapred-site.xml and hdfs-site.xml to override properties of " +  
  55.           "core-default.xml, mapred-default.xml and hdfs-default.xml " +  
  56.           "respectively");  
  57.     }  
  58.     addDefaultResource("core-default.xml");  
  59.     addDefaultResource("core-site.xml");  
  60.   }  
  61.   /**配置文件解析后的键值对**/  
  62.   private Properties properties;  
  63.   /**用于记录通过set()方式改变的配置项,而不是通过加载配置资源解析得到的变量**/  
  64.   private Properties overlay;  
  65.   private ClassLoader classLoader;  
  66.   {  
  67.     classLoader = Thread.currentThread().getContextClassLoader();  
  68.     if (classLoader == null) {  
  69.       classLoader = Configuration.class.getClassLoader();  
  70.     }  
  71.   }  
  72.     
  73.   /** A new configuration. */  
  74.   public Configuration() {  
  75.     this(true);  
  76.   }  
  77.   
  78.   /** A new configuration where the behavior of reading from the default  
  79.    * resources can be turned off. 
  80.    *  
  81.    * If the parameter {@code loadDefaults} is false, the new instance 
  82.    * will not load resources from the default files.  
  83.    * @param loadDefaults specifies whether to load from the default files 
  84.    */  
  85.   public Configuration(boolean loadDefaults) {  
  86.     this.loadDefaults = loadDefaults;  
  87.     updatingResource = new HashMap<String, String>();  
  88.     synchronized(Configuration.class) {  
  89.       REGISTRY.put(thisnull);  
  90.     }  
  91.   }  
  92.   //正则表达式对象,包含正则表达式\$\{[^\}\$\ ]+\},u0020是unicode中标识空格的十六进制  
  93.   private static Pattern varPat = Pattern.compile("\\$\\{[^\\}\\$\u0020]+\\}");  
  94.   //最多做20次扩展  
  95.   private static int MAX_SUBST = 20;  
  96.    

从上面的代码可以看出再新建Configuration对象时并没有读取XML文件中的属性(Property),而只是通过静态方法addDefaultResource()将core-default.xml和core-site.xml这两个XML文件名加入到Configuration.defaultResources静态成员变量中。这样就完成了一个Configuration对象的初始化。再这个过程中并没有从XML文件中读取Property属性的键值对,所以延迟到了需要使用的时候加载。

加载键值对

上面说过Configuration采用了延迟加载的方式来加载XML配置文件,那么在什么时候取读取XML文件将XML文件中的键值对加载到内存呢?在调用Configuration.get()方法的时候。上面的property的保存部分说道了使用Configuration.get()方法再properties中通过给定的键取其对应的值,在get()方法中调用了getProps()方法,getProps()方法先判断properties是否为空,如果为空,则调用loadResources()方法来加载XML文件中的键值对保存在properties成员变量中,loadResources()方法的代码如下:

[java]  view plain copy
  1. private void loadResources(Properties properties,  
  2.                              ArrayList resources,  
  3.                              boolean quiet) {  
  4.     if(loadDefaults) {  
  5.       for (String resource : defaultResources) {  
  6.         loadResource(properties, resource, quiet);  
  7.       }  
  8.       
  9.       //support the hadoop-site.xml as a deprecated case  
  10.       if(getResource("hadoop-site.xml")!=null) {  
  11.         loadResource(properties, "hadoop-site.xml", quiet);  
  12.       }  
  13.     }  
  14.       
  15.     for (Object resource : resources) {  
  16.       loadResource(properties, resource, quiet);  
  17.     }  
  18.   }  
loadResources先加载默认的资源(defaultResources中保存),再加载形参resources对应的资源。其中defaultResources表示通过方法addDefaultResource()可以添加系统默认资源,成员变量resources表示所有通过addRescource()方法添加Configuration对象的资源。在loadResource()方法中读取XML文件进行加载。loadResource()方法使用了DOM方式处理XML,逻辑比较简单,具体关于DOM加载XML的方式可以查阅其他资料。

总结

Configuration类在Hadoop的Common包中,它是所有Hadoop功能的基石,所以了解Hadoop首先应该知道Configuration类的作用与构造。上面介绍了Configuration的一些主要的变量与方法,可以为后面的其他源码分析打下坚实的基础。


Reference

《Hadoop技术内幕:深入解析Hadoop Common和HDFS架构设计与实现原理》

原文地址:点击打开链接

你可能感兴趣的:(hadoop,大数据,云计算)