Hadoop有提供一些脚本命令,以便于我们对HDFS进行管理,可以通过命令hadoop fs
进行查看:
通过以上使用说明可以发现,里面提供了大多数和我们在本地操作文件系统相类似的命令,例如,cat
查看文件内容,chgrp
改变用户群组权限,chmod
改变用户权限,chown
改变用户拥有者权限,还有创建目录,查看目录,移动文件,重命名等等。
这里,我们来看看命令hadoop fs -ls
:
这个命令大家肯定非常熟悉,在Linux下使用超级频繁,功能就是列出指定目录下的文件及文件夹。那接下来,我们来看看它是怎样查找到此目录下的文件及文件夹的。
在运行hadoop fs -ls /
命令之后,真正的命令只是hadoop
,后面只是参数,那么,这个hadoop
命令到底是哪呢?如果说集群是自己手动搭配的话,那大家肯定知道,这个命令就在${HADOOP_HOME}/bin
目录下,但如果集群是自动化部署的时候,你可能一下子找不到这个命令在哪,此时,可以使用以下命令查找:
可以看到,这个命令应该是在目录/usr/bin/
之下,使用vim /usr/bin/hadoop
查看命令详细:
#!/bin/bash
export HADOOP_HOME=${HADOOP_HOME:-/usr/hdp/2.5.0.0-1245/hadoop}
export HADOOP_MAPRED_HOME=${HADOOP_MAPRED_HOME:-/usr/hdp/2.5.0.0-1245/hadoop-mapreduce}
export HADOOP_YARN_HOME=${HADOOP_YARN_HOME:-/usr/hdp/2.5.0.0-1245/hadoop-yarn}
export HADOOP_LIBEXEC_DIR=${HADOOP_HOME}/libexec
export HDP_VERSION=${HDP_VERSION:-2.5.0.0-1245}
export HADOOP_OPTS="${HADOOP_OPTS} -Dhdp.version=${HDP_VERSION}"
exec /usr/hdp/2.5.0.0-1245//hadoop/bin/hadoop.distro "$@"
这个脚本做了两件事,一是export了一些环境变量,使得之后运行的子程序都可以共享这些变量的值;二是执行了命令hadoop.distro
命令,并传上了所有参数。
现在,我们来看下命令hadoop.distro
做了哪些事,由于代码有点小多,我就不全部贴了,只贴与执行命令hadoop fs -ls /
相关的代码。
命令hadoop.distro
做的事情是:根据之前传入的参数,然后做一些判断,确定一些变量的值,最后执行的是以下命令:
exec "$JAVA" $JAVA_HEAP_MAX $HADOOP_OPTS $CLASS "$@"
这里,我们看到了一堆变量,其中$JAVA
:java
$JAVA_HEAP_MAX
:$HADOOP_OPTS
:
# Always respect HADOOP_OPTS and HADOOP_CLIENT_OPTS
HADOOP_OPTS="$HADOOP_OPTS $HADOOP_CLIENT_OPTS"
#make sure security appender is turned off
HADOOP_OPTS="$HADOOP_OPTS -Dhadoop.security.logger=${HADOOP_SECURITY_LOGGER:-INFO,NullAppender}"
$CLASS
:org.apache.hadoop.fs.FsShell
$@
: -ls /
注意:这里已经没有参数fs
了
因此,命令hadoop.distro
也就转换成执行一个JAVA类了,然后继续带上参数。
打开hadoop的源代码,找到类org.apache.hadoop.fs.FsShell
,它的main
方法如下:
public static void main(String argv[]) throws Exception {
FsShell shell = newShellInstance(); //创建FsShell实例
Configuration conf = new Configuration(); //配置类,
conf.setQuietMode(false); //设置成“非安静模式”,默认为“安静模式”,在安静模式下,error和information的信息不会被记录。
shell.setConf(conf);
int res;
try {
res = ToolRunner.run(shell, argv); //ToolRunner就是一个工具类,用于执行实现了接口`Tool`的类
} finally {
shell.close();
}
System.exit(res);
}
ToolRunner
类结合GenericOptionsParser
类来解析命令行参数,
在运行上述ToolRunner.run(shell, argv)
代码之后,经过一番解释之后,最后真正执行的仍然是类FsShell
的run
方法,而且对其参数进行了解析,run
方法如下:
@Override
public int run(String argv[]) throws Exception {
// initialize FsShell 包括注册命令类
init();
int exitCode = -1;
if (argv.length < 1) {
printUsage(System.err); //打印使用方法
} else {
String cmd = argv[0]; //取到第一个参数,即 ls
Command instance = null;
try {
// 取得实现了该命令(ls)的命令实例,并且此类已经通过类CommandFactory的addClass方法进行了注册
instance = commandFactory.getInstance(cmd);
if (instance == null) {
throw new UnknownCommandException();
}
exitCode = instance.run(Arrays.copyOfRange(argv, 1, argv.length));
} catch (IllegalArgumentException e) {
displayError(cmd, e.getLocalizedMessage());
if (instance != null) {
printInstanceUsage(System.err, instance);
}
} catch (Exception e) {
// instance.run catches IOE, so something is REALLY wrong if here
LOG.debug("Error", e);
displayError(cmd, "Fatal internal error");
e.printStackTrace(System.err);
}
}
return exitCode;
}
注意:通过查看类Ls
的代码,我们可以发现,它有一个静态方法registerCommands
,这个方法就是对类Ls进行注册,但是,这只是一个静态方法,那么,到底是在哪进行了此方法的调用呢?
class Ls extends FsCommand {
public static void registerCommands(CommandFactory factory) {
factory.addClass(Ls.class, "-ls");
factory.addClass(Lsr.class, "-lsr");
}
...
细心的朋友可能已经发现,就在类FsShell的run
方法中,调用了一个init
方法,而就在此方法中,有一行注册命令的代码,如下:
protected void init() throws IOException {
getConf().setQuietMode(true);
if (commandFactory == null) {
commandFactory = new CommandFactory(getConf());
commandFactory.addObject(new Help(), "-help");
commandFactory.addObject(new Usage(), "-usage");
// 注册,调用registerCommands方法
registerCommands(commandFactory);
}
}
protected void registerCommands(CommandFactory factory) {
// TODO: DFSAdmin subclasses FsShell so need to protect the command
// registration. This class should morph into a base class for
// commands, and then this method can be abstract
if (this.getClass().equals(FsShell.class)) {
// 调用CommandFactory类的registerCommands方法
// 注意,这里传的参数是类FsCommand
factory.registerCommands(FsCommand.class);
}
}
CommandFactory类的registerCommands方法如下:
public void registerCommands(Class> registrarClass) {
try {
// 这里触发的是类CommandFactory的registerCommands方法
registrarClass.getMethod(
"registerCommands", CommandFactory.class
).invoke(null, this);
} catch (Exception e) {
throw new RuntimeException(StringUtils.stringifyException(e));
}
}
接下来,我拉看看类CommandFactory的registerCommands方法,代码如下:
public static void registerCommands(CommandFactory factory) {
factory.registerCommands(AclCommands.class);
factory.registerCommands(CopyCommands.class);
factory.registerCommands(Count.class);
factory.registerCommands(Delete.class);
factory.registerCommands(Display.class);
factory.registerCommands(Find.class);
factory.registerCommands(FsShellPermissions.class);
factory.registerCommands(FsUsage.class);
// 我们会用到的就是这个类Ls
factory.registerCommands(Ls.class);
factory.registerCommands(Mkdir.class);
factory.registerCommands(MoveCommands.class);
factory.registerCommands(SetReplication.class);
factory.registerCommands(Stat.class);
factory.registerCommands(Tail.class);
factory.registerCommands(Test.class);
factory.registerCommands(Touch.class);
factory.registerCommands(Truncate.class);
factory.registerCommands(SnapshotCommands.class);
factory.registerCommands(XAttrCommands.class);
}
我们再来看看Ls类
class Ls extends FsCommand {
public static void registerCommands(CommandFactory factory) {
factory.addClass(Ls.class, "-ls");
factory.addClass(Lsr.class, "-lsr");
}
也就是,在调用init
方法的时候,对这些命令类进行了注册。
因此,上面的那个instance
,在这里的话,其实就是类Ls
的实例。类Ls
继承类FsCommand
,而类FsCommand
是继承类Command
,前面instance调用的run方法其实是父类Command
的run
方法,此方法主要做了两件事,一是处理配置选项,如-d
,-R
,-h
,二是处理参数,如下:
public int run(String...argv) {
LinkedList args = new LinkedList(Arrays.asList(argv));
try {
if (isDeprecated()) {
displayWarning(
"DEPRECATED: Please use '"+ getReplacementCommand() + "' instead.");
}
processOptions(args);
processRawArguments(args);
} catch (IOException e) {
displayError(e);
}
return (numErrors == 0) ? exitCode : exitCodeForError();
}
方法processRawArguments
的调用层次关系如下:
\-> processRawArguments(LinkedList)
|-> expandArguments(LinkedList)
| \-> expandArgument(String)*
\-> processArguments(LinkedList)
|-> processArgument(PathData)*
| |-> processPathArgument(PathData)
| \-> processPaths(PathData, PathData...)
| \-> processPath(PathData)*
\-> processNonexistentPath(PathData)
从这个层次关系中可以看出,整个方法是先进行展开参数,传入的参数是LinkedList
,展开后的参数是LinkedList
,PathData
类中包含了Path
,FileStatus
,FileSystem
。其实,当程序运行到这里的时候,命令ls
的结果就已经可以通过类PathData
中的相关方法获取了。
展开参数后,开始进行处理参数,此时的参数就是LinkedList
,然后循环处理此List
,先是判断目录是否存在,是否需要递归查找,是否只是列出本目录(就是看有没有-R
和-d
参数),我们来看一下到底是如何输出结果的:
@Override
protected void processPaths(PathData parent, PathData ... items)
throws IOException {
if (parent != null && !isRecursive() && items.length != 0) {
out.println("Found " + items.length + " items");
}
adjustColumnWidths(items); // 计算列宽,重新构建格式字符串
super.processPaths(parent, items);
}
看到这里,大家是不是觉得很面熟?没想起来?我们上个图:
这下看到了吧,最是输出结果的第一行,找到11项。
接下来重新调整了一下列宽,最后调用了父类的processPaths
方法,我们继续来看父类的这个方法,它做了哪些事:
protected void processPaths(PathData parent, PathData ... items)
throws IOException {
// TODO: this really should be iterative
for (PathData item : items) {
try {
processPath(item); // 真正处理每一项,然后打印出来
if (recursive && isPathRecursable(item)) {
recursePath(item); // 如果有指定参数 -R,则需要进行递归
}
postProcessPath(item); // 这个没理解,DFS还有后序DFS么?有知情者,请告知,谢谢。
} catch (IOException e) {
displayError(e);
}
}
}
我们来看一下打印具体每行信息的代码:
@Override
protected void processPath(PathData item) throws IOException {
FileStatus stat = item.stat;
String line = String.format(lineFormat,
(stat.isDirectory() ? "d" : "-"), // 文件夹显示d,文件显示-
stat.getPermission() + (stat.getPermission().getAclBit() ? "+" : " "), // 获取权限
(stat.isFile() ? stat.getReplication() : "-"),
stat.getOwner(), // 获取拥有者
stat.getGroup(), // 获取组
formatSize(stat.getLen()), // 获取大小
dateFormat.format(new Date(stat.getModificationTime())), // 日期
item // 项,即路径
);
out.println(line); // 打印行
}
到这里,命令hadoop fs -ls /
的执行过程基本已经结束(关于文件系统内部细节,后续再讲),这就是整个命令执行的过程。最后,我们来总结一下:
hadoop fs -ls /
,首先执行的是shell命令,然后转换成执行Java类。Ls
的run
方法。Ls
的相关方法。类Ls
负责处理路径,并打印详情。