参考: JeffreyZhou的博客园
- 《Hadoop权威指南》第四版
0 前言
在之前源码初窥中,我们已经找到了主要几个的main函数入口,这里我们列一列:
FsShell – main | org.apache.hadoop.fs.FsShell |
NameNode – main | org.apache.hadoop.hdfs.server.namenode.NameNode |
DataNode – main | org.apache.hadoop.hdfs.server.datanode.DataNode |
JobTracker – main | org.apache.hadoop.mapred.JobTracker |
TaskTracker – main | org.apache.hadoop.mapred.TaskTracker |
按照这个顺序去学下吧。
3.1 main函数入口类
在eclipse中我们很方便地就能找到每个模块的对应的main函数,但还是有些不便,为了调试方便,我们再新建三个入口类:
自建入口类主要是为方便找到,然后这三个类中的代码分别为:
// FsShellEnter.java
import org.apache.hadoop.fs.FsShell;
public class FsShellEnter {
public static void main(String[] args) throws Exception {
FsShell.main(new String[]{"-ls"});
}
}
// NameNodeEnter.java
public class NameNodeEnter {
public static void main(String[] args) throws Exception {
org.apache.hadoop.hdfs.server.namenode.NameNode.main(args);
}
}
// DataNodeEnter.java
public class DataNodeEnter {
public static void main(String[] args) {
org.apache.hadoop.hdfs.server.datanode.DataNode.main(args);
}
}
可以运行一下来测试是否成功,就按照上一篇文末讲的:
启动命令行,运行$ bin/hadoop namenode
,然后在eclipse中,打开FsShellEnter.java
,运行之,应该可以看到hdfs dfs -ls
一样的效果。
反之亦然。
3.2 Configuration类
Configuration类,见字如面,用于读取配置文件的,先看看在程序中是如何使用Configuration类的:
Configuration conf = new Configuration();
String name = conf.get("fs.defaultFS");
System.out.println(name):
可以看出,上述程序就是读出配置文件中fs.defaultFS
的值。
小插曲 – new一个实例的过程:
- 先加载静态变量
- 再加载非静态成员变量
- 最后构造函数
3.2.1 静态变量
所以,先看看静态初始化模块:
static {
// 中间省略掉的是 deprecation warning,无光紧要
addDefaultResource("core-default.xml");
addDefaultResource("core-site.xml");
}
可以看到,在构造方法运行之前,会先加载core-default.xml和core-site.xml两个文件,大家应该会想起来,当初在Hadoop分布式搭建的时候,编辑过几个配置文件,其中就有core-site.xml文件,但是没有core-default.xml文件。
有兴趣的同学,可以去打开这两个文件看看,就会发现,两者有一些相同的配置项,而在这个加载时,按照顺序,先加载default,再加载site,有相同的key时,site则会覆盖掉default。
3.2.2 构造函数
打开源码会发现,有三个构造函数:
// 1、调用第二个构造函数,参数为true
public Configuration() {
this(true);
}
// 2、确定是否加载默认配置
public Configuration(boolean loadDefaults) {
this.loadDefaults = loadDefaults;
if (LOG.isDebugEnabled()) {
LOG.debug(StringUtils.stringifyException(new IOException("config()")));
}
synchronized(Configuration.class) {
REGISTRY.put(this, null);
}
}
// 3、将指定的Configuration对象重新复制一份
public Configuration(Configuration other) {
if(LOG.isDebugEnable()) {
LOG.debug(StringUtils.stringifyException(new IOException("config(config)")));
}
this.resources = (ArrayList)other.properties.clone();
synchronized(other) {
if(other.properties != null) {
this.properties = (Properties)other.properties.clone();
}
if(other.overlay != null) {
this.overlay = (Properties)other.overlay.clone();
}
}
this.finalParameters = new HashSet<String>(other.finalParameters);
synchronized(Configuration.class) {
REGISTRY.put(this, null);
}
}
也就是说,默认是要加载上面两个配置文件的(true),好吧,我就先不管他加进去之后咋处理了,反正知道,在
Configuration conf = new Configuration();
之后,conf类就已经把core-site.xml加载进去了,里面一些内容如下:
<configuration>
<property>
<name>fs.default.name</name>
<value>hdfs://localhost:9000</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/home/gmy/hadoop/tmp</value>
</property>
</configuration>
3.2.3 get() 函数
那接下来就研究一下
String name = conf.get("fs.defaultFS");
这句是干嘛的吧,咋就返回了我们想要的String name了呢。
// get函数
public String get(String name, String defaultValue) {
return substituteVars(getProps().getProperty(name, defaultValue));
}
// getProps函数
private synchronized Properties getProps() {
if (properties == null) {
properties = new Properties();
loadResources(properties, resources, quietmode);
if (overlay!= null)
properties.putAll(overlay);
}
return properties;
}
// getProperty()函数为Property中的方法,输入Key,返回value;
// 若不存在key,则返回defaultValue
这里面先说getProps()函数,这里又出来了一个properties,这又是啥呢?不知道大家有没有注意到,上面的core-site.xml中,就有一个标签<property></property>
,哈,是不是有种顿悟的感觉。具体见后记。
getProps函数中,对hashtable类型的properties进行判断,如果为空则调用loadResources()方法来加载XML文件中的键值对保存在properties成员变量中,否则直接返回。然后getProperty再根据其key值进行取值。
这里是采用了懒加载的方式,就是说并没有一开始加载配置文件中的数据,而是等要访问时,才进行加载。
接着往下看,用到了loadResource函数:
private void loadResources(Properties properties, ArrayList resources, boolean quiet) {
// 加载defaultResource
if(loadDefaults) {
for (String resource : defaultResources) {
loadResource(properties, resource, quiet);
}
//support the hadoop-site.xml as a deprecated case
if(getResource("hadoop-site.xml")!=null) {
loadResource(properties, "hadoop-site.xml", quiet);
}
}
// 加载形参resource的资源
for (Object resource : resources) {
loadResource(properties, resource, quiet);
}
}
loadResources先加载默认的资源(defaultResources中保存),再加载形参resources对应的资源。其中defaultResources表示通过方法addDefaultResource()可以添加系统默认资源,成员变量resources表示所有通过addRescource()方法添加Configuration对象的资源。
在loadResource()方法中读取XML文件进行加载。loadResource()方法使用了DOM方式处理XML,逻辑比较简单,具体关于DOM加载XML的方式可以查阅其他资料。
通过以上getProps就会获得所有配置信息了,调用其getProperty方法就可以获取需要属性的值了。再传递给substituteVars进行属性扩展。关于属性扩展概念见后记。
捋一捋
- 初始化,加载core-site.xml,搁着,没动;
- get(),调用getProps()函数,得到resoure中的properties;
- 中间再调用getProperties()函数,取得其中的value值;
- 最后再使用subtituteVars()函数,修正最后结果。
3.3 总结一下
从最开始的start-all.sh
启动脚本开始,一步步往下摸索,发现脚本启动实际上是启动java程序;
然后又按照.sh脚本中的路径,找到各个模块的main函数,并将核心的几个main函数自建了几个入口,方便启动;
在研究核心模块之前,先研究了一下基础的Configuration类的大概原理,知道了就是对于各个配置文件(.xml文件)进行加载,读取。
3.x 后记
3.x.1 Configuration中的Property 和 Resource
resource——各个.xml
文件,里面含有一个或多个property;
properties——一个键值对为一个property;
配置文件的根节点是<configuration>,下一层节点是
<property>,每个property都代表一个配置项,有键值对组成,name节点表示该配置项的键,value节点表示值,除了name和value节点,property节点另一个重要的子节点是final,它表示这个键值对是不可覆盖的,是固定不变的,和Java中的final关键字类似。property节点还有个<description>子节点,是对该属性的描述,类似于Java的注释,在程序中不使用。
3.x.2 什么是属性扩展?
举例说明:
如果配置项dfs.name.dir值是${hadoop.tmp.dir}/dfs/name
,而配置项hadoop.tmp.dir的值是/data, 那么${hadoop.tmp.dir}会使用hadoop.tmp.dir的值/data进行扩展,扩展后dfs.name.dir的值为/data/dfs/name。