天天看點

nutch-1.7-學習筆記(1)-org.apache.nutch.crawl.Injector.java-Configuration2.2 Hadoop Configuration詳解

     

2.2 Hadoop Configuration詳解

2.2 Hadoop Configuration詳解

Hadoop沒有使用java.util.Properties管理配置檔案,也沒有使用Apache Jakarta Commons Configuration管理配置檔案,而是使用了一套獨有的配置檔案管理系統,并提供自己的API,即使用org.apache.hadoop.conf.Configuration處理配置資訊。

2.2.1 Hadoop配置檔案的格式

Hadoop配置檔案采用XML格式,下面是Hadoop配置檔案的一個例子:

  1. <?xml version="1.0"?> 
  2. <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> 
  3. <configuration> 
  4.   <property> 
  5.      <name>io.sort.factor</name> 
  6.      <value>10</value> 
  7.      <description>The number of streams to merge at once while sorting  
  8.      files.  This determines the number of open file handles.</description> 
  9.   </property> 
  10.  <property> 
  11.      <name>dfs.name.dir</name> 
  12.      <value>${hadoop.tmp.dir}/dfs/name</value> 
  13.      <description>Determines where on the local filesystem the DFS name  
  14.      nodeshould store the name table(fsimage).  ……</description> 
  15.   </property> 
  16.  <property> 
  17.      <name>dfs.web.ugi</name> 
  18.      <value>webuser,webgroup</value> 
  19.      <final>true</final> 
  20.      <description>The user account used by the web interface.  
  21.      Syntax: USERNAME,GROUP1,GROUP2, ……</description> 
  22.   </property> 
  23. </configuration> 

Hadoop配置檔案的根元素是configuration,一般隻包含子元素property。每一個property元素就是一個配置項,配置檔案不支援分層或分級。每個配置項一般包括配置屬性的名稱name、值value和一個關于配置項的描述description;元素final和Java中的關鍵字final類似,意味着這個配置項是“固定不變的”。final一般不出現,但在合并資源的時候,可以防止配置項的值被覆寫。

在上面的示例檔案中,配置項dfs.web.ugi的值是“webuser,webgroup”,它是一個final配置項;從description看,這個配置項配置了Hadoop Web界面的使用者賬号,包括使用者名和使用者組資訊。這些資訊可以通過Configuration類提供的方法通路。

在Configuration中,每個屬性都是String類型的,但是值類型可能是以下多種類型,包括Java中的基本類型,如boolean(getBoolean)、int(getInt)、long(getLong)、float(getFloat),也可以是其他類型,如String(get)、java.io.File(getFile)、String數組(getStrings)等。以上面的配置檔案為例,getInt("io.sort.factor")将傳回整數10;而getStrings("dfs.web.ugi")傳回一個字元串數組,該數組有兩個元素,分别是webuser和webgroup。

合并資源指将多個配置檔案合并,産生一個配置。如果有兩個配置檔案,也就是兩個資源,如core-default.xml和core-site.xml,通過Configuration類的loadResources()方法,把它們合并成一個配置。代碼如下:

  1. Configurationconf = new Configuration();  
  2. conf.addResource("core-default.xml");  
  3. conf.addResource("core-site.xml"); 

如果這兩個配置資源都包含了相同的配置項,而且前一個資源的配置項沒有标記為final,那麼,後面的配置将覆寫前面的配置。上面的例子中,core-site.xml中的配置将覆寫core-default.xml中的同名配置。如果在第一個資源(core-default.xml)中某配置項被标記為final,那麼,在加載第二個資源的時候,會有警告提示。

Hadoop配置系統還有一個很重要的功能,就是屬性擴充。如配置項dfs.name.dir的值是${hadoop.tmp.dir}/dfs/name,其中,${hadoop.tmp.dir}會使用Configuration中的相應屬性值進行擴充。如果hadoop.tmp.dir的值是“/data”,那麼擴充後的dfs.name.dir的值就是“/data/dfs/name”。

