参考: JeffreyZhou的博客园
- 《Hadoop权威指南》第四版
上一篇文章中讲到,DataNode.java中的main函数:
public static void main(String args[]) {
try {
StringUtils.startupShutdownMessage(DataNode.class, args, LOG);
DataNode datanode = createDataNode(args, null); //
if (datanode != null)
datanode.join();
} catch (Throwable e) {
LOG.error(StringUtils.stringifyException(e));
System.exit(-1);
}
}
我们在上一篇的后半部分,顺着startupShutdownMessage()
的线索,去摸索了一下在启动和关闭时的 MSG 来源,接下来的createDataNode()
才是DN的初始化呢。
5.1 先看下源码中的注释
从main函数中开始运行,创建DataNode对象,然后在runDatanodeDaemon函数创建了一个线程,然后程序调用了线程就停留在哪里了。
在DataNode源代码的注释中:
DataNodes spend their lives in an endless loop of asking the NameNode for sth. to do. A NameNode cannot connect to a DataNode directly; a NameNode simply returns values from functions invoked by a DataNode.
DataNodes maintain an open server sockets so that client code or other DataNodes can read/write data. the host/port for this server is reported to NameNode. which then sends that information to clients or other DataNodes that might be interesed.
从我个人理解来看,就是说哦,DN开启线程运行后,就一直处于轮询状态,socket一直打开状态,并把host/port报告给NN,记录下来,等有客户端或者其他DN需要的时候,找到NN,查到该host/port,就可以直接从DN读写数据,而不用经过NN这个中间商赚差价。
这也就是为什么第一段说,NN不能直接连接DN,而是返回一些值,而DN一直处于循环中,他等的不是主人(NN),而是客户(client)。
借助《Hadoop权威指南》中的图看一下:
所以,DataNode总的来说,初始化就是两块,创建对象,运行线程,然后waiting for request。
5.2 主要函数
public static DataNode createDataNode(String args[], Configuration conf) {
DataNode dn = instantiateDataNode(args, conf);
runDatanodeDaemon(dn);
return dn;
}
在源码中顺着createDataNode(args, null)
一步步往下探索,大概的整理了一下所用到的主要函数,在此用xmind大概的画了一下:
其中有4个备注地方;
- instantiateDataNode(args, conf)
检查参数,配置文件是否存在/有效;检查参数,配置文件是否存在/有效;
获取tmp/data/dfs/data里的内容; - makeInstance (dataDirs,conf)
检查dirs是否存在/有效;
存储有效的dirs列表,即有效的DN节点; - new DataNdoe (conf, dirs)
新建Datanode对象 - startDataNode (conf, dataDirs)
初始化完毕,开启socket等,开启datanode
DataNode启动前的动作大致就是上述这些流程了。也比较好理解、
接下来将其中各个函数列出来看一下。
5.3 instantiateDataNode函数
public static DataNode instantiateDataNode(String args[], Configuration conf) {
// 判断参数是否为空
if(conf == null)
conf = new Configuration();
if(!parseArguments(args, conf)) {
printUsage();
return null;
}
// 判断该配置是否有效
// if(conf.get("dfs.network,scirpt") != null) {
// LOG.error("This configuration for rack identification is not supported anymore");
// System.exit(-1);
// }
// 获取tmp/dfs/data
String[] dataDirs = conf.getString("dfs.data.dir");
dnThreadName = "DataNode:["+StringUtils.arratToString(dataDirs)+"]";
return makeInstance(dataDirs, conf);
}
这里面的"dfs.data.dir",大家有没有感觉有点熟悉啊?而且用的还是Configuration.getString(),回忆一下。
结合前面几篇文章里的,Configuration类读取的就是core-site.xml
等文件里面的内容,这些.xml
文件就是在最开始,配置(伪)分布式环境时,所编辑的配置文件。依稀记得,有一个xml文件里面是有这个属性的,咱去找找。
果然,在hdfs-site.xml
里面看到这两个配置:
<property>
<name>dfs.namenode.name.dir</name>
<value>file:/usr/local/hadoop/tmp/dfs/name</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>file:/usr/local/hadoop/tmp/dfs/data</value>
</property>
既然知道了这个目录,那咱就去找一找这个目录下是啥玩意儿:
奇怪的是,我刚开始并没有找到这个目录,只看到了dfs.namenode.name.dir
的位置,突然想起,我现在操作的是Masrer节点,而Data应该是在Slave节点中,去看看。
果然有东西, 但是怎么只有一个文件夹呢,看源码中,返回的类型是String[]
类型啊,一个文件夹也成为一个数组啦?不对,虽然每个DN中只有一个文件夹,但是咱有好几个DN呢,嗯,果真是数组。
等等,这个文件夹好像也有点印象,打开看看:
乖乖,这不就是在最开始搭建分布式环境时,因为错误执行了hadoop namenode -format
后,导致运行失败的原因所在地么。
有兴趣的可以看看我遇过的坑 Hadoop实践学习中的问题汇总(持续补充)
好了,这到底干啥用的后面再看吧,总之知道了,这个dataDirs中作用应该就是查到与NN对应的DN的id啊之类的信息匹配上吧。
5.4 makeInstance函数
public static DataNode makeInstance(String[] args, Configuration conf) {
ArrayList<File> dirs = new ArrayList<File>();
for(int i = 0; i < dataDirs.length; i++) {
File data = new File(dataDirs[i]);
DiskChecker.checkDir(data);
dirs.add(data);
}
if(dirs.size() > 0)
return new DataNoede(conf, dirs);
LOG.error("All directories in dfs.dir are invalid.");
return null
}
上面讲了获取的dataDirs是啥内容了,这里就是对获取到的内容,挨个检查,可能是防止一些有问题的DN混进来吧。比如(个人猜测)某个DN起初是在正常的,但是出故障了,被NN解除掉了。
好了,筛掉了滥竽充数的,接下来就为每个DN构造对象了。
5.5 DataNode构造函数
DataNode(Configuration conf, AbstractList<File> dataDirs) {
super(conf);
datanodeObject = this;
startDataNode(conf, dataDirs);
}
构造函数很简单,就是创建一个对象,调用启动函数。
关于startDataNode(conf, dataDirs);
,这里就暂时不深究了,大概看了一下,就是启动Socket之类的,按照本文开头说的,启动之后,就是不停的向NN轮询之类的,具体内容我还需要再练练级才能来闯。。。
5.6 别忘了还有runDatanodeDaemon函数呢
前面一系列都是操作都是实例化DN对象,然后在runDatanodeDaemon函数创建了一个线程,然后程序调用了线程就停留在哪里了。
换句话说,整个DataNode目前看来,就是一个大线程在跑。
这里有两个地方讲一下:
- SetDaemon(true):这个函数的意义,并非是简单的字面意义了(Daemon:守护),一般情况下,当一个线程还没运行完,主程序是卡住的,但设置为daemon为true时,当主程序结束,则线程也就自动结束。
- Join():这个函数就是等待线程结束后再往下走。就相当于我们常在主序中写的while(true){}一样。
java多线程 —— 创建线程的两种方法:
- 继承Thread类,复写run()方法,new此类对象,调用start()方法;
- 实现Runnable接口,复写run()方法,new此类对象,作为Thread类的参数启动。
继承Thread类方法不太方便,因为一个类只能继承一个类,但可继承多个接口,所以用接口更加灵活。
5.x 后记
这里大概的知道了一下DN的启动过程,至于在运行过程中的各种操作后面用到再来研究。
根据DN和NN中的main函数表现,我估计NN的启动过程应该也差不多,毕竟也要检查configuration和dfs.namenode.name.dir之类的配置和参数,但应该要比DN多一些内容,毕竟人家是领导层,权利越大,责任越大。
顺便查看了一下dfs.namenode.name.dir
,即/usr/local/hadoop/tmp/dfs/name</value>
里的内容,其实和DN一样的形式。