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 配置文件的一个例子:
<?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.2 Configuration 的成员变量
org.apache.hadoop.conf.Configuration 类图如图 2-2 所示。
图 2-2 Configuration 类图
从类图可以看到,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的可以在网上找这本书。
后续还会给大家上,敬请期待。
作者:爱我的程序人生
链接:https://www.jianshu.com/p/22dfb77391d4
共同学习,写下你的评论
评论加载中...
作者其他优质文章