|
Hadoop有提供一些腳本命令,以便于我們對HDFS進(jìn)行管理,可以通過命令hadoop fs進(jìn)行查看:
通過以上使用說明可以發(fā)現(xiàn),里面提供了大多數(shù)和我們在本地操作文件系統(tǒng)相類似的命令,例如,cat查看文件內(nèi)容,chgrp改變用戶群組權(quán)限,chmod改變用戶權(quán)限,chown改變用戶擁有者權(quán)限,還有創(chuàng)建目錄,查看目錄,移動文件,重命名等等。
hadoop fs -ls
這里,我們來看看命令hadoop fs -ls:

這個命令大家肯定非常熟悉,在Linux下使用超級頻繁,功能就是列出指定目錄下的文件及文件夾。那接下來,我們來看看它是怎樣查找到此目錄下的文件及文件夾的。
在運行hadoop fs -ls /命令之后,真正的命令只是hadoop,后面只是參數(shù),那么,這個hadoop命令到底是哪呢?如果說集群是自己手動搭配的話,那大家肯定知道,這個命令就在${HADOOP_HOME}/bin目錄下,但如果集群是自動化部署的時候,你可能一下子找不到這個命令在哪,此時,可以使用以下命令查找:

可以看到,這個命令應(yīng)該是在目錄/usr/bin/之下,使用vim /usr/bin/hadoop查看命令詳細(xì):
#!/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了一些環(huán)境變量,使得之后運行的子程序都可以共享這些變量的值;二是執(zhí)行了命令hadoop.distro命令,并傳上了所有參數(shù)。
現(xiàn)在,我們來看下命令hadoop.distro做了哪些事,由于代碼有點小多,我就不全部貼了,只貼與執(zhí)行命令hadoop fs -ls /相關(guān)的代碼。
命令hadoop.distro做的事情是:根據(jù)之前傳入的參數(shù),然后做一些判斷,確定一些變量的值,最后執(zhí)行的是以下命令:
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 / 注意:這里已經(jīng)沒有參數(shù)fs了
因此,命令hadoop.distro也就轉(zhuǎn)換成執(zhí)行一個JAVA類了,然后繼續(xù)帶上參數(shù)。
打開hadoop的源代碼,找到類org.apache.hadoop.fs.FsShell,它的main方法如下:
public static void main(String argv[]) throws Exception {
FsShell shell = newShellInstance(); //創(chuàng)建FsShell實例
Configuration conf = new Configuration(); //配置類,
conf.setQuietMode(false); //設(shè)置成“非安靜模式”,默認(rèn)為“安靜模式”,在安靜模式下,error和information的信息不會被記錄。
shell.setConf(conf);
int res;
try {
res = ToolRunner.run(shell, argv); //ToolRunner就是一個工具類,用于執(zhí)行實現(xiàn)了接口`Tool`的類
} finally {
shell.close();
}
System.exit(res);
}
ToolRunner類結(jié)合GenericOptionsParser類來解析命令行參數(shù),
在運行上述ToolRunner.run(shell, argv)代碼之后,經(jīng)過一番解釋之后,最后真正執(zhí)行的仍然是類FsShell的run方法,而且對其參數(shù)進(jìn)行了解析,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]; //取到第一個參數(shù),即 ls
Command instance = null;
try {
// 取得實現(xiàn)了該命令(ls)的命令實例,并且此類已經(jīng)通過類CommandFactory的addClass方法進(jìn)行了注冊
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的代碼,我們可以發(fā)現(xiàn),它有一個靜態(tài)方法registerCommands,這個方法就是對類Ls進(jìn)行注冊,但是,這只是一個靜態(tài)方法,那么,到底是在哪進(jìn)行了此方法的調(diào)用呢?
class Ls extends FsCommand {
public static void registerCommands(CommandFactory factory) {
factory.addClass(Ls.class, "-ls");
factory.addClass(Lsr.class, "-lsr");
}
...
細(xì)心的朋友可能已經(jīng)發(fā)現(xiàn),就在類FsShell的run方法中,調(diào)用了一個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");
// 注冊,調(diào)用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)) {
// 調(diào)用CommandFactory類的registerCommands方法
// 注意,這里傳的參數(shù)是類FsCommand
factory.registerCommands(FsCommand.class);
}
}
CommandFactory類的registerCommands方法如下:
public void registerCommands(Class<?> registrarClass) {
try {
// 這里觸發(fā)的是類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");
}
也就是,在調(diào)用init方法的時候,對這些命令類進(jìn)行了注冊。
因此,上面的那個instance,在這里的話,其實就是類Ls的實例。類Ls繼承類FsCommand,而類FsCommand是繼承類Command,前面instance調(diào)用的run方法其實是父類Command的run方法,此方法主要做了兩件事,一是處理配置選項,如-d,-R,-h,二是處理參數(shù),如下:
public int run(String...argv) {
LinkedList<String> args = new LinkedList<String>(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的調(diào)用層次關(guān)系如下:
\-> processRawArguments(LinkedList)
|-> expandArguments(LinkedList)
| \-> expandArgument(String)*
\-> processArguments(LinkedList)
|-> processArgument(PathData)*
| |-> processPathArgument(PathData)
| \-> processPaths(PathData, PathData...)
| \-> processPath(PathData)*
\-> processNonexistentPath(PathData)
從這個層次關(guān)系中可以看出,整個方法是先進(jìn)行展開參數(shù),傳入的參數(shù)是LinkedList<String>,展開后的參數(shù)是LinkedList<PathData>,PathData類中包含了Path,FileStatus,FileSystem。其實,當(dāng)程序運行到這里的時候,命令ls的結(jié)果就已經(jīng)可以通過類PathData中的相關(guān)方法獲取了。
展開參數(shù)后,開始進(jìn)行處理參數(shù),此時的參數(shù)就是LinkedList<PathData>,然后循環(huán)處理此List,先是判斷目錄是否存在,是否需要遞歸查找,是否只是列出本目錄(就是看有沒有-R和-d參數(shù)),我們來看一下到底是如何輸出結(jié)果的:
@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); // 計算列寬,重新構(gòu)建格式字符串
super.processPaths(parent, items);
}
看到這里,大家是不是覺得很面熟?沒想起來?我們上個圖:

這下看到了吧,最是輸出結(jié)果的第一行,找到11項。
接下來重新調(diào)整了一下列寬,最后調(diào)用了父類的processPaths方法,我們繼續(xù)來看父類的這個方法,它做了哪些事:
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); // 如果有指定參數(shù) -R,則需要進(jìn)行遞歸
}
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() ? "+" : " "), // 獲取權(quán)限
(stat.isFile() ? stat.getReplication() : "-"),
stat.getOwner(), // 獲取擁有者
stat.getGroup(), // 獲取組
formatSize(stat.getLen()), // 獲取大小
dateFormat.format(new Date(stat.getModificationTime())), // 日期
item // 項,即路徑
);
out.println(line); // 打印行
}
到這里,命令hadoop fs -ls /的執(zhí)行過程基本已經(jīng)結(jié)束(關(guān)于文件系統(tǒng)內(nèi)部細(xì)節(jié),后續(xù)再講),這就是整個命令執(zhí)行的過程。最后,我們來總結(jié)一下:
- 執(zhí)行shell。執(zhí)行命令
hadoop fs -ls /,首先執(zhí)行的是shell命令,然后轉(zhuǎn)換成執(zhí)行Java類。
- 執(zhí)行Java。在執(zhí)行Java類的時候,使用工具類對其進(jìn)行配置項解析,并使用反射機(jī)制對命令進(jìn)行了轉(zhuǎn)換,于是后面變成了調(diào)用類
Ls的run方法。
- 調(diào)用類
Ls的相關(guān)方法。類Ls負(fù)責(zé)處理路徑,并打印詳情。
|