HDFS源码分析四-HDFS Client
4. HDFS Client ( 未完待续 )
目录:
4.1 认识 DFSClient ( 未完待续 )
4.2 输入流 ( 未完待续 )
4.3 输出流 ( 未完待续 )
4.4 DistributedFileSystem 的实现 ( 未完待续 )
4.5 HDFS 常用工具 ( 未完待续 )
4.5.1 FsShell ( 未完待续 )
4.5.2 DFSAdmin ( 未完待续 )
内容:
客户端 DFSClient 和建立在 DFSClient 基础上的 DistributedFileSystem, DFSAdmin 和 FsShell, 屏蔽了 HDFS 系统的复杂性,为应用程序提供了标准的 Hadoop 文件系统应用程序接口, 文件系统 Shell 和管理工具.
客户端对文件的操作有多种方法: 可以在命令行通过 Hadoop Shell 来操作, 如在命令行输入 hadoop fs -help 可以查看, 具体参考4.5.1节. 第二种, 是根据提供的应用程序接口, 编程完成特定功能, ( 比如常见的 write 和 read 在 hadoop shell 中是没有的 ), 具体参考4.2节和4.3节.
4.1 认识 DFSClient ( 未完待续 )
4.2 输入流 ( 未完待续 )
4.3 输出流 ( 未完待续 )
4.4 DistributedFileSystem 的实现 ( 未完待续 )
4.5 HDFS 常用工具 ( 未完待续 )
使用 HDFS 时的两个常用工具是多任务工具 dfsadmin 和文件系统 Shell 命令.
多任务工具 dfsadmin 的实现是 org.apache.hadoop.hdfs.tools.DFSAdmin. 严格来说, 文件系统 Shell 是 Hadoop 文件系统( 注意, 不是 Hadoop 分布式文件系统 )的一部分, 它不但支持对 HDFS 进行操作, 也可用于其他实现了 Hadoop 文件系统的具体文件系统中, 它的实现是 org.apache.hadoop.fs 包中. DFSAdmin 继承自文件系统 Shell 命令的实现 FsShell, ( DFSAdmin extends FsShell ), 它们在用法上有很多的共同点.
4.5.1 FsShell ( 未完待续 )
Hadoop 文件系统 Shell 命令可以执行其他文件系统中常见的操作, 例如读取文件, 移动文件, 创建目录, 删除数据等. 在终端上可以通过下面的命令, 获得 Shell 命令的详细帮助信息:
hadoop fs -help
文件系统 Shell 命令包含了许多类似于传统 Shell 的命令,这些命令会与 HDFS 进行交互,执行类似于读取文件, 移动文件, 创建目录等操作. 在终端上执行下面的命令, 可以触发文件系统 Shell 命令的执行.
bin/hadoop fs <args>
我们研究文件系统的 Shell 实现:
FsShell 是一个 Java 程序, 是 HDFS 中用于执行文件系统 Shell 命令的类, 这个类的入口方法是 main() 方法, 是一个典型的基于 ToolRunner 实现的应用. FsShell.main() 中的 ToolRunner.run() 方法最终会调用 FsShell.run() 方法, FsShell.run() 会调用 CommandFactory.getInstance() 从参数中解析出命令对应的 Command 对象, 然后在 Command 对象上调用 run() 方法执行对应的操作.具体见下面的分析.
为了简化 Hadoop 命令行应用的开发( 大量 MapReduce 程序使用命令行方式运行作业 ), Hadoop 提供了一些辅助类, 包括 ToolRunner, GenericOptionsParser 和 Tool.
GenericOptionsParser 是一个类, 用来解释常用的 Hadoop 命令行选项, 并根据需要为 Hadoop 配置 Configuration 对象设置相应的配置项. 一般情况下不直接使用 GenericOptionsParser, 更方便的方式是: 实现 Tool 接口, 通过 ToolRunner 来运行应用程序, ToolRunner 内部调用 GenericOptionsParser, 相关代码如下:
// 该类在 org.apache.hadoop.fs.FsShell /**
* main() has some simple utility methods
* @param argv the command and its arguments
* @throws Exception upon error
*/
public static void main(String argv[]) throws Exception {
FsShell shell = newShellInstance();
Configuration conf = new Configuration();
conf.setQuietMode(false);
shell.setConf(conf);
int res;
try {
res = ToolRunner.run(shell, argv); // 注意这里的参数 shell 是 FsShell 的对象
} finally {
shell.close();
}
System.exit(res);
} /**
* run
*/
// 该方法根据不同的 Shell 命令调用不同的处理函数
@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];
Command instance = null;
try {
instance = commandFactory.getInstance(cmd); // init()注册过 FsCommand 的所有子类, 若命令是 copyFromLocal, 则返回 CopyFromLocal 类对象.
if (instance == null) {
throw new UnknownCommandException();
}
exitCode = instance.run(Arrays.copyOfRange(argv, 1, argv.length)); // 运行, 调用 Command 的 run(),
} 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;
} protected void init() throws IOException {
getConf().setQuietMode(true);
if (commandFactory == null) {
commandFactory = new CommandFactory(getConf());
commandFactory.addObject(new Help(), "-help"); // 把 Help 类添加到 CommandFactory 的 objectMap 以及 classMap 中
commandFactory.addObject(new Usage(), "-usage");
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)) {
factory.registerCommands(FsCommand.class); // 注册 FsCommand , 会调用 FsCommand 的 registerCommands() 方法
}
}
FsShell 实现了 Tool 接口, 在其 main() 方法中, 通过 ToolRunner 来运行 FsShell 对象:
// 该段代码在 org.apache.hadoop.util.ToolRunner /**
* Runs the <code>Tool</code> with its <code>Configuration</code>.
*
* Equivalent to <code>run(tool.getConf(), tool, args)</code>.
*
* @param tool <code>Tool</code> to run.
* @param args command-line arguments to the tool.
* @return exit code of the {@link Tool#run(String[])} method.
*/
public static int run(Tool tool, String[] args)
throws Exception{
return run(tool.getConf(), tool, args); // 调用本类的 run(...)方法
} /**
* Runs the given <code>Tool</code> by {@link Tool#run(String[])}, after
* parsing with the given generic arguments. Uses the given
* <code>Configuration</code>, or builds one if null.
*
* Sets the <code>Tool</code>'s configuration with the possibly modified
* version of the <code>conf</code>.
*
* @param conf <code>Configuration</code> for the <code>Tool</code>.
* @param tool <code>Tool</code> to run.
* @param args command-line arguments to the tool.
* @return exit code of the {@link Tool#run(String[])} method.
*/
public static int run(Configuration conf, Tool tool, String[] args)
throws Exception{
if(conf == null) {
conf = new Configuration();
}
GenericOptionsParser parser = new GenericOptionsParser(conf, args);
//set the configuration back, so that Tool can configure itself
tool.setConf(conf); //get the args w/o generic hadoop args
String[] toolArgs = parser.getRemainingArgs();
return tool.run(toolArgs); // 调用Tool 的 run() 方法, 而这里的 tool 之前在 FsShell main() 方法中调用run(...)传过来的参数, 是 FsShell 类对象, 所以调用的是 FsShell 的 run() 方法.
}
回到 FsShell 类的 run() 方法, 先调用 init() 方法初始化 , 在初始化方法内部调用本类的 registerCommands() 方法注册, 该方法内部调用 org.apache.hadoop.fs.shell.CommandFactory.registerCommands( FsCommand ) 注册 FsCommand :
首先传入的参数是 FsShell 类对象, 则这一步就是调用 FsShell 类的 registerCommands() 方法. 然后 FsShell 类的 registerCommands() 方法内部继续依次调用 CommandFactory 类的该方法, 只是传入的参数是 FsShell 的各种子类.包括 CopyCommands. 这里以 CopyFromLocal 命令为例, 它是 CopyCommands 类的内部类. 在依次调用时也会调用到 CopyCommands 的 registerCommands() 方法, 进而调用 CommandFactory 的 addClass() 方法, 把它的内部类添加到 classMap 中.
// 该段代码在 org.apache.hadoop.fs.shell.CommandFactory
/**
* Invokes "static void registerCommands(CommandFactory)" on the given class.
* This method abstracts the contract between the factory and the command
* class. Do not assume that directly invoking registerCommands on the
* given class will have the same effect.
* @param registrarClass class to allow an opportunity to register
*/
public void registerCommands(Class<?> registrarClass) {
try {
// 首先传入的参数是 FsShell 类对象, 则这一步就是调用 FsShell 类的 registerCommands() 方法.
// 然后 FsShell 类的 registerCommands() 方法内部继续依次调用 CommandFactory 类的该方法, 只是传入的参数是 FsShell 的各种子类.
// 这里以 copyFromLocal 命令为例, 它是 CopyCommands 类的内部类. 在依次调用时也会调用到 CopyCommands 的 registerCommands() 方法,
registrarClass.getMethod(
"registerCommands", CommandFactory.class
).invoke(null, this);
} catch (Exception e) {
throw new RuntimeException(StringUtils.stringifyException(e));
}
} /**
* Returns an instance of the class implementing the given command. The
* class must have been registered via
* {@link #addClass(Class, String...)}
* @param cmd name of the command
* @return instance of the requested command
*/
// 返回实现给定命令的类的一个实例。这个类必须通过 addClass() 注册过. 例,如果命令是 copyFromLocal,
public Command getInstance(String cmd) {
return getInstance(cmd, getConf());
} /**
* Get an instance of the requested command
* @param cmdName name of the command to lookup
* @param conf the hadoop configuration
* @return the {@link Command} or null if the command is unknown
*/
// 获取请求的命令的一个实例
public Command getInstance(String cmdName, Configuration conf) {
if (conf == null) throw new NullPointerException("configuration is null"); Command instance = objectMap.get(cmdName);
if (instance == null) {
Class<? extends Command> cmdClass = classMap.get(cmdName);
if (cmdClass != null) {
instance = ReflectionUtils.newInstance(cmdClass, conf);
instance.setName(cmdName);
instance.setCommandFactory(this);
}
}
return instance;
}
FsShell 类的 run() 方法, 在初始化之后, 会调用 org.apache.hadoop.fs.shell.Command 类的 run() 方法, 抽象类 Command 有两个实现类: 一个是 org.apache.hadoop.hdfs.tools.DFSAdmin 的内部抽象类 DFSAdminCommand; 一个是抽象类 org.apache.hadoop.fs.shell.FsCommand , 前面已经注册过 FsCommand, 所以调用是抽象类 FsCommand (最后调用是其子类).
Command.run() 方法会解析命令选项, 扩展命令的参数, 然后依次处理每个参数. Command.run() 方法的调用序列如下注释中所示的.
// 该段代码在 org.apache.hadoop.fs.shell.Command
/**
* Invokes the command handler. The default behavior is to process options,
* expand arguments, and then process each argument.
* <pre>
* run
* |-> {@link #processOptions(LinkedList)}
* \-> {@link #processRawArguments(LinkedList)}
* |-> {@link #expandArguments(LinkedList)}
* | \-> {@link #expandArgument(String)}*
* \-> {@link #processArguments(LinkedList)}
* |-> {@link #processArgument(PathData)}*
* | |-> {@link #processPathArgument(PathData)}
* | \-> {@link #processPaths(PathData, PathData...)}
* | \-> {@link #processPath(PathData)}*
* \-> {@link #processNonexistentPath(PathData)}
* </pre>
* Most commands will chose to implement just
* {@link #processOptions(LinkedList)} and {@link #processPath(PathData)}
*
* @param argv the list of command line arguments
* @return the exit code for the command
* @throws IllegalArgumentException if called with invalid arguments
*/
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();
} /**
* Must be implemented by commands to process the command line flags and
* check the bounds of the remaining arguments. If an
* IllegalArgumentException is thrown, the FsShell object will print the
* short usage of the command.
* @param args the command line arguments
* @throws IOException
*/
protected void processOptions(LinkedList<String> args) throws IOException {} /**
* Allows commands that don't use paths to handle the raw arguments.
* Default behavior is to expand the arguments via
* {@link #expandArguments(LinkedList)} and pass the resulting list to
* {@link #processArguments(LinkedList)}
* @param args the list of argument strings
* @throws IOException
*/
protected void processRawArguments(LinkedList<String> args)
throws IOException {
processArguments(expandArguments(args));
}
抽象类 FsCommand, 有很多子类 :
// 该段代码在 org.apache.hadoop.fs.shell.FsCommand
/**
* Register the command classes used by the fs subcommand
* @param factory where to register the class
*/
public static void registerCommands(CommandFactory factory) {
factory.registerCommands(AclCommands.class);
factory.registerCommands(CopyCommands.class); // 该方法先调用 CommandFactory 的 registerCommands() 方法, 进而调用 CopyCommands 的 registerCommands() 方法
factory.registerCommands(Count.class);
factory.registerCommands(Delete.class);
factory.registerCommands(Display.class);
factory.registerCommands(Find.class);
factory.registerCommands(FsShellPermissions.class);
factory.registerCommands(FsUsage.class);
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);
}
我们以 copyFromLocal 为例, hadoop fs [generic options] -copyFromLocal [-f] [-p] [-l] <localsrc> ... <dst> , 比如命令行 hadoop fs -copyFromLocal /home/hadoop/demo.txt /user/zc/input/demo.txt ,
copyFromLocal 就属于 org.apache.hadoop.fs.shell.CopyCommands 类的一个操作: 所以最后调用的是 CopyCommands 类的内部类 CopyFromLocal 的相应方法:
CopyFromLocal 类会首先在 CommandFactory 中注册 "-copyFromLocal" 命令, 用于 FsShell 调用 CommandFactory.getInstance() 方法解析出 CopyFromLocal 对象. 接着 CopyFromLocal.run() 方法会依次调用 继承 Put
// 该段代码属于 org.apache.hadoop.fs.shell.CopyCommands
public static void registerCommands(CommandFactory factory) {
factory.addClass(Merge.class, "-getmerge");
factory.addClass(Cp.class, "-cp");
factory.addClass(CopyFromLocal.class, "-copyFromLocal"); // 把 CopyFromLocal 类添加到 CommandFactory 类的 classMap 中.
factory.addClass(CopyToLocal.class, "-copyToLocal");
factory.addClass(Get.class, "-get");
factory.addClass(Put.class, "-put");
factory.addClass(AppendToFile.class, "-appendToFile");
}
public static class CopyFromLocal extends Put { // CopyFromLocal 继承 Put, CopyFromLocal 并没有实现方法, 所以调用的是 Put 的方法. 它们操作一样,只是名字不一样
public static final String NAME = "copyFromLocal";
public static final String USAGE = Put.USAGE;
public static final String DESCRIPTION = "Identical to the -put command.";
} /**
* Copy local files to a remote filesystem
*/
public static class Put extends CommandWithDestination {
public static final String NAME = "put";
public static final String USAGE = "[-f] [-p] [-l] <localsrc> ... <dst>";
public static final String DESCRIPTION =
"Copy files from the local file system " +
"into fs. Copying fails if the file already " +
"exists, unless the -f flag is given.\n" +
"Flags:\n" +
" -p : Preserves access and modification times, ownership and the mode.\n" +
" -f : Overwrites the destination if it already exists.\n" +
" -l : Allow DataNode to lazily persist the file to disk. Forces\n" +
" replication factor of 1. This flag will result in reduced\n" +
" durability. Use with care.\n"; @Override
protected void processOptions(LinkedList<String> args) throws IOException {
CommandFormat cf = new CommandFormat(1, Integer.MAX_VALUE, "f", "p", "l");
cf.parse(args);
setOverwrite(cf.getOpt("f"));
setPreserve(cf.getOpt("p"));
setLazyPersist(cf.getOpt("l"));
getRemoteDestination(args);
// should have a -r option
setRecursive(true);
} // commands operating on local paths have no need for glob expansion
@Override
protected List<PathData> expandArgument(String arg) throws IOException {
List<PathData> items = new LinkedList<PathData>();
try {
items.add(new PathData(new URI(arg), getConf()));
} catch (URISyntaxException e) {
if (Path.WINDOWS) {
// Unlike URI, PathData knows how to parse Windows drive-letter paths.
items.add(new PathData(arg, getConf()));
} else {
throw new IOException("unexpected URISyntaxException", e);
}
}
return items;
} @Override
protected void processArguments(LinkedList<PathData> args)
throws IOException {
// NOTE: this logic should be better, mimics previous implementation
if (args.size() == 1 && args.get(0).toString().equals("-")) {
copyStreamToTarget(System.in, getTargetPath(args.get(0)));
return;
}
super.processArguments(args);
}
}
Put 类 processArguments() 方法内部调用 org.apache.hadoop.fs.shell.CommandWithDestination 类的 copyStreamToTarget() 方法, 该方法内部继续调用本类的内部类 TargetFileSystem 的 writeStreamToFile() 方法, 该方法内部分两步: 第一是调用 create() 方法; 第二是调用 IOUtils.copyBytes().
这里先分析调用 create() 创建文件, 该方法内部判断是否是 lazyPersist 的, 不管是不是, 最终都会调用 org.apache.hadoop.fs.FileSystem 的 create() 方法.
抽象类 FileSystem 有很多实现类, 这里最后调用的是其子类 org.apache.hadoop.hdfs.DistributedFileSystem 的 create() 方法, (可以参考4.4节 ),
4.5.2 DFSAdmin ( 未完待续 )
2018-01-26 16:07:37
HDFS源码分析四-HDFS Client的更多相关文章
- HDFS源码分析之数据块及副本状态BlockUCState、ReplicaState
关于数据块.副本的介绍,请参考文章<HDFS源码分析之数据块Block.副本Replica>. 一.数据块状态BlockUCState 数据块状态用枚举类BlockUCState来表示,代 ...
- HDFS源码分析之UnderReplicatedBlocks(一)
http://blog.csdn.net/lipeng_bigdata/article/details/51160359 UnderReplicatedBlocks是HDFS中关于块复制的一个重要数据 ...
- HDFS源码分析数据块汇报之损坏数据块检测checkReplicaCorrupt()
无论是第一次,还是之后的每次数据块汇报,名字名字节点都会对汇报上来的数据块进行检测,看看其是否为损坏的数据块.那么,损坏数据块是如何被检测的呢?本文,我们将研究下损坏数据块检测的checkReplic ...
- HDFS源码分析心跳汇报之数据结构初始化
在<HDFS源码分析心跳汇报之整体结构>一文中,我们详细了解了HDFS中关于心跳的整体结构,知道了BlockPoolManager.BPOfferService和BPServiceActo ...
- HDFS源码分析DataXceiver之整体流程
在<HDFS源码分析之DataXceiverServer>一文中,我们了解到在DataNode中,有一个后台工作的线程DataXceiverServer.它被用于接收来自客户端或其他数据节 ...
- HDFS源码分析数据块校验之DataBlockScanner
DataBlockScanner是运行在数据节点DataNode上的一个后台线程.它为所有的块池管理块扫描.针对每个块池,一个BlockPoolSliceScanner对象将会被创建,其运行在一个单独 ...
- HDFS源码分析数据块复制监控线程ReplicationMonitor(二)
HDFS源码分析数据块复制监控线程ReplicationMonitor(二)
- HDFS源码分析数据块复制监控线程ReplicationMonitor(一)
ReplicationMonitor是HDFS中关于数据块复制的监控线程,它的主要作用就是计算DataNode工作,并将复制请求超时的块重新加入到待调度队列.其定义及作为线程核心的run()方法如下: ...
- HDFS源码分析之UnderReplicatedBlocks(二)
UnderReplicatedBlocks还提供了一个数据块迭代器BlockIterator,用于遍历其中的数据块.它是UnderReplicatedBlocks的内部类,有三个成员变量,如下: // ...
随机推荐
- JavaScript插件编写指南
在编写插件之前,大家要先了解做插件的几个要点: 插件需要满足的条件 一个可复用的插件需要满足以下条件: 插件自身的作用域与用户当前的作用域相互独立,也就是插件内部的私有变量不能影响使用者的环境变量: ...
- shell grep正则匹配汉字
Shell grep正则匹配中文 测试文本 demo_exe.c,内容如下,需要注意保存的编码格式,对输出到终端有影响: 我们中文操作系统ASNI默认是GBK的. #include<stdio. ...
- 【LeetCode with Python】 Sort List
博客域名:http://www.xnerv.wang 原题页面:https://oj.leetcode.com/problems/sort-list/ 题目类型: 难度评价:★ 本文地址:http:/ ...
- the ACID properties of HBase
http://hbase.apache.org/acid-semantics.html Apache HBase (TM) is not an ACID compliant database. How ...
- myeclipse10.0如何集成Git
现需要给myeclipse10.0集成git工具,经过搜索资料,现整理如下方法: myeclipse10.0对应的Git版本应该为:egit版本为2.3.1 下载地址:http://wiki.ecli ...
- 04-树4 是否同一棵二叉搜索树(25 point(s)) 【Tree】
04-树4 是否同一棵二叉搜索树(25 point(s)) 给定一个插入序列就可以唯一确定一棵二叉搜索树.然而,一棵给定的二叉搜索树却可以由多种不同的插入序列得到.例如分别按照序列{2, 1, 3}和 ...
- php memcache知识点总结
$memcache = new Memcache; $memcache->connect('localhost',11211) or die('Could not connect'); //me ...
- Mapper3中Example的高级使用方法
http://zhuangxiaoda.leanote.com/post/Mapper3%E4%B8%ADExample%E7%9A%84%E4%BD%BF%E7%94%A8%E6%96%B9%E6% ...
- vue组件挂载到全局方法
在最近的项目中,使用了bootstrap-vue来开发,然而在实际的开发过程中却发现这个UI提供的组件并不能打到我们预期的效果,像alert.modal等组件每个页面引入就得重复引入,并不像eleme ...
- HTML中级教程 自定义列表
在HTML初级教程中我们教授了无序列表和有序列表,很不幸,很像Peter Cushing的博士Who,自定义列表很容易被忽略.可能是因为自定义列表需要比无序列表和有序列表更多的设置和似乎更少用.当遭遇 ...