,作者: 范东来
3.3 如何访问HDFS
Hadoop海量数据处理:技术详解与项目实战
HDFS提供给HDFS客户端访问的方式多种多样,用户可以根据不同的情况选择不同的方式。
3.3.1 命令行接口
Hadoop自带一组命令行工具,而其中有关HDFS的命令是其工具集的一个子集。命令行工具虽然是最基础的文件操作方式,但却是最常用的。作为一名合格的Hadoop开发人员和运维人员,熟练掌握是非常有必要的。
执行hadoop dfs命令可以显示基本的使用信息。
[hadoop@master bin]$ hadoop dfs
Usage: java FsShell
[-ls <path>]
[-lsr <path>]
[-df [<path>]]
[-du <path>]
[-dus <path>]
[-count[-q] <path>]
[-mv <src> <dst>]
[-cp <src> <dst>]
[-rm [-skipTrash] <path>]
[-rmr [-skipTrash] <path>]
[-expunge]
[-put <localsrc> ... <dst>]
[-copyFromLocal <localsrc> ... <dst>]
[-moveFromLocal <localsrc> ... <dst>]
[-get [-ignoreCrc] [-crc] <src> <localdst>]
[-getmerge <src> <localdst> [addnl]]
[-cat <src>]
[-text <src>]
[-copyToLocal [-ignoreCrc] [-crc] <src> <localdst>]
[-moveToLocal [-crc] <src> <localdst>]
[-mkdir <path>]
[-setrep [-R] [-w] <rep> <path/file>]
[-touchz <path>]
[-test -[ezd] <path>]
[-stat [format] <path>]
[-tail [-f] <file>]
[-chmod [-R] <MODE[,MODE]... | OCTALMODE> PATH...]
[-chown [-R] [OWNER][:[GROUP]] PATH...]
[-chgrp [-R] GROUP PATH...]
[-help [cmd]]
表3-2列出了hadoop命令行接口,并用例子说明各自的功能。
3.3.2 Java API
本地访问HDFS最主要的方式是HDFS提供的Java应用程序接口,其他的访问方式都建立在这些应用程序接口之上。为了访问HDFS,HDFS客户端必须拥有一份HDFS的配置文件,也就是hdfs-site.xml文件,以获取NameNode的相关信息,每个应用程序也必须能访问Hadoop程序库JAR文件,也就是在$HADOOP_HOME、$HADOOP_HOME/lib下面的jar文件。
Hadoop是由Java编写的,所以通过Java API可以调用所有的HDFS的交互操作接口,最常用的是FileSystem类,它也是命令行hadoop fs的实现,其他接口在这一节也会有 介绍。
1.java.net.URL
先来看一个例子,如代码清单3-1所示。
代码清单3-1 java.net.URL示例
package com.hdfsclient;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.hadoop.fs.FsUrlStreamHandlerFactory;
import org.apache.hadoop.io.IOUtils;
public class MyCat {
static{
URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
}
public static void main(String[] args) throws MalformedURLException, IOException{
InputStream in = null;
try {
in = new URL(args[0]).openStream();
IOUtils.copyBytes(in, System.out, 4096,false);
} finally{
IOUtils.closeStream(in);
}
}
}
编译代码清单3-1所示的代码,导出为xx.jar文件,执行命令:
hadoop jar xx.jar hdfs://master:9000/user/hadoop/test
执行完成后,屏幕上输出HDFS的文件/user/hadoop/test的文件内容。
该程序是从HDFS读取文件的最简单的方式,即用java.net.URL对象打开数据流。代码中,静态代码块的作用是让Java程序识别Hadoop的HDFS url。
2.org.apache.hadoop.fs.FileSystem
虽然上面的方式是最简单的方式,但是在实际开发中,访问HDFS最常用的类还是FileSystem类。
(1)读取文件
读取文件的示例如代码清单3-2所示。
代码清单3-2 读取文件示例
package com.hdfsclient;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
public class FileSystemCat {
public static void main(String[] args) throws IOException {
String uri = "hdfs://master:9000/user/hadoop/test";
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(uri), conf);
InputStream in = null;
try {
in = fs.open(new Path(uri));
IOUtils.copyBytes(in, System.out, 4096,false);
} finally{
IOUtils.closeStream(in);
}
}
}
编译代码3-2,导出为xx.jar文件,上传至集群任意一节点,执行命令:
hadoop jar xx.jar com.hdfsclient.FileSystemCat
执行完成后控制台会输出文件内容。
FileSystem类的实例获取是通过工厂方法:
public static FileSystem get(URI uri,Configuration conf) throws IOException
其中Configuration对象封装了HDFS客户端或者HDFS集群的配置,该方法通过给定的URI方案和权限来确定要使用的文件系统。得到FileSystem实例之后,调用open()函数获得文件的输入流:
Public FSDataInputStream open(Path f) throws IOException
方法返回Hadoop独有的FSDataInputStream对象。
(2)写入文件
写入文件的示例如代码清单3-3所示。
代码清单3-3 写入文件示例
package com.hdfsclient;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
public class FileCopyFromLocal {
public static void main(String[] args) throws IOException {
//本地文件路径
String source = "/home/hadoop/test";
String destination = "hdfs://master:9000/user/hadoop/test2";
InputStream in = new BufferedInputStream(new FileInputStream(source));
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(destination),conf);
OutputStream out = fs.create(new Path(destination));
IOUtils.copyBytes(in, out, 4096,true);
}
}
编译代码清单3-3所示的代码,导出为xx.jar文件,上传至集群任意一节点,执行命令:
hadoop jar xx.jar com.hdfsclient.FileCopyFromLocal
FileSystem实例的create()方法返回FSDataOutputStream对象,与FSDataInputStream类不同的是,FSDataOutputStream不允许在文件中定位,这是因为HDFS只允许一个已打开的文件顺序写入,或在现有文件的末尾追加数据。
(3)创建HDFS的目录
创建HDFS目录的示例如代码清单3-4所示。
代码清单3-4 创建HDFS目录示例
package com.hdfsclient;
import java.io.IOException;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class CreateDir {
public static void main(String[] args){
String uri = "hdfs://master:9000/user/test";
Configuration conf = new Configuration();
try {
FileSystem fs = FileSystem.get(URI.create(uri), conf);
Path dfs=new Path("hdfs://master:9000/user/test");
fs.mkdirs(dfs);
} catch (IOException e) {
e.printStackTrace();
}
}
}
编译代码清单3-4所示的代码,导出为xx.jar文件,上传至集群任意一节点,执行命令:
hadoop jar xx.jar com.hdfsclient.CreateDir
运行完成后可以用命令hadoop dfs -ls验证目录是否创建成功。
(4)删除HDFS上的文件或目录
删除HDFS上的文件或目录的示例如代码清单3-5所示。
代码清单3-5 删除HDFS上的文件或目录示例
package com.hdfsclient;
import java.io.IOException;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class DeleteFile {
public static void main(String[] args){
String uri = "hdfs://master:9000/user/hadoop/test";
Configuration conf = new Configuration();
try {
FileSystem fs = FileSystem.get(URI.create(uri), conf);
Path delef=new Path("hdfs://master:9000/user/hadoop");
boolean isDeleted=fs.delete(delef,false);
//是否递归删除文件夹及文件夹下的文件
//boolean isDeleted=fs.delete(delef,true);
System.out.println(isDeleted);
} catch (IOException e) {
e.printStackTrace();
}
}
}
编译代码清单3-5所示的代码,导出为xx.jar文件,上传至集群任意一节点,执行命令:
hadoop jar xx.jar com.hdfsclient.DeleteFile
如果需要递归删除文件夹,则需将fs.delete(arg0, arg1)方法的第二个参数设为true。
(5)查看文件是否存在
查看文件的示例如代码清单3-6所示。
代码清单3-6 查看文件示例
package com.hdfsclient;
import java.io.IOException;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class CheckFileIsExist {
public static void main(String[] args){
String uri = "hdfs://master:9000/user/hadoop/test";
Configuration conf = new Configuration();
try {
FileSystem fs = FileSystem.get(URI.create(uri), conf);
Path path=new Path(url);
boolean isExists=fs.exists(path);
System.out.println(isExists);
} catch (IOException e) {
e.printStackTrace();
}
}
}
编译代码清单3-6所示的代码,导出为xx.jar文件,上传至集群任意一台节点,执行命令:
hadoop jar xx.jar com.hdfsclient.CheckFileIsExist
(6)列出目录下的文件或目录名称
列出目录下的文件或目录名称的示例如代码清单3-7所示。
代码清单3-7 列出目录下的文件或目录名称示例
package com.hdfsclient;
import java.io.IOException;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class ListFiles {
public static void main(String[] args){
String uri = "hdfs://master:9000/user";
Configuration conf = new Configuration();
try {
FileSystem fs = FileSystem.get(URI.create(uri), conf);
Path path =new Path(uri);
FileStatus stats[]=fs.listStatus(path);
for(int i = 0; i < stats.length; ++i){
System.out.println(stats[i].getPath().toString());
}
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
编译代码清单3-7所示的代码,导出为xx.jar文件,上传至集群任意一节点,执行命令:
hadoop jar xx.jar com.hdfsclient.ListFiles
运行后,控制台会打印出/user目录下的目录名称或文件名。
(7)文件存储的位置信息
文件存储的位置信息的示例如代码清单3-8所示。
代码清单3-8 文件存储的位置信息示例
package com.hdfsclient;
import java.io.IOException;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.BlockLocation;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class LocationFile {
public static void main(String[] args){
String uri = "hdfs://master:9000/user/test/test";
Configuration conf = new Configuration();
try {
FileSystem fs = FileSystem.get(URI.create(uri), conf);
Path fpath=new Path(uri);
FileStatus filestatus = fs.getFileStatus(fpath);
BlockLocation[] blkLocations = fs.getFileBlockLocations(filestatus, 0, filestatus.getLen());
int blockLen = blkLocations.length;
for(int i=0;i<blockLen;i++){
String[] hosts = blkLocations[i].getHosts();
System.out.println("block_"+i+"_location:"+hosts[0]);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
编译代码清单3-8所示的代码,导出为xx.jar文件,上传至集群任意一节点,执行命令:
hadoop jar xx.jar com.hdfsclient.LocationFile
前面提到过,HDFS的存储由DataNode的块完成,执行成功后,控制台会输出:
block_0_location:slave1
block_1_location:slave2
block_2_location:slave3
表示该文件被分为3个数据块存储,存储的位置分别为slave1、slave2、slave3。
3.SequenceFile
SequeceFile是HDFS API提供的一种二进制文件支持,这种二进制文件直接将对序列化到文件中,所以SequenceFile是不能直接查看的,可以通过hadoop dfs -text命令查看,后面跟要查看的SequenceFile的HDFS路径。
(1)写入SequenceFile
写入SequenceFile示例如代码清单3-9所示。
代码清单3-9 写入SequenceFile示例
package com.hdfsclient;
import java.io.IOException;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.SequenceFile;
import org.apache.hadoop.io.Text;
public class SequenceFileWriter {
private static final String[] text = {
"两行黄鹂鸣翠柳",
"一行白鹭上青天",
"窗含西岭千秋雪",
"门泊东吴万里船",
};
public static void main(String[] args) {
String uri = "hdfs://master:9000/user/hadoop/testseq";
Configuration conf = new Configuration();
SequenceFile.Writer writer = null;
try {
FileSystem fs = FileSystem.get(URI.create(uri), conf);
Path path =new Path(uri);
IntWritable key = new IntWritable();
Text value = new Text();
writer = SequenceFile.createWriter(fs, conf, path, key.getClass(), value.getClass());
for (int i = 0;i<100;i++){
key.set(100-i);
value.set(text[i%text.length]);
writer.append(key, value);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeStream(writer);
}
}
}
可以看到SequenceFile.Writer的构造方法需要制定键值对的类型。如果是日志文件,那么时间戳作为key,日志内容是value是非常合适的。
编译代码清单3-9所示的代码,导出为xx.jar文件,上传至集群任意一节点,执行命令:
hadoop jar xx.jar com.hdfsclient.SequenceFileWriter
运行完成后,执行命令:
hadoop dfs -text /user/hadoop/testseq
可以看到如下内容:
100 两行黄鹂鸣翠柳
99 一行白鹭上青天
98 窗含西岭千秋雪
97 门泊东吴万里船
……
2 窗含西岭千秋雪
1 门泊东吴万里船
(2)读取SequenceFile
读取SequenceFile示例如代码清单3-10所示。
代码清单3-10 读取SequenceFile示例
package com.hdfsclient;
import java.io.IOException;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.SequenceFile;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.util.ReflectionUtils;
public class SequenceFileReader {
public static void main(String[] args) {
String uri = "hdfs://master:9000/user/hadoop/testseq";
Configuration conf = new Configuration();
SequenceFile.Reader reader = null;
try {
FileSystem fs = FileSystem.get(URI.create(uri), conf);
Path path = new Path(uri);
reader = new SequenceFile.Reader(fs, path, conf);
Writable key = (Writable) ReflectionUtils.newInstance(reader.getKeyClass(), conf);
Writable value = (Writable) ReflectionUtils.newInstance(reader.getValueClass(), conf);
long position = reader.getPosition();
while(reader.next(key,value)){
System.out.printf("[%s]\t%s\n",key,value);
position = reader.getPosition();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeStream(reader);
}
}
}
编译代码清单3-10所示的代码,导出为xx.jar文件,上传至集群任意一节点,执行命令:
hadoop jar xx.jar com.hdfsclient.SequenceFileReader
运行完成后,控制台会输出:
[100] 两行黄鹂鸣翠柳
[99] 一行白鹭上青天
[98] 窗含西岭千秋雪
[97] 门泊东吴万里船
……
[2] 窗含西岭千秋雪
[1] 门泊东吴万里船
3.3.3 其他常用的接口
1.Thrift
HDFS的底层的接口是通过Java API提供的,所以非Java程序访问HDFS比较麻烦。弥补方法是通过thriftfs功能模块中的Thrift API将HDFS封装成一个Apache Thrift服务。这样任何与Thrift绑定的语言都能与HDFS进行交互。为了使用Thrift API,需要运行提供Thrift服务的服务器,并以代理的方式访问Hadoop。目前支持远程调用Thrift API的语言有C++、Perl、PHP、Python和Ruby。
2.FUSE
用户空间文件系统(Filesystem in Universe,FUSE)允许把按照用户空间实现的文件系统整合成一个Unix文件系统。通过使用Hadoop的Fuse-DFS功能模块,任意一个Hadoop文件系统(如HDFS)均可作为一个标准文件系统进行挂载。随后便可以使用普通Unix命令,如ls、cat等与该文件系统交互,还可以通过任意一种编程语言调用POSIX库来访问文件系统。
其余可以访问HDFS的还有WebDAV、HTTP、ftp接口,不过并不常用。
3.3.4 Web UI
我们还可以通过NameNode的50070端口号访问HDFS的Web UI,HDFS的Web UI包含了非常丰富并且实用的信息,如图3-10所示。通过HDFS的Web UI了解集群的状况是一名合格Hadoop开发和运维人员必备的条件。
我们可以直接在浏览器中输入master:9000(即NameNode的主机名:端口号)便可进入Web UI。如图3-10,点击“Browse the filesystem”可以查看整个HDFS的目录树,点击“Namenode Logs”可以查看所有的NameNode的日志,这对于排查错误十分有帮助。下面的表格展示了整个HDFS大致的信息,如总容量、使用量、剩余量等,其中点击“Live Nodes”选项,可以看到所有DataNode节点的状况,如图3-11所示。