参考: JeffreyZhou的博客园
- 《Hadoop权威指南》第四版
0. 为什么选择0.20.2版本
前面学习搭建的Hadoop版本是2.7.6,可是这里为什么要学习0.20.2这么老的版本呢?
因为Hadoop经过这么多年的发展,其源代码已经是一个庞大的代码库了,而我的目的是研究Hadoop最基本,最底层的实现思想,而不是各种各样的插件和功能代码,所以还是找一个早期的版本源码来学习吧。这里贴上一个Apache的软件大全:
在里面几乎包括了Apache大多数的项目,按字母排序,往下找就可以看到hadoop文件夹了,在common/
文件夹内,就可以看到各个版本的hadoop了,本例中下载的hadoop-0.20.2.tar.gz
文件。
然后将其解压到一个专门的文件夹就行了,用来进行学习研究。
1.1 源文件目录
根据本文的参考资料,整理如下:
目录/文件 | 说明 |
---|---|
bin | 下面存放着可执行的sh命名,所有操作都在这里 |
conf | 配置文件所在目录 |
ivy | Apache Ivy是专门用来管理项目的jar包依赖的,这个是ivy的主要目录 |
lib | 引用的库文件目录,里面存放用到的jar包 |
src | 这个里面就是主要的源码了 |
build.xml | 用于编译的配置文件。 编译我们用的是ant(还未编译暂时无build文件夹,后面会讲到) |
CHANGES.txt | 文本文件,记录着本版本的变更历史 |
ivy.xml | Ivy的配置文件 |
LICENSE.txt | 文件本文件 |
NOTICE.txt | 文本文件,记录着需要注意的地方 |
README.txt | 说明文件 |
src目录
进入src目录,可以看见:
目录/文件 | 说明 |
---|---|
ant | 为ant命令编写的扩展指定 |
build | 就存放一个打包信息文件 |
c++ | linux下amd64-64位系统以及i386-32位系统提供的库文件集合 |
contrib | 是开源界或第三方为hadoop编写的一些扩展程序,如eclipse插件等 |
core | Hadoop的核心代码 |
docs | 文档 |
examples | 示例程序 |
hdfs | HDFS模块的代码 |
marped | MapReduce模块代码 |
test | 测试程序 |
tools | 工具集 |
webapps | 网页管理工具的代码,主要是jsp文件 |
fixFontsPath.sh | 用于修正字体路径的批处理命令 |
saveVersion.sh | 用于生成打包信息文件批处理命令 |
1.2 编译
对于刚下载的源代码,是无法直接运行的,需要编译,这个主要就是ant
和build.xml
的作用了。
先修改一个build.xml,打开,将其中的版本号改为:0.20.2,如:
用ant进行编译,至于这是什么操作,我不太懂,也没去深究,想着暂时能搭起来框架再说,所以直接在命令行里输入(这里前提是执行路径在已经解压后的hadoop-src文件夹中):
~/hadoop-src$ ant
~/hadoop-src$ ant jar
~/hadoop-src$ ant examples
这时候,就好自动去下载jar包,进行编译了,好像时间不短,到最后出现下面这样就行了:
这时,在根目录可以发现,多了一个build文件夹,这个文件夹下,我们发现有大量的子文件夹,再深入看,可以找到了N多个.class文件。那这个正是java程序的编译产出物。
关于ant
我们知道,一个.java程序对应一个.class文件,手动的话用javac来编译。我们要将这么多的java文件都要编译成一个个的.class文件,敲javac命令肯定是不行的,我们得找个打包处理的办法。这个就是ant。简单的说ant就是将编译命名进行打包处理的程序,这个程序有一个配置文件就是build.xml。所以我们进入hadoop根目录后输入了ant后就开始运行了,因为它在当前目录下找到了build.xml文件
1.3 build.xml文件
我们简要的来看一下build.xml。 打开一看,build.xml文件貌似很复杂,有1千8百多行,但不要怕,我们暂不关注代码细节,用面向对象的思想,简单看下结构:
-
可以看到,一上来,定义了一个project,看来这是一个工程,有名称和default属性(default后面看是啥)。
接下来发现是一堆的property,然后是name-value的健值。应该猜的出,这些就是后面真正执行用的一些变量或参数。 -
再往下,看到有这些:
看到有target,然后取了个名,字面意思是目标,然后看看子结点,发现是mkdir,好熟悉的字眼,这不是在创建目录么,看下第一个dir是啥,${build.dir}。然后立即跑回上面property中,看下是否有呢?
果然,这个就是在编译后的产生的目录,第一步创建之,很正常。
既然这样,这个target就是一个个目标,然后往下拖一下,发现下面的都是一个个的目录,全文搜索一下:
发现里面有106个。
- 继续搜,发现了亮点:
这个target(目标)好眼熟,~/hadoop-src$ant jar
没错,当时在编译时,输入这个命令后,就产出了一个jar文件。看来这个target就是在形成 jar文件,略看下其子命令,的确就是在生成jar包了。
简单了解了这个target后,就可以继续找找,我们刚才编译时使用的examples命令了。现回想起来,在编译时第一个命令是~/hadoop-src$ant
,这个后面没有加参数,所以,有一个默认参数?想到第一行,新建project时,里面有一个default=“complie”
,
来查查看,搜索“target name="complie”:
果然,猜的没错。找到了这个默认目录,然后发现好多target后还有depends,字面意思,依赖吧,然后可以继续找,依赖里面的目录,也是一个个的target。
到这里,也就知道了,这个build.xml文件里面包括了很多子模块,我们在一开始就只使用了默认参数,jar,example,还有很多没用过。
1.4 运行
在前面的学习中,启动Hadoop需要在 Master 节点上执行:
$ start-dfs.sh
$ start-yarn.sh
$ mr-jobhistory-daemon.sh start historyserver
那么,这个start-dfs.sh
和start-yarn.sh
又是什么东东呢?我们先打开bin目录看下:
我们先观察下所有文件,看到下面8个,很有规律,4个startXXX.sh然后4个stopXXX.sh文件。看来这些就是用户启动和关闭hadoop用的。
可以看到,里面有很多个sh文件,hadoop这个文件,虽然没有后缀,但打开看后,发现跟其他sh文件样,类似的脚本。
在windows中我们知道bat文件,就是将若干个命令放到一个文件中,依次执行,称之为批处理文件。在Linux中,这个bat文件就是sh文件。
1.4.1 start-all.sh文件
在早期的Hadoop版本中,启动Hadoop使用的是start-all.sh
命令,知道到了现在已经被换成了start-dfs.sh
和start-yarn.sh
,那我们就先看看start-all.sh
这个 all 到底 all 了多少东西。
直接看主要内容,发现并不多,首先是调用了一下hadoop-config.sh
,看字面意思就是配置文件,然后再调用start-dfs.sh
和start-mapred.sh
,然后就没啦。这不就是偷个懒,将两个命令放在一个命令了嘛,没啥干货。
同理,打开stop-all.sh
也就是对称的几个命令了:
1.4.2 start-dfs.sh文件
那就按照上面的思路来吧,它是靠着start-dfs.sh
来伪装自己的,那我们就看看这里面有啥:
这里面乍一看都是相同的命令,其实主要是两条命令hadoop-daemons.sh
和hadoop-daemon.sh
,其实这时大家可以打开其他的几个startXX.sh
和stopXX.sh
文件,你会发现,所有的操作都又转入了hadoop-daemon.sh和hadoop-daemons.sh这两个命令,同时传入了参数—config stop/start (opt参数),即形式为:
hadoop-daemon(s).sh --config $HADOOP_CONF_DIR stop/start namenode/datanode/...
1.4.3 hadoop-daemons.sh文件
其实,打开hadoop-daemons.sh
,
可以看到,这一连串下来,最终执行的就是两个东西:hadoop-daemon.sh
和slaves.sh
。
1.4.4 slaves.sh文件
看到 slaves 文件,可以想到在前期配置Hadoop2.7.6分布式环境的时候,我们也配置了一个slaves文件,里面是用回车换行隔开的slave1、slave2等各个子节点。那么,这两个slaves文件是否有关系呢?来看看:
看一下代码,第一个if与fi之间就是取得conf文件夹下的slaves文件(就是配置Hadoop分布式环境时,创建的slaves文件),所以第二段代码,for循环slaves中的文件,然后调用ssh命令,调到了子系统中的相应的命令,这里,就完全可以想通了,为什么子系统中部署的hadoop目录需要与主目录相同,然后slaves中配置的是子系统机器的名称。
1.4.5 hadoop-daemon.sh文件
到这里,整个bin目录的脚本,就集中在剩下的两个hadoop-daemon.sh
和hadoop
了。胜利在望了。先看hadoop-daemon.sh。
一开始,代码是在取参数,startstop和command,从前面的传入可以看到,startstop参数传的是start和stop,看来是启动和关闭, command是namenode、datanode之类的。
继续往下看:
对照前面传进来的参数,进行case分类,将start和stop命令分开处理。
在start时,先是创建一个文件夹PID_DIR,具体值可以看上面,然后第一段if,是在判断进程有没有启动,然后最关健是执行
nohup nice -n $HADOOP_NICENESS…. /bin/hadoop。
也就是说归根到底又都是在执行hadoop
命令了。这里nohup,是指启动进程后不被卡住,即转为后台进程,又称守护进程,所以该sh文件命名为daemon也不为过。
然后stop段时,把进程进行kill掉。这里有疑问了,启动的命令kill里需要知道进程的PID,而kill里哪里获取呢,在启动时,将启动进程后的pid记录在PID文件夹内,然后kill时就可以跟据这些PID来处理了。机智。
1.4.6 hadoop文件
在执行hadoop命令时,又将namenode、datanode、secondarynamenode等命令传入。所以现在可以打开hadoop命令文件了,(直接跳入重点部分):
可以看到,这里有大量的if语句,判断条件就是command命令,然后给class和hadoop_opts,赋予不同的值,先不看后面,这里有点java基础的同学可以看到,class的值,像不像java包的名?换句话说,是不是就是class文件的位置?所以,这一个个NameNode之类的主函数应该就是在这找到并执行咯?好,继续往下看:
哦哟,这里真的是在执行Java程序,可以看到后面的路径是$CLASSPATH $CLASS
,这里应该是hadoop目录下的包路径,那就去看看到底是啥吧,比如找到并打开DataNode.java
文件,然后搜索是否有main函数:
perfect!
1.5总结一下
整个hadoop程序,是一个java为主的程序,编译是将.class文件生成在build目录,在运行时,虽然执行的的.sh文件,但在.sh脚本文件中,一步步的执行下去,最终还是执行java命令,执行入口就是各个子程序的函数入口。
1.6 后记
-
前面的启动程序我们都是通过命令
start-all.sh
,在后台启动整个程序,输出内容在log文件夹内,那么,按照上面的推理,可以从命令行单独启动NameNode或者DataNode,可以试一试。在命令行输入bin/hadoop namenode
,应该是可以看到启动了namenode程序,日志信息也直接打印在屏幕上了。 -
在hadoop.sh文件中,可以看到所有的入口,那就把它整理成一个表,这就是所有的main函数:
命令 | 入口 |
---|---|
namenode | org.apache.hadoop.hdfs.server.namenode.NameNode |
secondarynamenode | org.apache.hadoop.hdfs.server.namenode.SecondaryNameNode |
datanode | org.apache.hadoop.hdfs.server.datanode.DataNode |
fs / dfs | org.apache.hadoop.fs.FsShell |
jobtracker | org.apache.hadoop.mapred.JobTracker |
tasktracker | org.apache.hadoop.mapred.TaskTracker |
job | org.apache.hadoop.mapred.JobClient |
version | org.apache.hadoop.util.VersionInfo |
- 现在知道hadoop是从哪开始启动的了,也找到了各个main函数的入口,那么,接下来就顺着这些入口去探索吧。