Hadoop没有使用java.util.Properties管理配置文件,也没有用Apache Jakarta Commons Configuration管理配置文件,而是使用了一套独有的配置文件管理系统,使用org.apache.hadoop.conf.Configuration处理配置信息。
Hadoop的配置文件采用xml格式
如例子所示:
Hadoop 配置文件的根元素是configuration,一般只包含子元素property。
每一个property元素是一个配置项
每个配置项一般包括name,value和一个关于配置项的描述description,元素final表示这个配置项是固定不变的,在合并资源的时候,可以防止配置项的值被覆盖
Configuration中每个属性都是String类型的,但是值类型可能是多种类型(java中的基本类型、String、File、String数组等)
以上面的为例,getInt(“io.sort.factor”)返回整数10,而getStrings(“dfs.web.ugi”)则会返回一个字符串数组,该数组有两个元素分别是webuser和webgroup
合并资源指将多个配置文件合并,产生一个配置,通过Configuration类的loadResources()方法,把他们合并成一个配置:
如果两个配置自愿都包含了相同的配置项,而且前一个自愿的配置项没有被标记为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*方法访问/设置配置项,资源会在第一次使用的时候自动加载到对象中。
Configuration有7个主要的非静态成员变量
quietmode:设置加载配置的模式,默认值是true,表示在加载解析配置文件的过程中,不输出日志信息。
resources:保存了所有通过addResource()方法添加Configuration对象的资源
finalParameters:保存所有配置文件中已经被声明为final的键--值对的键
loadDefaults:用于是否确定加载默认资源,而这些默认资源保存在defaultResources
defaultResources:静态成员变量,通过addDefaultResources()可以添加系统的默认资源。
我们所常见的core-default.xml、core-site.xml、hdfs-default.xml、hdfs-site.xml、mapred-default.xml和mapred-site.xml等都属于默认资源。
REGISTRY:WeakHashMap<Configuration,Object>类型,用于多个对象的相关配置的注册及对它们进行管理,记录了所有的Configuration;
properties:hadoop配置文件解析后的键值对,存放在此成员变量中
资源配置文件中的属性会加载到Properties属性中来
overlay:记录通过set()方式改变的配置项。出现在overlay中的键–值对是应用设置的,而不是通过对配置资源解析得到的。
classLoader:类加载器变量,可以用来加载指定类或者加载相关的资源
addResource()可以通过字符串方式加载CLASSPATH资源,它其实通过Configuration中的getResource()将字符串转换成URL资源,相关代码如下:
=====================================================================================================
静态代码块:
以上代码会在调用构造方法之前执行,会加载core-default.xml和core-site.xml两个文件,Configuration的子类也会同样加载这两个文件。
构造方法:
public Configuration()
public Configuration(boolean loadDefaults)
public Configuration(Configuration other)
第三个构造函数是克隆一个Configuration对象,调用的是object类的clone方法
在1行代码处,我们看见将新创建的这个Configuration放到REGISTRY变量中
资源通过对象的addResource方法或类的静态addDefaultResources()方法(设置了loadDefaults标志)添加到Configuration对象中,相关代码如下:
静态成员REGISTRY记录了系统中所有的Configuration对象,所以addDefaultResource()被调用时。
在add之后会调用reloadConfiguration()方法清空properties和finalParameters。
遍历REGISTRY中的元素并在元素(即Configuration对象)上调用reloadConfiguration(),即可触发资源的重新加载。
重新加载Configuration就是重新将里面的属性记录清空,这就将内存中存在的name-value都清空了。
这里我们看到会清空propertries和finalParameters。
文章开头大致的介绍了get*方法,这些方法里最重要的是get()方法,它根据配置项的键获取对应的值,如果键不存在,则返回默认值defaultValue。
其他的方法都会依赖于Configuration.get(),并在get()的基础上做进一步处理。get()方法如下:
首先将该属性的name的值,这个属性有可能是数组,将其值或者被替代的值放到数组中,然后观察getProps()方法。
代码1行处,我们发现添加的资源并不会立即被加载,但也就是刚才add之后资源并不会立即被加载,说明hadoop的Configuration被设计成了“懒加载”,即在需要时才会被加载。当真正需要配置数据的时候,才开始分析配置文件。
成员变量properties中的数据,直到需要的时候才会加载进来。在getProps()方法中,如果发现properties为空,将触发loadResources()方法加载配置资源。
我们再次进入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。
接下来我们查看该方法。
substituteVars(getProps().getProperty(n));
Set类似get,只不过相比get来说比较简单
相对于get*来说,set*的大多数方法都很简单,这些方法对输入进行类型转换等处理后,最终都调用了下面的Configuration.set()方法:
public String set(String name, String value)
对比相对复杂的Configuration.get(),成员函数set()只是简单地调用了成员变量properties和overlay的setProperty()方法,保存传入的键–值对。
总结:
1、Configuration的构造函数里面默认loadDefaults是true,即表示加载默认配置资源
加载资源有两种方法:
addDefaultResource()
addResource()
前者是加载默认资源,静态方法,后者是加载指定的资源
加载的时候是懒加载,即不会立即加载资源,用到的时候才会加载资源
但是都会调用到reloadConfiguration是重新加载,这里只是清除原来的数据
2、所有的get*方法都会调用到get方法,
而get方法又会调用getProps()方法,触发加载资源,完成后进入
substituteVars方法,进行属性扩展
参考资料:
http://www.2cto.com/kf/201412/357011.html
http://www.hackbase.com/tech/2014-12-12/70073.html
Hadoop技术内幕:深入解析Hadoop Common和HDFS架构设计与实现原理(蔡斌 陈湘萍)
================================================================================================
由于“$”、左花括号“{”、右花括号“}”都是正则表达式中的保留字,因此需要通过“\”进行转义。正则表达式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对象中保存的键–值对。
一般的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。