Hadoop源碼之Configuration

  本文hadoop版本爲最新版本2.6。Configuration作爲Hadoop的一個基礎功能承擔着重要的責任,爲Yarn、HSFS、MapReduce、NFS、調度器等提供參數的配置、配置文件的分佈式傳輸(實現了Writable接口)等重要功能。java

  Hadoop的加載配置文件的功能沒有采用Java本身的java.util.Properties,也沒有采用Apache Jakarta Commons中的Commons Configuration,而是本身單獨實現了一個本身的Configuration類:org.apache.hadoop.conf.Configuration,在hadoop-common-project子工程中。它的實現子類有:HdfsConfiguration、YarnConfiguration、JobConf、NfsConfiguration、FairSchedulerConfiguration等。正則表達式

  1、Configuration類重要屬性講解apache

  A、quitemode:boolean類型,配置信息加載過程當中,是否處於安靜模式,即有一些信息不會被記錄,默認是true;數組

  B、resources:ArrayList<Resource>類型,Resource是Configuration的內部類,有兩個屬性Object resource和String name;resources是一個對象數組,用於存儲有關包含配置信息的對象;網絡

  C、finalParameters:Set<String>類型,全部被聲明爲final的變量集合,聲明爲final就表示不能被後續覆蓋;app

  D、loadDefaults:boolean類型,是否加載默認配置;jvm

  E、REGISTRY:WeakHashMap<Configuration,Object>類型,用於多個對象的相關配置的註冊及對它們進行管理,記錄了全部的Configuration;分佈式

  F、defaultResources:CopyOnWriteArrayList<String>類型,用於存儲默認的配置資源名或路徑;ide

  G、properties:java內置的Properties類型,存儲全部配置信息,KV值;oop

  H、overlay:Properties類型,是用戶設置的而不是經過對資源解析獲得的;

  I、classloader:ClassLoader類型,主要用於加載指定的類或者加載相關資源;

  J、updatingResource:HashMap<String, String[]>類型,存儲最近加載或修改的屬性;

  K、VAR_PATTERN:靜態Pattern類型,用於正則匹配,Pattern.compile("\\$\\{[^\\}\\$\u0020]+\\}"),正則表達式中$、{、}都是保留字,因此須要用"\"進行轉義,「\\$\\{」用於匹配${key}中的key前面的"${";最後的"\\}"用於匹配key後的"}";中間部分"[^\\}\\$\u0020]+"用於匹配屬性擴展鍵,將匹配除了"$"、"}"和空格(\u0020指的是空格)之外的全部字符,還有"+"出現至少1次。

  L、MAX_SUBST:靜態int類型,默認值是20,MAX_SUBST是設定對帶有環境變量的值所可以深刻解析的層次數,超出這個最大的層數的值將不可以解析。

  2、Configuration的初始化

  A、靜態代碼塊,用於加載默認的配置資源

 1     //是一個靜態初始化塊,用於加載默認的配置資源。
 2     static {
 3         //print deprecation warning if hadoop-site.xml is found in classpath
 4         ClassLoader cL = Thread.currentThread().getContextClassLoader();
 5         if (cL == null) {
 6             cL = Configuration.class.getClassLoader();
 7         }
 8         if (cL.getResource("hadoop-site.xml") != null) {
 9             LOG.warn("DEPRECATED: hadoop-site.xml found in the classpath. " +
10                     "Usage of hadoop-site.xml is deprecated. Instead use core-site.xml, "
11                     + "mapred-site.xml and hdfs-site.xml to override properties of " +
12                     "core-default.xml, mapred-default.xml and hdfs-default.xml " +
13                     "respectively");
14         }
15         addDefaultResource("core-default.xml");
16         addDefaultResource("core-site.xml");
17     }

  以上代碼會在調用構造方法以前執行,會加載core-default.xml和core-site.xml兩個文件,Configuration的子類也會一樣加載這兩個文件。

  B、三個構造方法,Configuration()、Configuration(boolean loadDefaults)、Configuration(Configuration other),第一個會調用第二個參數是true;第二個肯定是否加載默認配置;第三個就是將指定的Configuration對象從新複製一份。

  3、加載資源

  能夠經過Configuration的addResource()方法或者靜態方法addDefaultResource()(設置了loadDefaults標誌)來添加資源到Configuration中。可是add以後資源並不會當即被加載,hadoop的Configuration被設計成了「懶加載」,即在須要時纔會被加載。在add以後會調用reloadConfiguration()方法清空properties和finalParameters。

  A、addDefaultResource方法,這是一個靜態方法。經過這個方法能夠添加系統的默認資源。在HDFS中會被用來加載hdfs-default.xml和hdfs-site.xml;在MapReduce中會被用來加載mapred-default.cml和mapred-site.xml,能夠在相關的Configuration子類中找到相應地靜態代碼塊。 

 1     /**
 2      * Add a default resource. Resources are loaded in the order of the resources
 3      * added.
 4      *
 5      * @param name file name. File should be present in the classpath.
 6      */
 7     public static synchronized void addDefaultResource(String name) {
 8         if (!defaultResources.contains(name)) {
 9             defaultResources.add(name);
10             for (Configuration conf : REGISTRY.keySet()) {
11                 if (conf.loadDefaults) {
12                     conf.reloadConfiguration();
13                 }
14             }
15         }
16     }

  此方法經過遍歷REGISTRY中得元素並在元素(Configuration對象)上調用reloadConfiguration方法,就會觸發資源的從新加載。

  B、addResource方法,該方法有6種形式:

  public void addResource(Path file)  

  public void addResource(String name)

  public void addResource(URL url)

  public void addResource(InputStream in)

  public void addResource(InputStream in, String name)

  public void addResource(Configuration conf)

  也就是能夠add的形式:能夠是一個輸入流、HDFS文件路徑、WEB URL、CLASSPATH資源、以及Configuration對象。這些方法都會將參數封裝成Resource對象後,傳遞給addResourceObject方法並調用該方法。在addResourceObject方法中會將Resource對象加入resources中並調用reloadConfiguration方法。代碼以下:

