频道栏目
首页 > 资讯 > 云计算 > 正文

Hadoop源码学习-脚本命令(hadoopfs-ls)执行细节

17-04-06        来源:[db:作者]  
收藏   我要投稿

Hadoop源码学习-脚本命令(hadoopfs-ls)执行细节,Hadoop有提供一些脚本命令,以便于我们对HDFS进行管理,可以通过命令hadoop fs进行查看:

这里写图片描述

通过以上使用说明可以发现,里面提供了大多数和我们在本地操作文件系统相类似的命令,例如,cat查看文件内容,chgrp改变用户群组权限,chmod改变用户权限,chown改变用户拥有者权限,还有创建目录,查看目录,移动文件,重命名等等。

hadoop fs -ls

这里,我们来看看命令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 /的执行过程基本已经结束(关于文件系统内部细节,后续再讲),这就是整个命令执行的过程。最后,我们来总结一下:

执行shell。执行命令hadoop fs -ls /,首先执行的是shell命令,然后转换成执行Java类。 执行Java。在执行Java类的时候,使用工具类对其进行配置项解析,并使用反射机制对命令进行了转换,于是后面变成了调用类Ls的run方法。 调用类Ls的相关方法。类Ls负责处理路径,并打印详情。

相关TAG标签
上一篇:SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)
下一篇:Windows平台下部署Git环境(内附完整操作截图)
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站