使用Configuration類的一般過程是:構造Configuration對象,并通過類的addResource()方法添加需要加載的資源;然後就可以使用get*方法和set*方法通路/設定配置項,資源會在第一次使用的時候自動加載到對象中。

2.2.2 Configuration的成員變量

org.apache.hadoop.conf.Configuration類圖如圖2-2所示。

nutch-1.7-學習筆記(1)-org.apache.nutch.crawl.Injector.java-Configuration2.2 Hadoop Configuration詳解

從類圖可以看到,Configuration有7個主要的非靜态成員變量。

布爾變量quietmode,用來設定加載配置的模式。如果quietmode為true(預設值),則在加載解析配置檔案的過程中,不輸出日志資訊。quietmode隻是一個友善開發人員調試的變量。

數組resources儲存了所有通過addResource()方法添加Configuration對象的資源。Configuration.addResource()有如下4種形式:

  1. public void addResource(InputStream in)  
  2. public void addResource(Path file)  
  3. public void addResource(String name)  //CLASSPATH資源  
  4. public void addResource(URL url) 

也就是說,使用者可以添加如下形式的資源:

一個已經打開的輸入流InputStream;

Hadoop檔案路徑org.apache.hadoop.fs.Path形式(後面會讨論Path類)的資源,

CLASSPATH資源(String形式),前面提到的“core-default.xml”就是這種形式。

