《Hadoop技術內幕深入解析Hadoop和HDFS》2.2 Configuration詳解
2.2Hadoop Configuration 詳解
Hadoop 沒 有 使 用 java.util.Properties 管 理 配 置 文 件, 也 沒 有 使 用 Apache Jakarta
Commons Configuration 管理配置檔案,而是使用了一套獨有的配置檔案管理系統,並提供
自己的 API,即使用 org.apache.hadoop.conf.Configuration 處理配置資訊。
2.2.1Hadoop 配置檔案的格式
Hadoop 配置檔案採用 XML 格式,下面是 Hadoop 配置檔案的一個例子:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>io.sort.factor</name>
<value>10</value>
<description>The number of streams to merge at once while sorting
files. This determines the number of open file handles.</description>
</property>
<property>
<name>dfs.name.dir</name>
<value>${hadoop.tmp.dir}/dfs/name</value>
<description>Determines where on the local filesystem the DFS name
nodeshould store the name table(fsimage). ……</description>
</property>
<property>
<name>dfs.web.ugi</name>
<value>webuser,webgroup</value>
<final>true</final>
<description>The user account used by the web interface.
Syntax: USERNAME,GROUP1,GROUP2, ……</description>
</property>
</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() 方法,把它們合併成一個配置。程式碼如下:
Configurationconf = new Configuration();
conf.addResource("core-default.xml");
conf.addResource("core-site.xml");
如果這兩個配置資源都包含了相同的配置項,而且前一個資源的配置項沒有標記為
final,那麼,後面的配置將覆蓋前面的配置。上面的例子中,core-site.xml 中的配置將覆蓋
core-default.xml 中的同名配置。如果在第一個資源(core-default.xml)中某配置項被標記為inal,那麼,在載入第二個資源的時候,會有警告提示。
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.2Configuration 的成員變數
org.apache.hadoop.conf.Configuration 類圖如圖 2-2 所示。

