为了账号安全,请及时绑定邮箱和手机立即绑定

《Hadoop技术内幕深入解析Hadoop和HDFS》2.2 Configuration详解

标签:
Hadoop

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 所示。

webp

图 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


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消