布爾變量loadDefaults用于确定是否加載預設資源,這些預設資源儲存在defaultResources中。注意,defaultResources是個靜态成員變量,通過方法addDefaultResource()可以添加系統的預設資源。在HDFS中,會把hdfs-default.xml和hdfs-site.xml作為預設資源,并通過addDefaultResource()儲存在成員變量defaultResources中;在MapReduce中,預設資源是mapred-default.xml和mapred-site.xml。如HDFS的DataNode中,就有下面的代碼,加載上述兩個預設資源:

  1. //下面的代碼來自org.apache.hadoop.hdfs.server.datanode.DataNode  
  2. static{  
  3. Configuration.addDefaultResource("hdfs-default.xml");  
  4. Configuration.addDefaultResource("hdfs-site.xml");  

properties、overlay和finalParameters都是和配置項相關的成員變量。其中,properties和overlay的類型都是前面介紹過的java.util.Properties。Hadoop配置檔案解析後的鍵–值對,都存放在properties中。變量finalParameters的類型是Set<String>,用來儲存所有在配置檔案中已經被聲明為final的鍵–值對的鍵,如前面配置檔案例子中的鍵“dfs.web.ugi”。變量overlay用于記錄通過set()方式改變的配置項。也就是說,出現在overlay中的鍵–值對是應用設定的,而不是通過對配置資源解析得到的。

Configuration中最後一個重要的成員變量是classLoader,這是一個類加載器變量,可以通過它來加載指定類,也可以通過它加載相關的資源。上面提到addResource()可以通過字元串方式加載CLASSPATH資源,它其實通過Configuration中的getResource()将字元串轉換成URL資源,相關代碼如下:

  1. public URL getResource(String name) {  
  2. return classLoader.getResource(name);  

其中,getResource()用于根據資源的名稱查找相應的資源,并傳回讀取資源的URL 對象。

注意 這裡的資源,指的是可以通過類代碼以與代碼基無關的方式通路的一些資料,如圖像、聲音、文本等,不是前面提到的配置資源。

了解了Configuration各成員變量的具體含義,Configuration類的其他部分就比較容易了解了,它們都是為了操作這些變量而實作的解析、設定、擷取方法。

2.2.3 資源加載(1)

資源通過對象的addResource()方法或類的靜态addDefaultResource()方法(設定了loadDefaults标志)添加到Configuration對象中,添加的資源并不會立即被加載,隻是通過reloadConfiguration()方法清空properties和finalParameters。相關代碼如下:

  1. public void addResource(String name) { // 以CLASSPATH資源為例  
  2.   addResourceObject(name);  
  3. }  
  4. private synchronized void addResourceObject(Object resource) {  
  5.   resources.add(resource);// 添加到成員變量resources中  
  6.   reloadConfiguration();  
  7. }  
  8. public synchronized void reloadConfiguration() {  
  9.   properties = null;// 會觸發資源的重新加載  
  10.   finalParameters.clear();  
  11. }  

靜态方法addDefaultResource()也能清空Configuration對象中的資料(非靜态成員變量),這是通過類的靜态成員REGISTRY作為媒介進行的。

靜态成員REGISTRY記錄了系統中所有的Configuration對象,是以,addDefaultResource()被調用時,周遊REGISTRY中的元素并在元素(即Configuration對象)上調用reloadConfiguration()方法,即可觸發資源的重新加載,相關代碼如下:

  1. public static synchronized void addDefaultResource(String name) {  
  2.   if(!defaultResources.contains(name)) {  
  3.      defaultResources.add(name);  
  4.      for(Configuration conf : REGISTRY.keySet()) {  
  5.         if(conf.loadDefaults) {  
  6.            conf.reloadConfiguration(); // 觸發資源的重新加載  
  7.         }  
  8.      }  
  9.   }  

成員變量properties中的資料,直到需要的時候才會加載進來。在getProps()方法中,如果發現properties為空,将觸發loadResources()方法加載配置資源。這裡其實采用了延遲加載的設計模式,當真正需要配置資料的時候,才開始分析配置檔案。相關代碼如下:

  1. private synchronized Properties getProps() {  
  2.   if (properties == null) {  
  3.      properties = new Properties();  
  4.      loadResources(properties, resources, quietmode);  
  5.      ……  
  6.   }  

Hadoop的配置檔案都是XML形式,JAXP(Java API for XML Processing)是一種穩定、可靠的XML處理API,支援SAX(Simple API for XML)和DOM(Document Object Model)兩種XML處理方法。

SAX提供了一種流式的、事件驅動的XML處理方式,但編寫處理邏輯比較複雜,比較适合處理大的XML檔案。

DOM和SAX不同,其工作方式是:首先将XML文檔一次性裝入記憶體;然後根據文檔中定義的元素和屬性在記憶體中建立一個“樹形結構”,也就是一個文檔對象模型,将文檔對象化,文檔中每個節點對應着模型中一個對象;然後使用對象提供的程式設計接口,通路XML文檔進而操作XML文檔。由于Hadoop的配置檔案都是很小的檔案,是以Configuration使用DOM處理XML。

首先分析DOM加載部分的代碼:

  1. private void loadResource(Properties properties,  
  2.      Object name, boolean quiet) {  
  3.   try {  
  4.      //得到用于建立DOM解析器的工廠  
  5.      DocumentBuilderFactory docBuilderFactory 
  6.            = DocumentBuilderFactory.newInstance();  
  7.     //忽略XML中的注釋  
  8.      docBuilderFactory.setIgnoringComments(true);  
  9.      //提供對XML名稱空間的支援  
  10.      docBuilderFactory.setNamespaceAware(true);  
  11.      try {  
  12.         //設定XInclude處理狀态為true,即允許XInclude機制  
  13.         docBuilderFactory.setXIncludeAware(true);  
  14.      } catch (UnsupportedOperationException e) {  
  15.         ……  
  16.      }  
  17.     //擷取解析XML的DocumentBuilder對象  
  18.      DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();  
  19.      Document doc = null;  
  20.      Element root = null;  
  21.     //根據不同資源,做預處理并調用相應形式的DocumentBuilder.parse  
  22.      if (name instanceof URL) {//資源是URL形式  
  23.         ……  
  24.         doc = builder.parse(url.toString());  
  25.         ……  
  26.      } else if (name instanceof String) {//CLASSPATH資源  
  27.         ……  
  28.      } else if (name instanceof Path) {//資源是Hadoop Path形式的  
  29.         ……  
  30.      } else if (name instanceof InputStream) {//InputStream  
  31.         ……  
  32.      } else if (name instanceof Element) {//處理configuration子元素  
  33.         root = (Element)name;  
  34.      }  
  35.     if (doc == null && root == null) {  
  36.         if (quiet)  
  37.            return;  
  38.         throw new RuntimeException(name + " not found");  
  39.   }  
  40.   ……  

2.2.3 資源加載(2)

一般的JAXP處理都是從工廠開始,通過調用DocumentBuilderFactory的newInstance()方法,獲得用于建立DOM解析器的工廠。這裡并沒有建立出DOM解析器,隻是獲得一個用于建立DOM解析器的工廠,接下來需要對上述newInstance()方法得到的docBuilderFactory對象進行一些設定,才能進一步通過DocumentBuilderFactory,得到DOM解析器對象builder。

針對DocumentBuilderFactory對象進行的主要設定包括:

忽略XML文檔中的注釋;

支援XML空間;

支援XML的包含機制(XInclude)。

XInclude機制允許将XML文檔分解為多個可管理的塊,然後将一個或多個較小的文檔組裝成一個大型文檔。也就是說,Hadoop的一個配置檔案中,可以利用XInclude機制将其他配置檔案包含進來一并處理,下面是一個例子:

  1. <configuration xmlns:xi="http://www.w3.org/2001/XInclude"> 
  2.   ……  
  3.   <xi:include href="conf4performance.xml"/> 
  4.   ……  
  5. </configuration> 

通過XInclude機制,把配置檔案conf4performance.xml嵌入到目前配置檔案,這種方法更有利于對配置檔案進行子產品化管理,同時就不需要再使用Configuration.addResource()方法加載資源conf4performance.xml了。

設定完DocumentBuilderFactory對象以後,通過docBuilderFactory.newDocumentBuilder()獲得了DocumentBuilder對象,用于從各種輸入源解析XML。在loadResource()中,需要根據Configuration支援的4種資源分别進行處理,不過這4種情況最終都調用DocumentBuilder.parse()函數,傳回一個DOM解析結果。

如果輸入是一個DOM的子元素,那麼将解析結果設定為輸入元素。這是為了處理下面出現的元素configuration包含configuration子節點的特殊情況。

成員函數loadResource的第二部分代碼,就是根據DOM的解析結果設定Configuration的成員變量properties和finalParameters。

在确認XML的根節點是configuration以後,擷取根節點的所有子節點并對所有子節點進行處理。這裡需要注意,元素configuration的子節點可以是configuration,也可以是properties。如果是configuration,則遞歸調用loadResource(),在loadResource()的處理過程中,子節點會被作為根節點得到繼續的處理。

如果是property子節點,那麼試圖擷取property的子元素name、value和final。在成功獲得name和value的值後,根據情況設定對象的成員變量properties和finalParameters。相關代碼如下:

  1. if (root == null) {  
  2.   root = doc.getDocumentElement();  
  3. }  
  4. //根節點應該是configuration  
  5. if (!"configuration".equals(root.getTagName()))  
  6.   LOG.fatal("bad conf file: top-level element not <configuration>");  
  7. //擷取根節點的所有子節點  
  8. NodeList props = root.getChildNodes();  
  9. for (int i = 0; i <props.getLength(); i++) {  
  10.   Node propNode = props.item(i);  
  11.   if (!(propNode instanceof Element))  
  12.      continue;  //如果子節點不是Element,忽略  
  13.  Element prop = (Element)propNode;  
  14.   if ("configuration".equals(prop.getTagName())) {  
  15.      //如果子節點是configuration,遞歸調用loadResource進行處理  
  16.      //這意味着configuration的子節點可以是configuration  
  17.      loadResource(properties, prop, quiet);  
  18.      continue;  
  19.   }  
  20.  //子節點是property  
  21.   if (!"property".equals(prop.getTagName()))  
  22.      LOG.warn("bad conf file: element not <property>");  
  23.  NodeList fields = prop.getChildNodes();  
  24.      String attr = null;  
  25.      String value = null;  
  26.      boolean finalParameter = false;  
  27.     //查找name、value和final的值  
  28.      for (int j = 0; j <fields.getLength(); j++) {  
  29.         Node fieldNode = fields.item(j);  
  30.         if (!(fieldNode instanceof Element))  
  31.            continue;  
  32.         Element field = (Element)fieldNode;  
  33.         if ("name".equals(field.getTagName()) &&field.hasChildNodes())  
  34.            attr = ((Text)field.getFirstChild()).getData().trim();  
  35.         if ("value".equals(field.getTagName()) &&field.hasChildNodes())  
  36.            value = ((Text)field.getFirstChild()).getData();  
  37.         if ("final".equals(field.getTagName()) &&field.hasChildNodes())  
  38.            finalParameter =  
  39.               "true".equals(((Text)field.getFirstChild()).getData());  
  40.      }  
  41.     if (attr != null && value != null) {  
  42.         //如果屬性已經标志為'final',忽略  
  43.         if (!finalParameters.contains(attr)) {  
  44.            //添加鍵-值對到properties中  
  45.            properties.setProperty(attr, value);  
  46.            if (finalParameter) {  
  47.               //該屬性标志為'final',添加name到finalParameters中  
  48.               finalParameters.add(attr);  
  49.            }  
  50.         }  
  51.         ……  
  52.      }  
  53.   }  
  54.   //處理異常  
  55.   ……  
  56. }  

2.2.4 使用get*和set*通路/設定配置項

1. get*

get*一共代表21個方法,它們用于在Configuration對象中擷取相應的配置資訊。這些配置資訊可以是boolean(getBoolean)、int(getInt)、long(getLong)等基本類型,也可以是其他一些Hadoop常用類型,如類的資訊(getClassByName、getClasses、getClass)、String數組(getStringCollection、getStrings)、URL(getResource)等。這些方法裡最重要的是get()方法,它根據配置項的鍵擷取對應的值,如果鍵不存在,則傳回預設值defaultValue。其他的方法都會依賴于Configuration.get(),并在get()的基礎上做進一步處理。get()方法如下:

  1. public String get(String name, String defaultValue) 

Configuration.get()會調用Configuration的私有方法substituteVars(),該方法會完成配置的屬性擴充。屬性擴充是指配置項的值包含${key}這種格式的變量,這些變量會被自動替換成相應的值。也就是說,${key}會被替換成以key為鍵的配置項的值。注意,如果${key}替換後,得到的配置項值仍然包含變量,這個過程會繼續進行,直到替換後的值中不再出現變量為止。

substituteVars的工作依賴于正規表達式:

  1. varPat:\$\{[^\}\$ ]+\} 

由于“$”、左花括号“{”、右花括号“}”都是正規表達式中的保留字,是以需要通過“\”進行轉義。正規表達式varPat中,“\$\{”部分用于比對${key}中的key前面的“${”,最後的“\}”部分比對屬性擴充項的右花括号“}”,中間部分“[^\}\$ ]+”用于比對屬性擴充鍵,它使用了兩個正規表達式規則:

[^ ]規則,通過[^ ]包含一系列的字元,使表達式比對這一系列字元以外的任意一個字元。也就是說,“[^\}\$ ]”将比對除了“}”、“$”和空格以外的所有字元。注意,$後面還包含了一個空格,這個看不見的空格,是通過空格的Unicode字元\u0020添加到表達式中的。

+是一個修飾比對次數的特殊符号,通過該符号保證了“+”前面的表達式“[^\}\$ ]”至少出現1次。