1     public synchronized void reloadConfiguration() {
2         properties = null;                            // trigger reload
3         finalParameters.clear();                      // clear site-limits
4     }
5 
6     private synchronized void addResourceObject(Resource resource) {
7         resources.add(resource);                      // add to resources
8         reloadConfiguration();
9     }

  4、get*取值

  A、get*方法,get*方法通常有兩個參數,一個是須要獲取屬性的名字,另一個是默認值,以便找不到值時就返回默認值。這些方法都會先經過getTrimmed(name)去掉兩端的空格,而後調用get(String name)方法取值。get方法中通過處理過時鍵以後會調用substituteVars消除屬性擴展狀況。在調用substituteVars以前會先調用getProps方法,這個方法在發現properties爲null時會經過loadResources加載配置資源。

 1 protected synchronized Properties getProps() {
 2         if (properties == null) {
 3             properties = new Properties();
 4             HashMap<String, String[]> backup =
 5                     new HashMap<String, String[]>(updatingResource);
 6             loadResources(properties, resources, quietmode);
 7             if (overlay != null) {
 8                 properties.putAll(overlay);
 9                 for (Map.Entry<Object, Object> item : overlay.entrySet()) {
10                     String key = (String) item.getKey();
11                     updatingResource.put(key, backup.get(key));
12                 }
13             }
14         }
15         return properties;
16     }

  loadResources相關調用代碼以下:

  1 private void loadResources(Properties properties,
  2                                ArrayList<Resource> resources,
  3                                boolean quiet) {
  4         if (loadDefaults) { //加載默認配置資源
  5             for (String resource : defaultResources) {
  6                 loadResource(properties, new Resource(resource), quiet);
  7             }
  8 
  9             //support the hadoop-site.xml as a deprecated case
 10             if (getResource("hadoop-site.xml") != null) {
 11                 loadResource(properties, new Resource("hadoop-site.xml"), quiet);
 12             }
 13         }
 14 
 15         for (int i = 0; i < resources.size(); i++) {    //其餘配置資源
 16             Resource ret = loadResource(properties, resources.get(i), quiet);
 17             if (ret != null) {
 18                 resources.set(i, ret);
 19             }
 20         }
 21     }
 22 
 23     private Resource loadResource(Properties properties, Resource wrapper, boolean quiet) {
 24         String name = UNKNOWN_RESOURCE;
 25         try {
 26             Object resource = wrapper.getResource();
 27             name = wrapper.getName();
 28             //獲得用於建立DOM解析器的工廠
 29             DocumentBuilderFactory docBuilderFactory
 30                     = DocumentBuilderFactory.newInstance();
 31             //ignore all comments inside the xml file忽略XML中得註釋
 32             docBuilderFactory.setIgnoringComments(true);
 33 
 34             //allow includes in the xml file提供對XML命名空間的支持
 35             docBuilderFactory.setNamespaceAware(true);
 36             try {
 37                 //設置XInclude處理狀態爲true,即容許XInclude機制
 38                 docBuilderFactory.setXIncludeAware(true);
 39             } catch (UnsupportedOperationException e) {
 40                 LOG.error("Failed to set setXIncludeAware(true) for parser "
 41                                 + docBuilderFactory
 42                                 + ":" + e,
 43                         e);
 44             }
 45             //獲取解析XML的DocumentBuilder對象
 46             DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
 47             Document doc = null;
 48             Element root = null;
 49             boolean returnCachedProperties = false;
 50             //根據不一樣資源,作預處理並調用相應刑事的DocumentBuilder.parse
 51             if (resource instanceof URL) {                  // an URL resource
 52                 doc = parse(builder, (URL) resource);
 53             } else if (resource instanceof String) {        // a CLASSPATH resource
 54                 URL url = getResource((String) resource);
 55                 doc = parse(builder, url);
 56             } else if (resource instanceof Path) {          // a file resource
 57                 // Can't use FileSystem API or we get an infinite loop
 58                 // since FileSystem uses Configuration API.  Use java.io.File instead.
 59                 File file = new File(((Path) resource).toUri().getPath())
 60                         .getAbsoluteFile();
 61                 if (file.exists()) {
 62                     if (!quiet) {
 63                         LOG.debug("parsing File " + file);
 64                     }
 65                     doc = parse(builder, new BufferedInputStream(
 66                             new FileInputStream(file)), ((Path) resource).toString());
 67                 }
 68             } else if (resource instanceof InputStream) {
 69                 doc = parse(builder, (InputStream) resource, null);
 70                 returnCachedProperties = true;
 71             } else if (resource instanceof Properties) {
 72                 overlay(properties, (Properties) resource);
 73             } else if (resource instanceof Element) {
 74                 root = (Element) resource;
 75             }
 76 
 77             if (root == null) {
 78                 if (doc == null) {
 79                     if (quiet) {
 80                         return null;
 81                     }
 82                     throw new RuntimeException(resource + " not found");
 83                 }
 84                 root = doc.getDocumentElement();
 85             }
 86             Properties toAddTo = properties;
 87             if (returnCachedProperties) {
 88                 toAddTo = new Properties();
 89             }
 90             //根節點應該是configuration
 91             if (!"configuration".equals(root.getTagName()))
 92                 LOG.fatal("bad conf file: top-level element not <configuration>");
 93             //獲取根節點的全部子節點
 94             NodeList props = root.getChildNodes();
 95             DeprecationContext deprecations = deprecationContext.get();
 96             for (int i = 0; i < props.getLength(); i++) {
 97                 Node propNode = props.item(i);
 98                 if (!(propNode instanceof Element))
 99                     continue;   //若是子節點不是Element,則忽略
100                 Element prop = (Element) propNode;
101                 if ("configuration".equals(prop.getTagName())) {
102                 //若是子節點是configuration,遞歸調用loadResource進行處理,這意味着configuration的子節點能夠是configuration
103                     loadResource(toAddTo, new Resource(prop, name), quiet);
104                     continue;
105                 }
106                 //子節點是property
107                 if (!"property".equals(prop.getTagName()))
108                     LOG.warn("bad conf file: element not <property>");
109                 NodeList fields = prop.getChildNodes();
110                 String attr = null;
111                 String value = null;
112                 boolean finalParameter = false;
113                 LinkedList<String> source = new LinkedList<String>();
114                 //查找name、value、final的值
115                 for (int j = 0; j < fields.getLength(); j++) {
116                     Node fieldNode = fields.item(j);
117                     if (!(fieldNode instanceof Element))
118                         continue;
119                     Element field = (Element) fieldNode;
120                     if ("name".equals(field.getTagName()) && field.hasChildNodes())
121                         attr = StringInterner.weakIntern(
122                                 ((Text) field.getFirstChild()).getData().trim());
123                     if ("value".equals(field.getTagName()) && field.hasChildNodes())
124                         value = StringInterner.weakIntern(
125                                 ((Text) field.getFirstChild()).getData());
126                     if ("final".equals(field.getTagName()) && field.hasChildNodes())
127                         finalParameter = "true".equals(((Text) field.getFirstChild()).getData());
128                     if ("source".equals(field.getTagName()) && field.hasChildNodes())
129                         source.add(StringInterner.weakIntern(
130                                 ((Text) field.getFirstChild()).getData()));
131                 }
132                 source.add(name);
133 
134                 // Ignore this parameter if it has already been marked as 'final'
135                 if (attr != null) {
136                     if (deprecations.getDeprecatedKeyMap().containsKey(attr)) {
137                         DeprecatedKeyInfo keyInfo =
138                                 deprecations.getDeprecatedKeyMap().get(attr);
139                         keyInfo.clearAccessed();
140                         for (String key : keyInfo.newKeys) {
141                             // update new keys with deprecated key's value
142                             loadProperty(toAddTo, name, key, value, finalParameter,
143                                     source.toArray(new String[source.size()]));
144                         }
145                     } else {
146                         loadProperty(toAddTo, name, attr, value, finalParameter,
147                                 source.toArray(new String[source.size()]));
148                     }
149                 }
150             }
151 
152             if (returnCachedProperties) {
153                 overlay(properties, toAddTo);
154                 return new Resource(toAddTo, name);
155             }
156             return null;
157         } catch (IOException e) {
158             LOG.fatal("error parsing conf " + name, e);
159             throw new RuntimeException(e);
160         } catch (DOMException e) {
161             LOG.fatal("error parsing conf " + name, e);
162             throw new RuntimeException(e);
163         } catch (SAXException e) {
164             LOG.fatal("error parsing conf " + name, e);
165             throw new RuntimeException(e);
166         } catch (ParserConfigurationException e) {
167             LOG.fatal("error parsing conf " + name, e);
168             throw new RuntimeException(e);
169         }
170     }
View Code

  如上,若是容許(loadDefaults==true)加載默認資源則會優先加載defaultResources中得資源,若是CLASSPATH下還有hadoop-site.xml文件也會加載;最後將指定的資源進行加載,由於有順序,因此有同名的話會被覆蓋,除非是final類型的。

  經過以上getProps就會得到全部配置信息了,調用其getProperty方法就能夠獲取須要屬性的值了。再傳遞給substituteVars進行屬性擴展,代碼以下:

 1 //是配合正則表達式對象對含有環境變量的參數值進行解析的方法
 2     private String substituteVars(String expr) {
 3         if (expr == null) {
 4             return null;
 5         }
 6         Matcher match = VAR_PATTERN.matcher("");
 7         String eval = expr;
 8         //循環,最多作MAX_SUBST次屬性擴展
 9         for (int s = 0; s < MAX_SUBST; s++) {
10             match.reset(eval);
11             if (!match.find()) {
12                 return eval;    //什麼都沒找到,返回
13             }
14             String var = match.group();
15             var = var.substring(2, var.length() - 1); // remove ${ .. }得到屬性擴展的鍵
16             String val = null;
17             try {
18                 //俺看java虛擬機的系統屬性有沒有var對應的val,這一步保證了優先使用java的系統屬性
19                 val = System.getProperty(var);
20             } catch (SecurityException se) {
21                 LOG.warn("Unexpected SecurityException in Configuration", se);
22             }
23             if (val == null) {
24                 val = getRaw(var);  //而後是Configuration對象中得配置屬性
25             }
26             if (val == null) {
27                 //屬性擴展中得var沒有綁定,不作擴展,返回
28                 return eval; // return literal ${var}: var is unbound
29             }
30             // substitute替換${ ... },完成屬性擴展
31             eval = eval.substring(0, match.start()) + val + eval.substring(match.end());
32         }
33         //屬性擴展次數太多,拋出異常
34         throw new IllegalStateException("Variable substitution depth too large: "
35                 + MAX_SUBST + " " + expr);
36     }

  這裏會限制擴展次數,優先考慮配置的系統屬性,而後是Configuration中配置的屬性。java系統屬性,能夠經過-DXXX=YYY的方式在jvm或者啓動命令中指定。

  這樣set*獲取到string類型的值了,而後能夠根據返回類型進行處理。

  5、set*設置配置項,set相對於get則要簡單一些,set*方法最終會調用set(String name, String value, String source)方法,source方法用來講明configuration的來源,通常設置爲null,這個方法會調用properties和overlay的setProperty()方法,保存傳入的鍵值對,同時也會更新updatingResource。

  在編寫mapreduce時,可能須要各個task共享一些數據,能夠經過Configuration的set*方法來配置,並在mapper或者reducer中setup方法中的context獲取。

  總之來講,1、建立對象,會加載默認資源(前提是loadResource=true);2、add資源(可選,沒有這步就是hadoop默認的資源了),會清楚原來的數據,但不會當即加載資源;3、get*方法,會觸發資源的加載(getProps),處理屬性擴展等,返回屬性對應的值;4、set*設置本身的參數。

   

參考:

  一、蔡斌 陳湘萍 《Hadoop技術內幕---深刻將誒西Hadoop Common和HDFS結構設計與實現原理》

  二、一些網絡資源

相關文章
相關標籤/搜索