圖 2-2Configuration 類圖
從類圖可以看到,Configuration 有 7 個主要的非靜態成員變數。
布林變數 quietmode,用來設定載入配置的模式。如果 quietmode 為 true(預設值),則
在載入解析配置檔案的過程中,不輸出日誌資訊。quietmode 只是一個方便開發人員除錯的
變數。
數 組 resources 保 存 了 所 有 通 過 addResource() 方 法 添 加 Configuration 對 象 的 資 源。
Configuration.addResource() 有如下 4 種形式:
public void addResource(InputStream in)
public void addResource(Path file)
public void addResource(String name) //CLASSPATH 資源
public void addResource(URL url)
也就是說,使用者可以新增如下形式的資源:
❑一個已經開啟的輸入流 InputStream;
❑Hadoop 檔案路徑 org.apache.hadoop.fs.Path 形式(後面會討論 Path 類)的資源,如
hdfs:// www.example.com:54300/conf/core-default.xml;
❑URL,如 http://www.example.com/core-default.xml;
❑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 中,就有下面的程式碼,載入上述兩個預設資源:
// 下面的程式碼來自 org.apache.hadoop.hdfs.server.datanode.DataNode
static{
Configuration.addDefaultResource("hdfs-default.xml");
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 資源,相關程式碼如下:
public URL getResource(String name) {
return classLoader.getResource(name);
}
其中,getResource() 用於根據資源的名稱查詢相應的資源,並返回讀取資源的 URL 物件。
注意這裡的資源,指的是可以通過類程式碼以與程式碼基無關的方式訪問的一些資料,如圖
像、聲音、文字等,不是前面提到的配置資源。
瞭解了 Configuration 各成員變數的具體含義,Configuration 類的其他部分就比較容易理
解了,它們都是為了操作這些變數而實現的解析、設定、獲取方法。
2.2.3資源載入
資源通過物件的 addResource() 方法或類的靜態 addDefaultResource() 方法(設定了
loadDefaults 標誌)新增到 Configuration 物件中,新增的資源並不會立即被載入,只是通過
reloadConfiguration() 方法清空 properties 和 finalParameters。相關程式碼如下:
public void addResource(String name) { // 以 CLASSPATH 資源為例
addResourceObject(name);
}
private synchronized void addResourceObject(Object resource) {
resources.add(resource);// 新增到成員變數 resources 中
reloadConfiguration();
}
public synchronized void reloadConfiguration() {
properties = null;// 會觸發資源的重新載入
finalParameters.clear();
}
靜態方法 addDefaultResource() 也能清空 Configuration 物件中的資料(非靜態成員變
量),這是通過類的靜態成員 REGISTRY 作為媒介進行的。
靜態成員 REGISTRY 記錄了系統中所有的 Configuration 物件,所以,addDefaultResource()
被呼叫時,遍歷 REGISTRY 中的元素並在元素(即 Configuration 物件)上呼叫 reloadConfiguration()
方法,即可觸發資源的重新載入,相關程式碼如下:
public static synchronized void addDefaultResource(String name) {
if(!defaultResources.contains(name)) {
defaultResources.add(name);
for(Configuration conf : REGISTRY.keySet()) {
if(conf.loadDefaults) {
conf.reloadConfiguration(); // 觸發資源的重新載入
}
}
}
}
成員變數 properties 中的資料,直到需要的時候才會載入進來。在 getProps() 方法中,
如果發現 properties 為空,將觸發 loadResources() 方法載入配置資源。這裡其實採用了延遲
載入的設計模式,當真正需要配置資料的時候,才開始分析配置檔案。相關程式碼如下:
private synchronized Properties getProps() {
if (properties == null) {
properties = new Properties();
loadResources(properties, resources, quietmode);
……
}
}
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 載入部分的程式碼:
private void loadResource(Properties properties,
Object name, boolean quiet) {
try {
// 得到用於建立 DOM 解析器的工廠
DocumentBuilderFactorydocBuilderFactory
= DocumentBuilderFactory.newInstance();
// 忽略 XML 中的註釋
docBuilderFactory.setIgnoringComments(true);
// 提供對 XML 名稱空間的支援
docBuilderFactory.setNamespaceAware(true);
try {
// 設定 XInclude 處理狀態為 true,即允許 XInclude 機制
docBuilderFactory.setXIncludeAware(true);
} catch (UnsupportedOperationException e) {
……
}
// 獲取解析 XML 的 DocumentBuilder 物件
DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
Document doc = null;
Element root = null;
// 根據不同資源,做預處理並呼叫相應形式的 DocumentBuilder.parse
if (name instanceof URL) {// 資源是 URL 形式
……
doc = builder.parse(url.toString());
……
} else if (name instanceof String) {//CLASSPATH 資源
……
} else if (name instanceof Path) {// 資源是 Hadoop Path 形式的
……
} else if (name instanceof InputStream) {//InputStream
……
} else if (name instanceof Element) {// 處理 configuration 子元素
root = (Element)name;
}
if (doc == null && root == null) {
if (quiet)
return;
throw new RuntimeException(name + " not found");
}
……
一般的 JAXP 處理都是從工廠開始,通過呼叫 DocumentBuilderFactory 的 newInstance()
方法,獲得用於建立 DOM 解析器的工廠。這裡並沒有創建出 DOM 解析器,只是獲
得 一 個 用 於 創 建 DOM 解 析 器 的 工 廠, 接 下 來 需 要 對 上 述 newInstance() 方 法 得 到 的docBuilderFactory 物件進行一些設定,才能進一步通DocumentBuilderFactory,得到 DOM解析器物件 builder。
針對 DocumentBuilderFactory 物件進行的主要設定包括:
❑忽略 XML 文件中的註釋;
❑支援 XML 空間;
❑支援 XML 的包含機制(XInclude)。
XInclude 機制允許將 XML 文件分解為多個可管理的塊,然後將一個或多個較小的文件
組裝成一個大型文件。也就是說,Hadoop 的一個配置檔案中,可以利用 XInclude 機制將其
他配置檔案包含進來一併處理,下面是一個例子:
<configuration xmlns:xi="http://www.w3.org/2001/XInclude">
……
<xi:include href="conf4performance.xml"/>
……
</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。相關程式碼如下:
if (root == null) {
root = doc.getDocumentElement();
}
// 根節點應該是 configuration
if (!"configuration".equals(root.getTagName()))
LOG.fatal("bad conf file: top-level element not <configuration>");
// 獲取根節點的所有子節點
NodeList props = root.getChildNodes();
for (int i = 0; i <props.getLength(); i++) {
Node propNode = props.item(i);
if (!(propNode instanceof Element))
continue; // 如果子節點不是 Element,忽略
Element prop = (Element)propNode;
if ("configuration".equals(prop.getTagName())) {
// 如果子節點是 configuration,遞迴呼叫 loadResource 進行處理
// 這意味著 configuration 的子節點可以是 configuration
loadResource(properties, prop, quiet);
continue;
}
// 子節點是 property
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;
// 查詢 name、value 和 final 的值
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 = ((Text)field.getFirstChild()).getData().trim();
if ("value".equals(field.getTagName()) &&field.hasChildNodes())
value = ((Text)field.getFirstChild()).getData();
if ("final".equals(field.getTagName()) &&field.hasChildNodes())
finalParameter =
"true".equals(((Text)field.getFirstChild()).getData());
}
if (attr != null && value != null) {
// 如果屬性已經標誌為 'final',忽略
if (!finalParameters.contains(attr)) {
// 新增鍵 - 值對到 properties 中
properties.setProperty(attr, value);
if (finalParameter) {
// 該屬性標誌為 'final',新增 name 到 finalParameters 中
finalParameters.add(attr);}
}
……
}
}
// 處理異常
……
}
這是給大家做的一個《Hadoop技術內幕:深入解析Hadoop和HDFS》的分享,這本書是由我們的蔡斌和陳湘萍著作,大家想學Hadoop的可以在網上找這本書。
後續還會給大家上,敬請期待。