通過正規表達式“\$\{[^\}\$ ]+\}”,可以在輸入字元串裡找出需要進行屬性擴充的地方,并通過字元串替換,進行屬性擴充。

前面提過,如果一次屬性擴充完成以後,得到的表達式裡仍然包含可擴充的變量,那麼,substituteVars()需要再次進行屬性擴充。考慮下面的情況:

屬性擴充${key1}的結果包含屬性擴充${key2},而對${key2}進行屬性擴充後,産生了一個包含${key1}的新結果,這會導緻屬性擴充進入死循環,沒辦法停止。

針對這種可能發生的情況,substituteVars()中使用了一個非常簡單而又有效的政策,即屬性擴充隻能進行一定的次數(20次,通過Configuration的靜态成員變量MAX_SUBST定義),避免出現上面分析的屬性擴充死循環。

最後一點需要注意的是,substituteVars()中進行的屬性擴充,不但可以使用儲存在Configuration對象中的鍵–值對,而且還可以使用Java虛拟機的系統屬性。如系統屬性user.home包含了目前使用者的主目錄,如果使用者有一個配置項需要使用這個資訊,可以通過屬性擴充${user.home},來獲得對應的系統屬性值。而且,Java指令行可以通過“-D<name>=<value>”的方式定義系統屬性。這就提供了一個通過指令行,覆寫或者設定Hadoop運作時配置資訊的方法。在substituteVars()中,屬性擴充優先使用系統屬性,然後才是Configuration對象中儲存的鍵–值對。具體代碼如下:

  1. //正規表達式對象,包含正規表達式\$\{[^\}\$ ]+\}  
  2. //注意,u0020前面隻有一個”\”,轉義發生在Java裡,不在正規表達式裡  
  3. private static Pattern varPat =  
  4.      Pattern.compile("\\$\\{[^\\}\\$\u0020]+\\}");  
  5. //最多做20次屬性擴充  
  6. private static int MAX_SUBST = 20;  
  7. private String substituteVars(String expr) {  
  8.   if (expr == null) {  
  9.      return null;  
  10.   }  
  11.   Matcher match = varPat.matcher("");  
  12.   String eval = expr;  
  13.  //循環,最多做MAX_SUBST次屬性擴充  
  14.   for(int s=0; s<MAX_SUBST; s++) {  
  15.      match.reset(eval);  
  16.     if (!match.find()) {  
  17.         return eval;  //什麼都沒有找到,傳回  
  18.      }  
  19.      String var = match.group();  
  20.      varvar = var.substring(2, var.length()-1); //獲得屬性擴充的鍵  
  21.      String val = null;  
  22.      try {  
  23.         //看看系統屬性裡有沒有var對應的val  
  24.         //這一步保證了我們首先使用系統屬性做屬性擴充  
  25.         val = System.getProperty(var);  
  26.      } catch(SecurityException se) {  
  27.         LOG.warn("Unexpected SecurityException in Configuration", se);  
  28.      }  
  29.     if (val == null) {  
  30.         //看看Configuration儲存的鍵-值對裡有沒有var對應的val  
  31.         val = getRaw(var);  
  32.      }  
  33.     if (val == null) {  
  34.         //屬性擴充中的var沒有綁定,不做擴充,傳回  
  35.         return eval;  
  36.      }  
  37.     //替換${……},完成屬性擴充  
  38.      evaleval = eval.substring(0,match.start())  
  39.             +val+eval.substring(match.end());  
  40.   }  
  41.  //屬性擴充次數過多,抛異常  
  42.   throw new IllegalStateException(……);  
  43. }  

2. set*

相對于get*來說,set*的大多數方法都很簡單,這些方法對輸入進行類型轉換等處理後,最終都調用了下面的Configuration.set()方法:

  1. public String set(String name, String value) 

對比相對複雜的Configuration.get(),成員函數set()隻是簡單地調用了成員變量properties和overlay的setProperty()方法,儲存傳入的鍵–值對。

轉自:

繼續閱讀